diff --git a/src/components/Diagram.astro b/src/components/Diagram.astro index 5b6a496..3464540 100644 --- a/src/components/Diagram.astro +++ b/src/components/Diagram.astro @@ -10,7 +10,7 @@ interface Props { const { name, caption } = Astro.props; const captions: Record = { - "edge-ai": "Design → single-press pipeline → readiness-gated GPU inference at the edge", + "edge-ai": "Design → single-touch pipeline → readiness-gated GPU inference at the edge", "iac-fleet": "One source of truth → AWX/Ansible → identical edge nodes, even air-gapped", homelab: "Bare metal → GitOps clusters → services, exposed outbound-only via a tunnel", "authentik-sso": "One identity provider, 2FA at the flow; apps validate over an internal back channel", diff --git a/src/components/Footer.astro b/src/components/Footer.astro index 8c1d5b2..406e7d5 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -35,7 +35,7 @@ const emailHref = socials.find((s) => s.label === "Email")?.href;

HEAD {buildInfo.sha} · built {buildInfo.builtAt} - · reconciled by ArgoCD + · reconciled by Argo CD

$ contact --priority low-ms

diff --git a/src/components/ProjectCard.astro b/src/components/ProjectCard.astro index 5b98fca..1047373 100644 --- a/src/components/ProjectCard.astro +++ b/src/components/ProjectCard.astro @@ -34,7 +34,7 @@ const dossierNo = String(index).padStart(3, "0");

{p.title}

-

The argument: {p.outcome}

+

Outcome {p.outcome}

@@ -146,6 +146,14 @@ const dossierNo = String(index).padStart(3, "0"); .dossier:hover .dossier__arrow { transform: translateX(4px); } + .dossier__outcome-label { + font-family: var(--mono, ui-monospace, monospace); + font-size: 11px; + letter-spacing: .08em; + text-transform: uppercase; + color: var(--accent, #3fbaf5); + margin-right: 6px; + } .dossier__argument { margin: 0; font-size: 14px; diff --git a/src/content/blog/shipping-this-site.md b/src/content/blog/shipping-this-site.md index 963dcf5..3f055b0 100644 --- a/src/content/blog/shipping-this-site.md +++ b/src/content/blog/shipping-this-site.md @@ -1,7 +1,7 @@ --- title: "Shipping this site: GitOps from a homelab to the public internet" date: 2026-06-15 -summary: "How this portfolio is built and served - Astro to a container image, a self-hosted Gitea registry, ArgoCD, and a Cloudflare Tunnel - with security as acceptance criteria, not polish." +summary: "How this portfolio is built and served - Astro to a container image, a self-hosted Gitea registry, Argo CD, and a Cloudflare Tunnel - with security as acceptance criteria, not polish." tags: ["gitops", "astro", "homelab", "security"] hero: "/blog/shipping-this-site.webp" heroAlt: "A glowing data container travels a luminous rail from a small server rack toward a softly glowing globe of Earth, wrapped by a reconcile loop." @@ -18,7 +18,7 @@ by GitOps. No special case for "it's just a website." The build is baked into a hardened `nginx-unprivileged` image and pushed to a **self-hosted public Gitea registry**, deliberately a separate instance from the private one holding my infrastructure code, so the public artifact and the private estate never share a trust boundary. The image digest -is then pinned in a private `home-ops` repo, **ArgoCD** reconciles that repo onto the cluster, and +is then pinned in a private `home-ops` repo, **Argo CD** reconciles that repo onto the cluster, and a **Cloudflare Tunnel** exposes exactly one service, this site, outbound-only. Follow the chain and notice what's missing. No open ports: the tunnel dials out. No server runtime: @@ -33,7 +33,7 @@ gesture at. The site didn't ship until every box was ticked: ```text [x] Static output - no server runtime to attack -[x] Strict CSP, no unsafe-inline / unsafe-eval +[x] CSP: script-src 'self', no inline or eval JavaScript (style-src keeps 'unsafe-inline' for Shiki) [x] Self-hosted fonts - zero third-party requests [x] No secrets in the client bundle (verified by build-time grep) [x] Outbound-only tunnel, single hostname, no catch-all diff --git a/src/content/blog/snat-ate-my-source.md b/src/content/blog/snat-ate-my-source.md index 66f1223..d6e39ea 100644 --- a/src/content/blog/snat-ate-my-source.md +++ b/src/content/blog/snat-ate-my-source.md @@ -48,7 +48,7 @@ traffic on the way out), pin-downable with a `/32`. That's how the LLM gateway's be a single line: one legitimate consumer, the agent cluster next door, exactly one `/32` allowed in. Here's the one that costs a 2am. I made that change with `kubectl patch` - quick, out-of-band, worked -immediately. The service was GitOps-managed with self-heal on. Within seconds ArgoCD noticed the live +immediately. The service was GitOps-managed with self-heal on. Within seconds Argo CD noticed the live object differed from git and put it back the way the repo said, which re-enabled the SNAT, which re-broke the policy, which took DNS down with it. **The change must be committed**, or the platform will politely undo your fix while you sleep. diff --git a/src/content/projects/edge-ai-platform.md b/src/content/projects/edge-ai-platform.md index aa25790..b9822af 100644 --- a/src/content/projects/edge-ai-platform.md +++ b/src/content/projects/edge-ai-platform.md @@ -1,6 +1,6 @@ --- title: "Single-Touch Edge AI Platform" -outcome: "Turned a high-level edge-AI design into a single-press deployment running on Kubernetes at the store edge." +outcome: "Turned a high-level edge-AI design into a single-touch deployment running on Kubernetes at the store edge." summary: "Store-edge Kubernetes running GPU-backed AI workloads, deployed from one command, with readiness-gated GPUs so inference never starts before the hardware is ready." role: "Infrastructure / DevOps Engineer · Woolworths" period: "2025 - Present" diff --git a/src/content/projects/self-hosted-ai-homelab.md b/src/content/projects/self-hosted-ai-homelab.md index e9a83e3..d71849a 100644 --- a/src/content/projects/self-hosted-ai-homelab.md +++ b/src/content/projects/self-hosted-ai-homelab.md @@ -1,10 +1,10 @@ --- title: "Self-Hosted AI & Homelab Platform" outcome: "A production-grade homelab - GitOps from bare metal to local AI, and the platform that serves this very site." -summary: "Proxmox with PCIe passthrough under Talos and OpenShift clusters, all driven by ArgoCD GitOps: local LLM inference, split-horizon DNS, 2FA/SSO VPN, full observability and NAS-backed backups." +summary: "Proxmox with PCIe passthrough under Talos and OpenShift clusters, all driven by Argo CD GitOps: local LLM inference, split-horizon DNS, 2FA/SSO VPN, full observability and NAS-backed backups." role: "Owner / Operator" period: "Ongoing" -stack: ["Talos", "OpenShift", "ArgoCD", "Proxmox", "Local LLM", "Cloudflare Tunnel"] +stack: ["Talos", "OpenShift", "Argo CD", "Proxmox", "Local LLM", "Cloudflare Tunnel"] featured: true order: 15 cover: "self-hosted-ai-homelab" @@ -26,7 +26,7 @@ genuine proving ground for Kubernetes, GPUs, AI and security, not a pile of cont ## Design Proxmox provides the hypervisor layer with **PCIe passthrough** (GPU and storage) into -single-node **Talos** and **OpenShift** clusters. Everything is **ArgoCD GitOps** - the +single-node **Talos** and **OpenShift** clusters. Everything is **Argo CD GitOps** - the cluster state lives in git and reconciles itself. On top: **local LLM inference** on a Blackwell-class GPU, **split-horizon DNS** via Pi-hole, a VPN with **2FA/SSO**, and a **Prometheus / Grafana** observability stack. ZFS handles storage tiering; restic ships diff --git a/src/data/experience.ts b/src/data/experience.ts index b63ec82..b1699c8 100644 --- a/src/data/experience.ts +++ b/src/data/experience.ts @@ -15,7 +15,7 @@ export const experience: Role[] = [ org: "Woolworths", period: "Aug 2025 - Present", summary: - "Core DevOps infrastructure engineer for store-edge solutions - turning high-level designs into single-press deployments and CD pipelines.", + "Core DevOps infrastructure engineer for store-edge solutions - turning high-level designs into single-touch deployments and CD pipelines.", highlights: [ "Single-touch deployment of an edge AI solution running on Kubernetes at the store edge", "GPU brought online as code - passthrough via ESXi, end-state manifests / Helm charts", @@ -76,6 +76,5 @@ export const education: string[] = [ "Advanced Diploma in IT Networking and Security", "Diploma in IT Networking", "Certificate IV in IT Networking", - "Ongoing study in IT certifications and new techniques", ]; diff --git a/src/data/fleet.ts b/src/data/fleet.ts index afe5291..aec8859 100644 --- a/src/data/fleet.ts +++ b/src/data/fleet.ts @@ -4,8 +4,8 @@ // Facts the hero typing bar cycles through. Real, verbatim - no invented numbers. export const fleetFacts = [ - "6 single-node Talos clusters · 0 SSH daemons · every machine is a document", - "139 ArgoCD Applications reconciled from one hub", + "6 single-node Talos clusters / 0 SSH daemons / every node rebuilt from committed config", + "139 Argo CD Applications reconciled from one hub", "170+ CiliumNetworkPolicies fleet-wide - every namespace starts at default-deny", "zero open inbound ports - the origin dials out", "*.bztmon.org - a Let's Encrypt wildcard per cluster · DNS-01 · auto-renewed", diff --git a/src/data/site.ts b/src/data/site.ts index b91e256..fd8693f 100644 --- a/src/data/site.ts +++ b/src/data/site.ts @@ -20,7 +20,7 @@ export const site = { "Edge AI", ], // One-paragraph elevator pitch for the About section. - bio: "I'm an infrastructure and DevOps engineer who turns high-level designs into single-press deployments. My day job is store-edge Kubernetes running GPU-backed AI; my craft is the automation, GitOps and network policy that make a fleet behave the same way every time. I've run global infrastructure at enterprise scale - a thousand VMs, multi-region migrations, segmented networks - and I bring that same rigour to the smallest edge node. I'm an IaC enthusiast: if it's a manual step, I want it gone.", + bio: "I'm an infrastructure and platform engineer. I spend most of my time on the automation people usually leave in runbooks: store-edge Kubernetes running GPU-backed AI workloads, the GitOps pipelines that deploy them, and the network policy that keeps a fleet behaving the same way on every node. Before that I worked across larger enterprise estates - around a thousand VMs, multi-region migrations, segmented networks. I like infrastructure that can be rebuilt and explained from the repo. If a deployment still depends on someone remembering the right sequence of clicks, there's usually another piece worth automating.", // Canonical URL (used for OG/sitemap/RSS). url: "https://www.bztmon.com", diff --git a/src/data/skills.ts b/src/data/skills.ts index 78affb1..6604beb 100644 --- a/src/data/skills.ts +++ b/src/data/skills.ts @@ -50,7 +50,7 @@ export const skills: SkillGroup[] = [ "vSphere / VMware ESXi", "Proxmox", "containerd", - "ArgoCD / GitOps", + "Argo CD / GitOps", "Helm & Kustomize", ], }, diff --git a/src/pages/projects/index.astro b/src/pages/projects/index.astro index bd58603..83517f8 100644 --- a/src/pages/projects/index.astro +++ b/src/pages/projects/index.astro @@ -10,11 +10,11 @@ const projects = (await getCollection("projects")).sort( --- -
+

Edge Kubernetes, GPU inference, self-hosted AI, productionised network automation, and the infrastructure rigour that ties it together - each with the problem, the design, and - the outcome. More always cooking. + the outcome.

{projects.map((entry, i) => )}