Real work on my own gear, written up honestly — including what broke. Each card links to its full write-up.
Projects
The projects behind the posts — networking, security, game modding, and tooling.
An approval-gated council of three agents
Claude, Codex and Kimi review one request independently, then see each other's findings, then a free model merges them — and nothing touches the lab without an approval bound to a hash of the exact plan. Includes the time the independent reviewer told me not to build the feature I wanted, and why I listened.
A vulnerability scan from Codex, a container upgrade from Claude Code
2042 fixable findings, 1751 of them in container images — and ten images floating on `:latest` with no recorded version to roll back to. Ten waves behind a hard validation gate took it to 1497. Includes the two numbers that went the wrong way, and why an unchanged image gaining 9 findings matters more than the headline.
An assurance plane for the agent running my homelab
Claude Code has real access to the lab, so I had Codex build the auditor for it — root-managed hooks, a hash-chained evidence ledger, a sanitised SIEM feed, and AI reviewers that can escalate a finding but never approve an action. Observe-only until it earns enforcement.
A read-only dashboard for my Obsidian vault
A localhost note dashboard built on Node built-ins alone — no npm install, no venv, read-only by construction. Two bugs only the browser could show: wikilinks resolved against HTML-escaped text so every note with an apostrophe broke, and a `display:grid` rule that quietly beat `element.hidden` and made the whole thing look frozen.
Patching a homelab — apt, and everything apt can't see
Automated Debian patching that reported success while patching nothing, plus a second n8n workflow for the half apt never touches: container image releases. The monitor was the thing that needed fixing first.
One DNS filter, three clients that bypass it differently
AdGuard Home took ten minutes; making a Windows PC, an iPhone and the lab itself actually resolve through it took the rest. Every layer that quietly reroutes DNS somewhere else, found one at a time.
A self-hosted multi-LLM gateway
One endpoint, many models — LiteLLM + Open WebUI + OpenRouter dropped into the Caddy/Authentik/Langfuse stack, with Claude Code still the main driver. Humans get SSO, machines get keys; plus a reasoning-model empty-reply trap, a Caddy single-file bind-mount inode gotcha, and why the free tier has no flagship models.
Implementing Langfuse to monitor Claude Code
Self-hosting Langfuse and wiring Claude Code's beta OpenTelemetry export into it — a host-port clash with Grafana, the OTLP/Basic-auth handshake, and being honest about what a trace dashboard does and doesn't tell you (it's not a hallucination detector).
Two VPNs, one phone — consolidating remote access
Merging WireGuard and Tailscale into one tunnel that reaches the whole lab — a subnet router, the 100.x-vs-10.x and Linux subnet-route gotchas, split DNS, and an ACL default-deny that locks out my own unauthenticated metrics DB. Plus why I kept WireGuard as a break-glass path.
Backup watchdog in n8n
My first n8n workflow — a nightly check that my backups actually ran, which caught a real five-day outage while I was building it. Plus three ways the tooling lied with a plausible wrong answer: a string that looked like a number, a zero that was a permission error, and a GET where a POST should be.
A front door for the home lab
Homepage in one Docker container — no published ports, reached through the reverse proxy on a wildcard cert. The status dots turned out to matter more than the links: a green 'running' dot next to a dead link, and the container-vs-host port lesson that fixed it.
OpenVAS vulnerability scanning
Greenbone Community Edition (OpenVAS) in Docker on my Debian lab VM — install, an nginx port clash with Caddy, a self-inflicted login lockout, a scanner race that made a working scan look dead, and the first self-scan's real findings.
Command Quest — a Linux CLI practice game
A single-file Python terminal game that drills every command from my Linux cheat sheet — scenarios, feedback, XP and levels — and validates answers without ever running them, with a self-test over all 105 built-in solutions.
Lynis scored my hand-hardened VM 68/100
An independent auditor grading a Debian VM I had already hardened by hand. What I fixed, which findings I deliberately ignored and why, and the score afterwards: 80.
Homelab SIEM — Loki + Promtail
Firewall and server logs centralised into Loki and searched with LogQL in Grafana — through a journal dead end, an RFC 3164/5424 mismatch, and an rsyslog translator in the middle.
Claudian — Claude inside Obsidian
An agentic Claude Code sidebar embedded in my study vault — runbook frontmatter, semantic callouts and cross-links applied vault-wide, with every code fence preserved byte-for-byte.
A backup you haven't restored is a hypothesis
The identity provider had quietly become load-bearing with no backup behind it. Building one — encrypted, scheduled, and actually restore-tested — taught me more about Postgres and SQLite internals than the identity provider ever did.
Alerting — the lab pages my phone
Alertmanager + ntfy on top of the existing Prometheus stack — a packaging conflict dodged with Docker, a silent YAML trap caught by querying state, and a firewall that correctly said no.
pfSense containment lab
A pfSense VM firewalling the Debian VM where my Claude agents run — allow internet-out, block every private range. Plus a full-outage debug.
Home network hardening
Baseline hardening and guest segmentation on an ISP-issued Arcadyan HWG2025 — WPA3, WPS off, two isolated zones, and the ISP-DNS workaround.
Virtual VLAN segmentation
Three firewall-isolated zones — Trusted, IoT, Guest — built entirely in VMs with no managed switch, and proven by one client changing a tag.
WireGuard through a nested firewall
Raw WireGuard on pfSense, reached from my phone on mobile data through the lab's containment layer — plus a four-layer silent-failure debug.
Self-hosted Vaultwarden vault
A password vault with a genuinely trusted wildcard cert via DNS-01 and zero inbound ports — reached over WireGuard, invisible to the internet.
Identity provider — Authentik SSO
A self-hosted IdP with MFA enforced centrally, wired into Grafana over OIDC — so an app that has never heard of MFA suddenly requires a second factor and grants admin by group.
The firewall joins the IdP
pfSense authenticating against Authentik over LDAP — MFA and group-based admin on a device with no browser — plus proving the RADIUS path was a genuine upstream bug, not my config.
Hardening the Debian lab VM
An honest security audit of my own automation VM and the fixes — auto-updates, an nftables default-deny, Tailscale-only SSH, systemd sandboxing.
MCP hub over Tailscale
A custom MCP server on the lab VM a roaming laptop's Claude Code pushes notes to over a Tailscale mesh — no inbound ports, a narrow tool surface.
Prometheus + Grafana stack
The dashboard layer under the glance — Prometheus scraping the VM and pfSense, Grafana drawing live graphs, all on one localhost-bound VM.
LaMetric TIME on an isolated net
Live home-lab stats on a LaMetric display, bridged over MQTT through a cloud broker so the publisher and display never touch the LAN directly.
LaMetric — WAN throughput over SNMP
A follow-up polling pfSense over SNMP for real WAN in/out rates and adding them to the display — reading the gateway without breaking isolation.
BiggerBang — SPT mod port
An abandoned Single Player Tarkov trader mod, ported from the 3.x TypeScript API to a compiled C#/.NET DLL for SPT 4.0 — five bugs fixed.
FoxWeaponSound — port via Fable 5
A 2022 JavaScript weapon-sound mod rebuilt as a .NET 9 DLL for SPT 4.0.13, driven end-to-end through Claude Fable 5 — surfacing five long-shipped bugs.
Spec Grabber — system info tool
A native desktop GUI (Python, customtkinter, psutil) that collects system info into a styled HTML report — hardened, packaged for Linux and Windows.
Mini projects on the laptop
A running log of smaller Claude Code sessions on a Debian laptop — env setup, OneDrive sync, and an Obsidian vault tidy-up wired to the Claude API.
This blog
Hugo + PaperMod with a Claude Code publishing pipeline — raw notes in, finished post out, filed, committed and deployed by one command.
Moving to Cloudflare Pages
Migrated off GitHub Pages to Cloudflare Pages for real HTTP security headers — a hash-locked CSP, HSTS, and the rest a meta-tag CSP can't deliver.
My own domain — billsblog.dev
Registered a custom .dev domain (an HTTPS-forced TLD) and moved the site onto it with the whole hardened header set intact — plus the privacy trade-offs.