MB-404InfrastructureIn daily use · open-source edition in prep2026
Saga
A self-hosted control plane and request portal for my home media server. Browse and add titles in bulk, see download ETAs that are actually true, and give family and friends a portal to ask for things. It replaced the off-the-shelf request app in a day.
- J1
Guest portal
invite-only, no keys
- U1
Saga API
one image, two surfaces
- U2
Library + queue
media services
- U3
Insights
forecasts, upgrades
- U4
Agent hand-off
Problems → Claude Code
The request app I used could only take a request for a title you had already found. It couldn’t browse and add in bulk, and every download ETA it showed was fiction. Saga replaces it and becomes the one place to run the media server from.
What it does
- Discover: trending and filtered browsing, people, collections and curated lists, with a live badge on every poster: in the library, downloading with its real ETA, queued, requested or missing. Titles already owned are hidden by default.
- Bulk add: select a page of titles and add them as a background job. Rules choose the quality profile and folder, and the size and time are shown before you commit. One click fills the gaps in a film collection.
- Honest ETAs: throughput is measured every minute from the download client’s own byte counter, pauses included, and played forward over the queue in order. The maths is a pure, unit-tested function.
- Problems and the stack: stalled items can be retried or re-searched, and a Stack page shows each service’s running and available versions with update and rollback. Any problem can be handed to the homelab agent, which streams its steps back.
- Insights: a storage forecast, an upgrade finder, a library hygiene report, “because you watched”, and discovery in plain English.
- Request portal: invite-only, sign in with Plex or a magic link, per-guest quotas and trust levels, an approval queue, duplicate requests merged, kid profiles and push notifications. It imported every user and request from the old app.
Decisions
- One image, two surfaces. The internet-facing portal runs the same code with no admin routes registered, no database and no service keys, and a test proves the admin routes aren’t there. The browser never sees an API key.
- Safety rules live on the server. Saga never resumes a pause made by the post-processing guard, never forces priority past a pause, and every pause it makes has a timer, so it can’t fight the agent. Adds always go through the library services, so nothing is untracked.
- Narrow keys for status. Host status comes over SSH forced commands with fixed verbs, with one key for read-only status and another for operations.
- No native modules. Saga’s own data is small, so it uses Node’s built-in SQLite.
Gotchas it found
- Plex retired its friends-list endpoint mid-build. Checking the owner and the friends in one step meant that one failure locked the owner out of the portal. They are now checked separately, against the endpoints that still work.
- Free space over NFS lies. Each ZFS dataset reports its own usage but the pool’s shared free space, so the library looked empty. Saga merges mounts that share free space into one pool.
- React 19 runs whatever an effect returns as its cleanup. A one-line arrow function that returned a value crashed the portal and signed guests out. Effects now always use block bodies.
No screenshots: it runs on my own library.