Files
serverAppConfigs/serverAppConfigs-docs/Dankserver/ARR-Stack.md
T

11 KiB


type: reference title: *arr Stack description: The *arr automation stack (Prowlarr, Sonarr, Radarr, Lidarr, Readarr, qBittorrent) — FrankBook-Pro only, every container on the gluetun WireGuard netns (all egress VPN): topology, wiring, auth posture, and gotchas. tags: [arr-stack, prowlarr, sonarr, radarr, lidarr, readarr, qbittorrent, frankbook-pro, dankserver] timestamp: 2026-09-04T00:00:00Z

*arr Stack

The *arr suite is a collection of automated media management tools for Usenet and BitTorrent. It runs in exactly one place: FrankBook-Pro — the Mac mini that fronts this household's downloads — as quadlet units inside its podman machine, with every container (the five arrs AND qBittorrent) sharing the Gluetun WireGuard tunnel's network namespace so all egress exits through the VPN. A short-lived Dankserver compose copy was removed on 2026-09-04 (see below). The stack provides automatic downloading and library management that Copyparty alone does not cover: quality profile enforcement, series/movie discovery, metadata enrichment, and automated file organization.

The core chain is: Prowlarr (indexer API aggregator) → Sonarr/Radarr/Lidarr/Readarr (media-specific managers) → qBittorrent (download client, sharing the Gluetun WireGuard tunnel netns) → Jellyfin (media server scan → auto-refresh).

Service Overview

Service Role Image Port Min RAM
Prowlarr Indexer management lscr.io/linuxserver/prowlarr:latest 9696 256 MB
Radarr Movie management lscr.io/linuxserver/radarr:latest 7878 512 MB
Sonarr TV show management lscr.io/linuxserver/sonarr:latest 8989 512 MB
Lidarr Music management lscr.io/linuxserver/lidarr:latest 8686 256 MB
Readarr Ebook/audiobook management lscr.io/linuxserver/readarr:amd64-1.0.980 (pinned — LSIO ships no untagged latest for readarr) 8787 256 MB
qBittorrent Torrent download client lscr.io/linuxserver/qbittorrent:4.6.7 (pinned; see qBittorrent) 8080 (published by the gluetun unit) 512 MB

Jackett and Whisparr were evaluated as optional extras but never deployed (no unit, no vhost, no live container).

Deployment shape (current)

Each service is a quadlet .container unit in this repo under frankbook-pro/<svc>/quadlet/, installed inside the podman machine at /home/core/.config/containers/systemd/<svc>.service by the frankbook-pro handler in scripts/deploy-remote.sh. Every unit runs with PUID=0 PGID=0 TZ=America/Los_Angeles UMASK_SET=022, mounts its config root at /Volumes/COMPLETE/<svc>-data:/config, publishes its API port on the host, and runs Restart=always with TimeoutStartSec=900.

qBittorrent is special: it shares the Gluetun container's network namespace (Network=gluetun.container) so torrent egress never touches the residential IP. A netns-sharer publishes no ports of its own — qB's WebUI (8080) and BitTorrent port (6881 TCP+UDP) are published on the gluetun unit instead.

Dankserver compose copy (removed 2026-09-04)

A copy of the stack lived in dankserver/compose.yaml (/opt/<svc>/config data roots) from 2026-08-20 to 2026-09-04 and never ran healthy: the config dirs were created root-owned (root:root 755), so every app died at logger init (UnauthorizedAccessException: Access to the path '/config/Sentry' is denied) before binding any port — which read as dead ports, not a boot crash, until podman logs <svc> was checked. After a one-day revival it was removed by operator decision: *FrankBook-Pro is the sole arr deployment. Config roots moved to ~/.AGENTTRASH/20260904-dankserver-arr-teardown/ on Dankserver (recoverable until purged).

Rootless identity model on Dankserver (verified, keep for reference)

  • On Dankserver's podman 5.7.0 rootless, a container's default user namespace maps container root (PUID=0) → host uid 1000 (dank) and container uid N → host 100000 + N - 1. Verified via /proc/<pid>/uid_map (0 1000 1 / 1 100000 65536) across plex, tautulli, jellyfin, calibre-web, copyparty, romm. This is why PUID=0 services write as dank and why data roots are owned 1000:1000 on that host.
  • Never add userns_mode: host to a podman-compose service. podman-compose 1.5.0 runs every compose service inside a pod, and podman rejects the combination (--userns and --pod cannot be set together) — the stack then fails to recreate with no recourse. The default mapping already gives the fleet identity; PUID=0 + 1000:1000-owned data roots is the whole recipe. (The compose file briefly carried userns_mode: host on all six arr services on 2026-09-04 — reverted in e454159, same day.)
  • qBittorrent's first boot (PUID=0, empty config) prints a one-time WebUI admin password in podman logs qbittorrent — persist it via the PBKDF2 recipe in qBittorrent before restarting the container.

Access / auth posture

No *arr app sits behind the Anubis guard chain — the stack is exempt by construction (its own auth), with an exemption comment in the Caddyfile per vhost. Six TLS vhosts (<svc>.franksdank.cloud) terminate on Caddy on Dankclaw and route to FrankBook-Pro's published ports (192.168.0.112:<port>, all exposed by the gluetun unit since the netns consolidation); the same ports are reachable directly on the LAN.

Auth-config history (verified live 2026-08-29): all five arrs shipped with AuthenticationMethod=None plus AuthenticationRequired=Enabled. On the v1 (.NET) apps that combo is self-contradictory — UI Save is permanently disabled and any settings save throws DryIoc.ScopedItemException inside AuthorizationMiddleware (500 sprawl on every page after restart). The valid representation of "no auth" in v1 is absence of the <AuthenticationRequired> key, not the literal string Disabled — readarr's config (key absent) booted clean while its three siblings (key=Disabled) 500-sprawled, which is how it was caught. Fix applied live: key deleted on sonarr/radarr/lidarr/prowlarr, containers restarted, all five verified serving. Posture = zero-auth arrs on LAN and WAN; the Anubis wall guards homepage/search/hermes/prometheus/romm/files but not the arrs — first-account-wins is the current race condition if that ever flips to External auth (needs an IdP first; auth method choice remains an operator decision).

Chain wiring (verified live 2026-08-30)

The download chain is wired and test-first verified: indexers → Prowlarr → per-arr qBittorrent clients (category = app name) → import.

  • qB webUI auth is ON (admin + generated password; verify before writing it into any config — it was generated at first boot, not a default).
  • Per-arr QBittorrent download clients exist on sonarr/radarr/lidarr (enabled) and readarr (parked: its qB proxy fails auth live even with correct creds — known defect of the abandoned upstream; retest needed after the 2026-09-04 netns move). All four clients were re-pointed to loopback (127.0.0.1:8080, same netns) on 2026-09-04 when the arrs joined gluetun's netns — the old 10.88.0.1:8080 gateway hack routes into the VPN tunnel (AllowedIPs=0.0.0.0/0) and is dead from inside the netns. Verified live: podman exec sonarr curl reaches qB with 200, and a real qB login from inside the netns returns 200.
  • Prowlarr runs v2.5.2 where apps are first-class rows (Applications table): four Application rows (sonarr/radarr/lidarr/readarr) carried each arr's real baseUrl+apiKey with SyncLevel=FullSync as of 2026-08-30. Status 2026-09-04: the table is EMPTY again (live API check — 8 rows, all Cardigann/Torznab, zero Application rows; lost with the dankserver config migration). Prowlarr sync is broken until the four rows are rebuilt with baseUrl 127.0.0.1:<arr port> (same netns).
  • Indexers are test-before-enable: EZTV and 1337x stay disabled with reason "blocked by CloudFlare Protection" (this host's egress ASN was CF-blocked at those trackers). 2026-09-04: arr egress moved onto the ProtonVPN tunnel — the parked reason may no longer hold; retest before enabling. Nyaa.si, sukebei, RuTor, Byrutor, YTS passed live tests and are enabled. A failed test = parked with reason, never enabled blind.
  • Prowlarr v2 sync model: indexers stay in Prowlarr; arrs search through it and hand off to their own qB download client (category routing). Mappings materialize at grab time — zero mappings right after wiring is expected, not a fault.

Disk layout (current)

/Volumes/COMPLETE            <- completed-media volume (Mac volume)
├── sonarr-data:/config      <- per-app config roots, one per unit
├── radarr-data:/config
├── lidarr-data:/config
├── readarr-data:/config
├── qbittorrent-data:/config
└── <movie/tv/music/books roots>   <- arr import roots live here
/Volumes/INCOMPLETE          <- qB in-flight downloads (same path inside qB)

qB mounts both volumes at their identical host paths (Volume=/Volumes/COMPLETE:/Volumes/COMPLETE, Volume=/Volumes/INCOMPLETE:/Volumes/INCOMPLETE), so a category's download path is the same string on every container that touches it — no remapping between download and import.

Gotchas

  • Timezone: every unit runs TZ=America/Los_Angeles; Sonarr's scheduler is timezone-sensitive — keep them uniform.
  • File permissions: UMASK_SET=022 on qBittorrent; arr apps run PUID=0 so they can read/move qB downloads.
  • Network conflicts: *arr apps use many ports. On the machine VM, only the API ports (8989/7878/8686/8787/9696) and gluetun's published 8080 + 6881 are host-published; netns-sharers (qB) publish none of their own.
  • Prowlarr API key rotation: If rotated, update it in every arr app (Applications table rows carry the real keys).
  • qBittorrent WebUI password: generated at first boot, not a default — persist it via the PBKDF2 conf recipe in qBittorrent before any restart.
  • Readarr stability: abandoned upstream (retired May 2026); its qB download client is parked pending a successor image. Do not rely on it for mission-critical collections.

Best Practices

  1. Use tags to route content to specific download clients or categories.
  2. Enable the Blocklist in arr apps to prevent re-downloading failed torrents.
  3. Set download limits in qBittorrent to avoid bandwidth saturation.
  4. Back up arr configs regularly (they are small SQLite DBs + JSON) — each lives at /Volumes/COMPLETE/<svc>-data/config.
  5. Monitor Prowlarr indexer health — broken indexers hide new content.
  6. Start with default quality profiles. Add custom formats gradually.

See Also