portfolio pass: correct the CSP claim to match what nginx sends, rename the project outcome label, plain-english the projects intro and bio, standardise on Argo CD and single-touch, drop the education filler line
build-and-deploy / build (push) Failing after 14m34s

This commit is contained in:
2026-08-18 01:11:25 +10:00
parent 7542ae2665
commit 25425d6f6a
12 changed files with 26 additions and 19 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ interface Props {
const { name, caption } = Astro.props; const { name, caption } = Astro.props;
const captions: Record<string, string> = { const captions: Record<string, string> = {
"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", "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", 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", "authentik-sso": "One identity provider, 2FA at the flow; apps validate over an internal back channel",
+1 -1
View File
@@ -35,7 +35,7 @@ const emailHref = socials.find((s) => s.label === "Email")?.href;
<p class="term__out"> <p class="term__out">
HEAD <span class="term__val">{buildInfo.sha}</span> HEAD <span class="term__val">{buildInfo.sha}</span>
<span class="term__sep">·</span> built <span class="term__val">{buildInfo.builtAt}</span> <span class="term__sep">·</span> built <span class="term__val">{buildInfo.builtAt}</span>
<span class="term__sep">·</span> reconciled by <span class="term__val">ArgoCD</span> <span class="term__sep">·</span> reconciled by <span class="term__val">Argo CD</span>
</p> </p>
<p class="term__line"><span class="term__prompt">$</span> contact --priority low-ms</p> <p class="term__line"><span class="term__prompt">$</span> contact --priority low-ms</p>
+9 -1
View File
@@ -34,7 +34,7 @@ const dossierNo = String(index).padStart(3, "0");
<h3 class="dossier__title"> <h3 class="dossier__title">
{p.title} <span class="dossier__arrow" aria-hidden="true">→</span> {p.title} <span class="dossier__arrow" aria-hidden="true">→</span>
</h3> </h3>
<p class="dossier__argument">The argument: {p.outcome}</p> <p class="dossier__argument"><span class="dossier__outcome-label">Outcome</span> {p.outcome}</p>
<ul class="dossier__chips mono" role="list"> <ul class="dossier__chips mono" role="list">
{p.stack.map((s) => <li class="dchip">{s}</li>)} {p.stack.map((s) => <li class="dchip">{s}</li>)}
</ul> </ul>
@@ -146,6 +146,14 @@ const dossierNo = String(index).padStart(3, "0");
.dossier:hover .dossier__arrow { .dossier:hover .dossier__arrow {
transform: translateX(4px); 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 { .dossier__argument {
margin: 0; margin: 0;
font-size: 14px; font-size: 14px;
+3 -3
View File
@@ -1,7 +1,7 @@
--- ---
title: "Shipping this site: GitOps from a homelab to the public internet" title: "Shipping this site: GitOps from a homelab to the public internet"
date: 2026-06-15 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"] tags: ["gitops", "astro", "homelab", "security"]
hero: "/blog/shipping-this-site.webp" 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." 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 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 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 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. 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: 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 ```text
[x] Static output - no server runtime to attack [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] Self-hosted fonts - zero third-party requests
[x] No secrets in the client bundle (verified by build-time grep) [x] No secrets in the client bundle (verified by build-time grep)
[x] Outbound-only tunnel, single hostname, no catch-all [x] Outbound-only tunnel, single hostname, no catch-all
+1 -1
View File
@@ -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. 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 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 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 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. will politely undo your fix while you sleep.
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
title: "Single-Touch Edge AI Platform" 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." 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" role: "Infrastructure / DevOps Engineer · Woolworths"
period: "2025 - Present" period: "2025 - Present"
@@ -1,10 +1,10 @@
--- ---
title: "Self-Hosted AI & Homelab Platform" 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." 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" role: "Owner / Operator"
period: "Ongoing" period: "Ongoing"
stack: ["Talos", "OpenShift", "ArgoCD", "Proxmox", "Local LLM", "Cloudflare Tunnel"] stack: ["Talos", "OpenShift", "Argo CD", "Proxmox", "Local LLM", "Cloudflare Tunnel"]
featured: true featured: true
order: 15 order: 15
cover: "self-hosted-ai-homelab" cover: "self-hosted-ai-homelab"
@@ -26,7 +26,7 @@ genuine proving ground for Kubernetes, GPUs, AI and security, not a pile of cont
## Design ## Design
Proxmox provides the hypervisor layer with **PCIe passthrough** (GPU and storage) into 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 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 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 **Prometheus / Grafana** observability stack. ZFS handles storage tiering; restic ships
+1 -2
View File
@@ -15,7 +15,7 @@ export const experience: Role[] = [
org: "Woolworths", org: "Woolworths",
period: "Aug 2025 - Present", period: "Aug 2025 - Present",
summary: 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: [ highlights: [
"Single-touch deployment of an edge AI solution running on Kubernetes at the store edge", "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", "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", "Advanced Diploma in IT Networking and Security",
"Diploma in IT Networking", "Diploma in IT Networking",
"Certificate IV in IT Networking", "Certificate IV in IT Networking",
"Ongoing study in IT certifications and new techniques",
]; ];
+2 -2
View File
@@ -4,8 +4,8 @@
// Facts the hero typing bar cycles through. Real, verbatim - no invented numbers. // Facts the hero typing bar cycles through. Real, verbatim - no invented numbers.
export const fleetFacts = [ export const fleetFacts = [
"6 single-node Talos clusters · 0 SSH daemons · every machine is a document", "6 single-node Talos clusters / 0 SSH daemons / every node rebuilt from committed config",
"139 ArgoCD Applications reconciled from one hub", "139 Argo CD Applications reconciled from one hub",
"170+ CiliumNetworkPolicies fleet-wide - every namespace starts at default-deny", "170+ CiliumNetworkPolicies fleet-wide - every namespace starts at default-deny",
"zero open inbound ports - the origin dials out", "zero open inbound ports - the origin dials out",
"*.bztmon.org - a Let's Encrypt wildcard per cluster · DNS-01 · auto-renewed", "*.bztmon.org - a Let's Encrypt wildcard per cluster · DNS-01 · auto-renewed",
+1 -1
View File
@@ -20,7 +20,7 @@ export const site = {
"Edge AI", "Edge AI",
], ],
// One-paragraph elevator pitch for the About section. // 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). // Canonical URL (used for OG/sitemap/RSS).
url: "https://www.bztmon.com", url: "https://www.bztmon.com",
+1 -1
View File
@@ -50,7 +50,7 @@ export const skills: SkillGroup[] = [
"vSphere / VMware ESXi", "vSphere / VMware ESXi",
"Proxmox", "Proxmox",
"containerd", "containerd",
"ArgoCD / GitOps", "Argo CD / GitOps",
"Helm & Kustomize", "Helm & Kustomize",
], ],
}, },
+2 -2
View File
@@ -10,11 +10,11 @@ const projects = (await getCollection("projects")).sort(
--- ---
<Layout title="Projects" path="/projects" description="Selected platform & infrastructure projects by Jonathon Wright."> <Layout title="Projects" path="/projects" description="Selected platform & infrastructure projects by Jonathon Wright.">
<Section id="all-projects" eyebrow="Projects" index="*" title="Some bytes I'm proud of - a starting line, not a trophy shelf."> <Section id="all-projects" eyebrow="Projects" index="*" title="Systems I've built and run.">
<p class="lead projects__intro" data-reveal> <p class="lead projects__intro" data-reveal>
Edge Kubernetes, GPU inference, self-hosted AI, productionised network automation, and 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 infrastructure rigour that ties it together - each with the problem, the design, and
the outcome. More always cooking. the outcome.
</p> </p>
<div class="grid"> <div class="grid">
{projects.map((entry, i) => <ProjectCard entry={entry} index={i + 1} />)} {projects.map((entry, i) => <ProjectCard entry={entry} index={i + 1} />)}