Back in 2024 I wrote about swapping my aging HP MicroServer for a tiny N100 box. Two years on, that same little PC — its only hostname is, unimaginatively, SERVER — is still doing the job. What changed is everything running on top of it, and how it's kept.
This post is the tour: what's actually installed, why it's laid out the way it is, and the handful of mistakes that shape decided the layout, not a diagram drawn up front.
Everything is a Windows service, on purpose
No Docker here. Every app runs as a native Windows service via NSSM, and every service gets its own top-level folder on the box, named for what it does rather than the product behind it — cinema, not jellyfin; proxy, not caddy. A couple of names stuck as-is because the product name already read as intent (memories, youtube-exporter).
Inside each folder, the same five subfolders show up everywhere, and the same .gitignore rules apply to all of them:
| Folder | Holds | Tracked in git? |
|---|---|---|
app\ |
The vendor binary itself | No |
config\ |
Settings, where the app actually lets you point at one | Yes |
data\ |
Databases, caches, indexes | No |
secrets\ |
Tokens, keys, certs | No (except a .gitkeep) |
contents\ |
The actual media a service serves | No |
The whole thing is one git repo. That's the actual point of the exercise: a config change to Caddy or Prometheus is a diff I can read before applying it, and a git log I can blame six months later when something behaves differently than I remember setting it up. secrets\ exists as its own folder, separate from config\, specifically so a private key can be excluded by folder instead of by filename pattern — a wildcard TLS cert has no business anywhere near git history, however small the repo.
It sounds obvious written down. It wasn't obvious in practice — three different apps fought the split.
The gotcha that ate a weekend
Kavita, Jellyfin, and Grafana are all built to resolve their own web assets and config relative to the process's working directory, not the folder the executable happens to sit in. Split them into tidy app\ / config\ folders, start the service from the service root, and Kavita 500s looking for wwwroot\index.html that's sitting one level up. Jellyfin does the same thing.
There's no flag to fix it for either — no --content-root, nothing. So both of them, plus Grafana and the small internal youtube-exporter tool, just live flat in app\, config and all. Which means, bluntly: if app\ is gitignored — and it is, everywhere, because that's also where multi-hundred-megabyte binaries live — none of those four apps' actual settings are tracked by git at all. Every other service here (Caddy, Blocky, Prometheus, Navidrome, the photo gallery) takes an explicit --config/-c flag with a real path, so this genuinely is an app-by-app thing, not a pattern I imposed badly. Grafana in particular has zero backup story right now beyond the live SQLite file — worth fixing, not fixed yet.
The network side: three ways online, one way in
The internet connection here is three separate paths — a primary router, a DSL line as backup, and a 4G router as the backup for the backup — so a single outage doesn't take the house offline. Everything behind that funnels through one entry point: Caddy as the reverse proxy, terminating a single wildcard certificate for everything under *.home.sarmis.gr.
The trick that makes the subdomain scheme work without a single public DNS record: Blocky runs as the LAN's DNS resolver and holds one custom mapping — home.sarmis.gr → the server's LAN IP. Every device on the network already points at it for DNS, so cinema.home.sarmis.gr just resolves locally; nothing about it is reachable, or even visible, from outside the house. The wildcard cert itself still needs a real public DNS-01 challenge to issue, which means logging into the registrar and hand-creating a TXT record every renewal — the one manual step nothing here has automated yet.
What's actually running
Grouped the way the dashboard groups them:
Media — Jellyfin (cinema) for movies and shows, Kavita (bookshelf) for e-books and comics, Navidrome (music) for streaming my own music library, and Home Gallery (memories) for photos and video — 53GB of it and growing.
Downloads — qBittorrent (torrents), running as LocalSystem now instead of a dedicated user account, once --profile gave it an explicit place to keep its state.
Backup & sync — Syncthing is the one app here that predates the whole reorganization and hasn't been folded into it; it just runs, the old-fashioned way, outside the standardized folder convention everything else follows. It's also doing the actual work everything else here assumes exists: every personal PC and laptop syncs to both SERVER and to vault — the retired HP MicroServer from the 2024 post, still earning its keep as a second copy in a different room. Phones sync their camera roll to their owner's PC rather than straight to a server, so a phone that's off the network for a week doesn't hold up anything else. And one shared folder, syncbox, mirrors to every device in the house — the closest thing here to a shared clipboard.
Around the house — Homebox (inventory) for tracking what I actually own and where it's stored, and Gotify (notifications) as the push-notification server every script and backup job reports back to.
Watching the watchers — Prometheus and Grafana (dashboards) scrape six machines across the house, plus a small internal exporter that turns this channel's YouTube stats into a time series I can chart alongside everything else. Homer sits in front of all of it as a plain, static landing page — one tile per service, grouped by category, no login.
One honest experiment sitting off to the side of the "official" inventory: Hermes, a self-improving AI agent from Nous Research. It hasn't earned the standard folder layout yet — still just a raw clone — which is exactly why it's still labeled an experiment rather than a service.
Upgrading without downtime, or much drama
Gotify needed a major version bump this year — 2.6.3 to 3.1.0, which also dropped its old YAML config for environment variables entirely. Rather than upgrade in place and hope, the new version got built and tested side-by-side first: a second folder, a second service, a second temporary subdomain, running on a spare port next to the live one. Once it was confirmed working, the old service came down and the new folder got renamed into its place. Every service rename here follows the same rule — the new setup only ever touches its own name, and the old one keeps running, untouched, until the replacement has actually proven itself.
What's next
A few things are still open, logged rather than forgotten:
- Prometheus retention is 90 days globally, but the YouTube exporter alone could use a year of history — worth a second, dedicated Prometheus instance for just that one low-volume job rather than dragging retention up for everything, including the
windows-exportermetrics that are already the dominant cost. - Grafana still needs an actual backup story instead of one live SQLite file.
- Jellyfin 12 might be able to swallow music, books, and photos into one server instead of three — worth investigating before committing to anything, since all three of the apps it would replace already work.
None of this was planned upfront. It's the result of moving one service at a time into the same shape, hitting the same three or four gotchas more than once, and writing the answer down so the next migration doesn't rediscover it.