Building the dashboard is how I found the bugs
A hand-rolled log anomaly watcher went live, then giving it a status page exposed two flaws that alert-only monitoring structurally couldn't reveal.
Backfilled entry — this day predates the pipeline that normally writes these.
A crawl-anomaly detector, hand-rolled on purpose
Most of the day went to an always-on service that watches the web server’s access log and flags things worth knowing about: error spikes on paths that were previously clean, sudden drops in search engine crawl rate, new AI crawlers showing up, single-IP bursts that don’t match the existing intrusion rules, and failures on high-value canonical URLs. Instead of fixed thresholds it learns a per-hour-of-day baseline, because “200 requests an hour” means different things at 3am and 3pm.
There were easier options — off-the-shelf log analyzers, a full metrics stack — and we deliberately passed on them. The anomaly logic itself is the technical-SEO skill being practiced here: reasoning about crawl budget, bot classification, and canonical URL health from raw logs. Installing someone else’s dashboard would have skipped the part that’s actually worth learning.
Before it went live we tested every detection path with synthetic log lines rather than waiting for real traffic to happen by, including two negative controls to confirm it stays quiet when it should. That step is easy to skip and it’s the only reason the next section exists.
Then the dashboard found two bugs
The watcher was alert-only — a database and phone notifications, nothing at a URL. We gave it a status page: a dependency-free status server showing health, uptime, lines processed, a seven-day crawl chart with a plain-table fallback, and recent alert history.
Building it exposed two real flaws that were invisible while the system was alert-only:
The heartbeat only ticked when traffic arrived. It was being updated inside the loop that processes new log lines — so on a quiet day a perfectly healthy watcher would report itself as stalled. A monitor whose health signal depends on the thing it’s monitoring is silently useless exactly when the site is quiet. Now it ticks on a wall clock too, which makes “the watcher is dead” and “the site is just quiet” distinguishable states.
There was no alert history at all. Alerts only stored a per-rule cooldown timestamp — enough to suppress duplicates, nothing you could actually look at. Added a proper alert log, migrated in place without a rebuild.
Neither bug would have surfaced from the alerts themselves. Making state visible is what found them, and that generalizes well beyond a homelab: if a system can only tell you when it thinks something is wrong, you have no way to distinguish a healthy system from a broken one that’s gone quiet.
When your upstream gets archived
Added a web file manager for the storage drive. The obvious image announced on startup that it’s being archived in a month with ten unfixed security advisories, four of them high severity, including permission-scope bypasses and path traversal. For a component whose entire job is scoped file access, that’s disqualifying — so it got swapped for an actively maintained community fork created specifically because the original was archived. Access was scoped to the media drive only, deliberately keeping the home directory and its configs out of reach.
The fork is a near drop-in but not identical, and three migration snags were worth writing down: a bind mount shadowed the image’s built-in default config, first-run setup is interactive rather than auto-generating credentials, and the login API takes credentials in a query parameter and a custom header rather than a JSON body — which was the real cause of a confusing authentication failure while verifying it worked.
Elsewhere
Work also continued on reading an encrypted personal notes vault headlessly, and on performance root-causing for a client site. The client work stays abstract here by policy.