~/articles/my-homepage-had-a-number-on-it-that-nobody-was-checking
My homepage had a number on it that nobody was checking
- date
- read
- 4 min
- words
- 781
The number
For a while my homepage stated how many automations run on my home server. I typed that number by hand. It was true the day I wrote it. Then I kept adding timers and watchers, and the number sat there, unchanged, a little less true every week. Nobody would have caught it. Not a visitor, and not me. A stale number on a portfolio site is a small thing, but it is exactly the kind of small thing that turns a site about careful engineering into a site that fibs.
This week I fixed it, and the fix turned into a longer night than I planned.
Push the truth instead of typing it
The server already knows how many timers it runs. So the first piece was a small job that fires every 30 minutes, reads the live state from systemd, and pushes a count to the site. The homepage now renders that count with an "as of" stamp. If the snapshot is missing, stale, or malformed, the page says so instead of showing a stale figure. An honest "offline" beats a confident guess.
The part I spent the most care on is what leaves the server. Internal unit names say more than I want public: what they watch, who they serve, which private repos they touch. So the push goes through a fail-closed allowlist. Each internal name maps to a generic public label, and anything without a mapping is dropped, not forwarded. After that, a 17-pattern regex backstop scans the whole serialized payload, and a single hit refuses the entire push. I would rather the site show nothing than show one internal name.
Content that cannot drift either
The number was the obvious drift. The quieter one is prose. Three pages on the site (the homepage, an architecture page, and a "uses" page) describe how my system works. That description also lives in a master architecture document in my notes vault. The two drift apart the moment I update one and forget the other.
Back in June I built a sync for this and never deployed it. It hashes the relevant sections of the architecture doc plus the diagram sources, so it is a free no-op when nothing changed. When something did change, a worker proposes a minimal edit to just the affected page, and every changed string passes a leak and grounding gate that reuses machinery from my job-application pipeline. The proposal lands in my approval queue. If I approve, it opens a pull request from a throwaway clone. It never touches the live checkout, never pushes to main, and fails closed if the patch does not apply. A human, meaning me, always taps merge. That is the property I care about most. Automation can propose. It does not get to publish.
It runs once a week on Sunday, with 28 tests behind it. Landing it this week was mostly the unglamorous part: copy the unit onto the server, enable it, watch for the first tick.
The truth purge
Once I was in the site repo I kept going. I rewrote the ground rules my AI assistant follows in that repo so any number or claim has to trace back to a source. I reframed one page to say only what it can back up. I deleted 4,410 lines of dead code. I refreshed the public diagrams so they show the orchestration layer as it exists now, with the new telemetry lane drawn in as a dashed line. And I put the homepage on incremental regeneration every 1,800 seconds, so serving a live count costs almost nothing.
What I left open
Closing the documentation loop is the part I usually skip, so I did it before sleeping. The timer table in my architecture doc gained two rows, and the live count came to 72, up from 68 in late August. Checking that count against what was written down turned up two timers whose documentation did not match reality: one that never had a row, and one described as staged that is actually running. I flagged both and fixed neither. They belong to a different part of the system and a different night. Writing down what you found and did not fix beats pretending the list is clean.
The lesson is one I keep relearning. A system that describes itself will drift unless the description is generated from the system. The number on my homepage is now a reading from the server, not a memory of it. The pages are gated against the document that defines them. And every path from machine to public still ends with a human hand on the merge button.