Today started as research into Maintainerr, a tool that would trim old images and distro archives out of the library on a schedule instead of by hand. It turned into something else within the first twenty minutes: a setting both Radarr and Sonarr have had switched on the whole time turned out to have never once done what it says. The rest of the day was a design review for moving the monitoring stack off the main host, and the two things that review caught before anything moved matter more than the plan itself.
The through-line is the same across all of it: a thing reporting success and a thing actually working are not the same claim, and today was mostly spent finding the gap between them.
The hardlink that never linked anything
Both Radarr and Sonarr are set to copyUsingHardlinks: true. A hardlink means the library copy and the completed-download copy point at the same data on disk, so the file exists once, and deleting either reference doesn't touch the underlying bytes until both are gone. Checking the files directly instead of trusting the setting was what caught it: both copies came back with Links: 1. A real hardlink shows 2. These are two separate files.
The mount between the download directory and the library is sshfs over SFTP, and that protocol has no hardlink call to make. When the app asks for one and the filesystem underneath can't do it, the copy falls back to writing a full second file instead of failing. Nothing errors. The setting reads back as enabled because it is enabled; it has just never been able to do the thing it's enabled to do. The consequence is specific: deleting a library file reclaims only that file's space, the completed-download copy sits there untouched, and the measured duplication across the two libraries comes to 229 GB.
That also closed out the reason I'd opened Maintainerr research in the first place. A deletion tool pointed at a library with an active, silent duplication bug would have been cleaning up half the mess at best.
A version pinned on purpose
The rest of the Maintainerr work was picking a version rather than picking a default. The image went in pinned to ghcr.io/jorenn92/maintainerr:v3.24.0, not latest. The v3.0.0 release changed the tool's schema outright, and the v3.x line has a documented history of breaking changes against v2.x since. Pinning an exact tag against that history isn't caution for its own sake, it's the one deployment decision this stack doesn't already have on record.
Radarr, Sonarr, Tautulli and Bazarr all run on latest right now, each with a comment in the compose file that says PIN-ME. Nobody has acted on any of them. Deployment for Maintainerr got written up and left unexecuted for the day, but the version line in it is the one place this round of work is more careful than the stack it's joining.
Two ways a plan looked safe and wasn't
The monitoring-migration design covers moving nine containers off the main host onto a guest on the second node, and the review pass on that plan is where the day's other findings came from.
The first is an uptime monitor problem that has nothing to do with uptime. Roughly 66 home-automation entities are keyed to monitor IDs, not to names or URLs. Deleting a monitor and recreating it against the same target gets a new ID, and every entity pointed at the old one goes dark with no error on either side, because nothing about a home-automation integration failing quietly looks like a monitoring problem. The fix is procedural rather than technical: monitors get edited in place from here on, never deleted and rebuilt, no matter how much cleaner a fresh one would be.
The second is a Grafana problem that isn't in Grafana's own backup path. About 15 dashboards exist only inside that container's data volume. No export, nothing checked into version control anywhere. Losing that volume loses the dashboards permanently, and nothing in the standard container lifecycle protects against that by default; a routine docker volume rm during cleanup would have been indistinguishable from success until someone went looking for a dashboard that wasn't there.
Neither of these came from the plan itself. They came from reading the plan a second time specifically looking for what it assumed rather than what it stated.
A backup, and proof the check works
The one thing that did get executed today rather than designed was a backup of the three volumes behind the monitoring stack, ahead of any move. They're root-owned, so a plain copy wasn't an option without more access than made sense to grant for a one-off; they got tarred through a container that could read them and written to a third machine, neither the source nor the eventual destination. Prometheus came out at 2.85 GB, Grafana at 95 MB, Loki at 194 MB.
SHA256 on the source and the destination matched exactly, and the archive listings held what they were supposed to: ULID blocks, the Grafana database file, 22,139 Loki chunks. That much would normally be where this stops. Instead I corrupted a copy of one of the archives on purpose and reran the same check, to see whether it would actually catch a bad file or just report a matching hash because the process ran without an error. It failed, the way it should have. A verification step that has never been watched fail is not a verification step, it's an assumption wearing a checksum.
What the backup was for happens tomorrow, and none of that is here yet.