Media automation
The *arr apps (apps.radarr, apps.sonarr, apps.prowlarr, apps.bazarr) follow one rule that shapes
the whole design: the framework wires the plumbing, and never configures a source.
The boundary
A media-automation stack has two halves. One is generic infrastructure: ingress, forward-auth, an API key kept out of the store, notifications, a backup of the library list, and the connections between the tools (root folders, download clients). The other is acquisition: which indexers and trackers to search, which release qualities to prefer, which categories to file under.
selfhost-nix owns the first half and ships nothing of the second. The words indexer, tracker, quality profile, custom format appear nowhere in it. Its plumbing is inert on its own: a download client with no indexers behind it fetches nothing. So what and where you acquire is entirely yours, kept in your own (typically private) config, never in the framework.
That makes the apps neutral, general media tooling with legitimate use, and keeps every acquisition decision and its consequences with the operator.
What the app wires
apps.radarr / apps.sonarr register the service (admin-group access, with both the route and
forward-auth following the active provider), generate the API key out of the store and set the app to trust
the forward-auth identity, add a library-list backup hook, and run an idempotent reconcile that applies
only what you declare:
What it reconciles — root folders, download clients, an optional delay profile — is in the options reference. Every one defaults to empty, so enabling an app configures nothing you did not ask for. The framework ships no acquisition config and assumes no protocol: you name a download client’s implementation rather than having torrent, or Transmission, presumed for you.
Because the app is set to trust the identity header, it serves no login of its own, so it is routed only once a forward-auth provider is active. Without one it stays on localhost rather than going up unguarded.
apps.prowlarr is wiring-only. An indexer manager talks to APIs, not files, so it has no root folders or
download clients. Its indexer list and app-sync are acquisition and live in your config, reading the
apps’ apiKeyFile.
apps.bazarr fetches subtitles for what Radarr/Sonarr already track. The framework seeds the API key and
the localhost bind before Bazarr starts (it rewrites its own config.yaml, so that file can never be a
store symlink), then reconciles the sonarr/radarr links and languageProfiles through Bazarr’s
settings API. Which providers to search, and with whose credentials, is acquisition — it arrives via
the freeform settings seam, with credentials in secretSettings (read from a file at reconcile time,
never through the store). No provider is named anywhere in the framework.
languageProfiles defaults to empty, and Bazarr with no profile downloads nothing — so wanting subtitles
at all is an explicit choice you make, not one the framework makes for you.
Failure visibility
A media stack fails quietly: a dead root folder or an unreachable client stops imports, and because
nothing is imported, nothing is ever removed from the download client. The apps therefore wire two signals
by default — the *arr ntfy connection carries onHealthIssue / onManualInteractionRequired, and
exportarr exposes *_system_health_issues and *_queue_total with alert rules on both. Neither is
optional, because the failure mode they cover is invisible until you notice missing media.
Note that the generated API keys are 32 characters rather than the framework’s usual 64: exportarr rejects
anything outside ^[a-zA-Z0-9]{20,32}$.
What stays yours
- Indexers / trackers: Prowlarr, from your private config.
- Quality profiles / custom formats: taste, e.g. a recyclarr unit syncing TRaSH guides. The framework neither bundles nor schedules it (a network-fetching opinion is not plumbing).
- Download-client ordering:
configureAfter = [ "transmission.service" ](or your usenet client). - Where the data lives: root-folder paths and the storage mount, as with any service.