Skip to content

Media stack: requests, storage, 4K, cleanup and re-encoding

This stack is here for learning: how the apps are wired, stored and run on Kubernetes. The only things downloaded with it are Linux ISOs and other openly licensed content.

The arr apps, Cleanuparr and Tdarr keep their settings in their own databases on their config PVCs, not in this repo. This page records the settings that matter. With them you can rebuild the apps after a lost volume.

Overview

App URL Namespace Role
Seerr https://seerr.bigd.no plex-media-stack Requests, with a separate "Request in 4K" button
Radarr https://radarr.local.bigd.no arr-stack 1080p movies in /data/movies
Radarr 4K https://radarr-4k.local.bigd.no arr-stack 4K movies in /data/movies-4k
Bazarr, Bazarr 4K https://bazarr.local.bigd.no, https://bazarr-4k.local.bigd.no arr-stack Subtitles for Sonarr and Radarr, and for Radarr 4K
Sonarr https://sonarr.local.bigd.no arr-stack TV in /data/series
Prowlarr https://prowlarr.local.bigd.no gluetun-vpn Indexers, sidecar in the VPN pod. Its DNS goes through the VPN, so it reaches the arr apps through hostAliases
qBittorrent https://qbittorrent.local.bigd.no gluetun-vpn Downloads, sidecar in the VPN pod
Tdarr https://tdarr.local.bigd.no arr-stack Re-encodes H.264 to HEVC
Plex http://10.3.10.103:32400/web plex-media-stack Playback, libraries below

A request goes from Seerr to Radarr, Radarr 4K or Sonarr. The arr app searches the Prowlarr indexers and sends the torrent to qBittorrent. When the download finishes, the arr app hardlinks the file into its root folder. Plex picks up the file. Tdarr later re-encodes it if it is H.264 and no longer seeding. All of these apps have tiles on the hub (hub.bigd.no, k8s/talos/apps/homepage/values.yaml).

Prowlarr, qBittorrent and Gluetun share one pod. All their traffic leaves through the PIA VPN. Prowlarr reaches FlareSolverr in arr-stack for indexers behind a browser challenge.

Request flow from Seerr through the arr apps and the VPN pod to the media share and Plex

Plex libraries: Movies (/movies), Movies 4K (/movies-4k), TV Shows (/tv), Courses, Youtube. Each is a read-only NFS mount of the matching folder on the share, declared in k8s/talos/apps/plex-media-stack/plex.yaml. A new library folder must exist on the share before you add it there. If it does not, Plex fails to start.

Everything is on one Synology share, /volume1/shared-data/media. Sonarr, Radarr, Radarr 4K, Bazarr, Unpackerr and Tdarr mount it at /data.

Which apps mount which folders of the media share, and the hardlink from torrents to the library

Path Contents
/data/series Sonarr root folder
/data/movies Radarr root folder (also the root for all Radarr collections)
/data/movies-4k Radarr 4K root folder
/data/torrents/download qBittorrent incomplete downloads
/data/torrents/completed qBittorrent completed torrents, still seeding
/data/transcode_cache/tdarr Tdarr work files

Library and torrents must stay on the same mount. Linux cannot hardlink across two mounts. With separate /tv and /torrents mounts the arr apps silently copy every import, and each seeding torrent costs a second full copy on the NAS.

qBittorrent is not part of this. It still mounts only /torrents (the torrents folder) and reports paths like /torrents/completed/.... Sonarr and Radarr translate those paths with a remote path mapping.

Check that an import was hardlinked: stat -c %h <library file> shows 2 while the torrent is still in qBittorrent.

Seeding

Almost every torrent is from a private tracker. If a torrent stops before the tracker's minimum, the tracker gives a hit-and-run strike. Every layer is set so that nothing stops or removes a torrent early:

Layer Setting
Prowlarr, per indexer (synced to Sonarr, Radarr, Radarr 4K) seed ratio and time set per indexer. Ratio limits are 5 or none, never a time limit
qBittorrent, global no ratio, seeding time or inactivity limit. Queueing off, so every torrent seeds
Sonarr "Remove Completed" on: removes a torrent only after qBittorrent stopped it at ratio 5
Radarr, Radarr 4K "Remove Completed" off
All arrs "Remove Failed" off
Cleanuparr "Delete Private" off, Download Cleaner off
Tdarr skips hardlinked files (library filter and flow), so a seeding file is never split
Moves inside /data renames on one share keep the inode, so the torrent keeps its data

qBittorrent stops a torrent at the ratio limit or at the time limit, whichever comes first. A ratio limit is only safe for a tracker whose hit-and-run rule is "ratio or time". Some trackers demand a minimum seed time at any ratio. For those, remove the ratio limit in Prowlarr.

Check: stoppedUP or missingFiles in qBittorrent's torrent list should be empty.

Sonarr and Radarr

  • Root folders: /data/series, /data/movies.
  • Media Management: "Use Hardlinks instead of Copy" on.
  • Download client qBittorrent: host qbittorrent.gluetun-vpn, port 8080, no SSL. Categories tv-sonarr and radarr.
  • Remote path mapping: host qbittorrent.gluetun-vpn, remote /torrents/, local /data/torrents/. The host must match the download client host exactly.
  • "Remove Failed Downloads" off in both. Almost every torrent is from a private tracker. A removal from qBittorrent risks a hit-and-run strike.
  • "Remove Completed Downloads": on in Sonarr, off in Radarr and Radarr 4K. The arr app removes a torrent only after qBittorrent has stopped it at its seeding limit.
  • qBittorrent categories: tv-sonarr, radarr, radarr-4k, plus manual ones. Each arr creates its own on the first connection test.
  • Seerr default folders: /data/series and /data/movies. 4K requests go to Radarr 4K (below).
  • General > Backups (also in Radarr 4K and Prowlarr): interval 1 day, retention 14 days, folder Backups. The nightly VolSync copy of /config carries these zips. A restore starts from the newest zip, not from the live database (docs/platform/backups/README.md).

Release filters

The goal is media that plays correctly, not the highest quality tier. Recyclarr (k8s/talos/apps/arr-stack/recyclarr.yaml) runs every night. It syncs TRaSH custom formats and scores into the HD - 720p/1080p profile in Sonarr and Radarr, and into the Any profile in Sonarr. Radarr 4K is not managed.

Score Custom formats Effect
-10000 LQ, LQ (Release Title), BR-DISK, Extras, Bad Dual Groups, Upscaled, 3D (Radarr only) The release is rejected
-2000 MULTi The release is grabbed only when nothing else exists. These releases often have a foreign first audio track
TRaSH default WEB Tier 01 to 03, WEB Scene, HD Bluray tiers Good groups win inside one quality
150 Season Pack (Sonarr) One source for a whole season wins, so the audio does not change between episodes

Minimum custom format score is -5000. Scores decide only new grabs. Upgrades stay off in these profiles, so a filter change replaces no file. Existing bad files need a manual search.

Do not add these rules:

  • A penalty on x265. Tdarr makes the library HEVC on purpose.
  • "Language: Not Original". Sonarr lists some Norwegian shows as English, and the rule would reject their Norwegian releases.
  • An upgrade key with a score cutoff in the Recyclarr config. Almost every file scores 0 today, so a large part of the library would download again.

Check a release: GET /api/v3/parse?title=<release name> on Sonarr or Radarr returns its custom formats and score.

Radarr 4K

https://radarr-4k.local.bigd.no is a second Radarr for films kept in 4K. A 4K film lives only here. The main Radarr does not track it, so there is no 1080p copy unless someone requests one in Seerr. Radarr tracks one file per movie, so 4K needs its own instance and root folder. Two instances that share /data/movies would delete each other's files on upgrade.

  • Root folder /data/movies-4k, owned by the Synology share user with mode 777.
  • Media Management and file naming copied from Radarr (hardlinks on).
  • Download client qBittorrent, same host, user and remote path mapping as Radarr, category radarr-4k, "Remove Completed" and "Remove Failed" off.
  • Quality profile 2160p: WEB 2160p and Bluray-2160p, no remux, cutoff WEB 2160p, upgrades on. Existing remuxes rank above the cutoff, so Radarr 4K keeps them and does not swap in a smaller WEB release. Custom format "DV (w/o HDR fallback)" from TRaSH (docs/json/radarr/cf/dv-wo-hdr-fallback.json) scored -10000. Those files play with purple and green colours on screens without Dolby Vision.
  • Indexers: Prowlarr syncs them. Radarr 4K is its own app there (https://radarr-4k.local.bigd.no, full sync, movie categories).
  • Seerr: Radarr server "Radarr 4K", hostname radarr-4k.local.bigd.no port 443 SSL, "4K Server" and "Default Server" on, profile 2160p, root /data/movies-4k.
  • Plex: Movies 4K library, shared only with users whose devices play 4K HDR. Then nobody else forces a 4K HDR transcode on the iGPU.
  • Tdarr: library Movies 4K with the same flow. Today every file is HEVC and is skipped. The library is there to catch a 2160p H.264 release. Such a release is SDR, so re-encoding loses no HDR.
  • Bazarr 4K: subtitles, see below.
  • Cleanuparr: its own Radarr instance, http://radarr-4k:7878.
  • Unpackerr: UN_RADARR_1_* in k8s/talos/apps/arr-stack/unpackerr.yaml.
  • Metrics: exportarr-radarr-4k, so the Radarr alerts in k8s/talos/infra/kube-prometheus-stack/homelab-alerts.yaml cover it too.
  • Hub tile with a widget. Its API key is Bitwarden secret homepage-radarr-4k-api. All of the above use that key in Git.

Moving an existing 1080p-library film to 4K only: move its folder from /data/movies to /data/movies-4k. This is a rename on the same share, so the hardlinks stay. Add the film in Radarr 4K with that path and profile 2160p, without a search. Then delete it from Radarr with "Delete files" off. Check first that its collection is not monitored in Radarr. If it is, Radarr adds the film back and downloads a 1080p copy.

Seerr and Tautulli

Both reach Plex at plex.plex-media-stack, port 32400, no SSL. Use the Service name, never the Plex pod IP or the *.plex.direct name that Plex offers during setup. That name encodes the pod IP. The pod IP changes on every Plex restart, and then scans, availability and activity silently time out.

Seerr (https://seerr.bigd.no):

  • Plex libraries enabled: Movies, Movies 4K, TV Shows, Courses, Youtube. A new Plex library shows up only after Settings > Plex > Sync Libraries. It stays disabled until you tick it.
  • Servers: Radarr (1080p, default, profile HD - 720p/1080p, /data/movies), Radarr 4K (4K, default, profile 2160p, /data/movies-4k), Sonarr (default, /data/series). There is no 4K Sonarr.
  • After you move films between libraries, run the "Media Availability Sync" job. It clears the old 1080p status, and the film can be requested in 1080p again.

Tautulli (https://tautulli.local.bigd.no): Settings > Plex Media Server, same address. If its websocket logs "401 Unauthorized", the stored Plex token was revoked. Either use "Fetch New Token" on the same page and sign in with the Plex account, or give it the server owner's token. That token is PlexOnlineToken in Plex's Preferences.xml. Set it with $.post('save_pms_token', {token: '<token>'}) in the browser console on Tautulli's settings page.

Bazarr and Bazarr 4K

Bazarr connects to one Sonarr and one Radarr. Radarr 4K therefore has its own instance, bazarr-4k, built from the same settings.

  • Both: the same subtitle providers (Settings > Providers). Language profile "English + Norwegian" (no, en) is the default for new titles. Embedded subtitles count. Upgrades for 7 days. They have no login page of their own and are reachable on the private gateway only.
  • Bazarr: Sonarr sonarr.arr-stack.svc.cluster.local:8989, Radarr radarr.arr-stack.svc.cluster.local:7878.
  • Bazarr 4K: Sonarr off, Radarr radarr-4k.arr-stack.svc.cluster.local:7878.
  • Rebuilding Bazarr 4K: copy /config/config/config.yaml from Bazarr. It carries the provider logins. Keep the new instance's own auth.apikey, set use_sonarr: false and the Radarr 4K address and key, then restart. The language profile is in the database, so create it again (Settings > Languages) and assign it to the films.
  • Each has an exporter (exportarr-bazarr, exportarr-bazarr-4k) and a hub tile. The 4K key is Bitwarden secret homepage-bazarr-4k-api.

Cleanuparr

https://cleanuparr.local.bigd.no, admin account in the password manager.

  • Download client qBittorrent at http://qbittorrent.gluetun-vpn:8080, with the qBittorrent exporter credentials.
  • Sonarr http://sonarr:8989 (v4), Radarr http://radarr:7878 (v6), Radarr 4K http://radarr-4k:7878 (v6).
  • Queue Cleaner, every 5 min, failed imports at 3 strikes: Ignore Private off, Delete Private off, Skip if not found in client on, Force Import on (3 tries), pattern mode Exclude with matched to series by ID and matched to movie by ID. No stall or slow rules.
  • Malware Blocker, every 5 min: blocklist https://cleanuparr.pages.dev/static/blacklist_permissive for Sonarr and Radarr, Ignore Private off, Delete Private off.
  • Seeker: search on, proactive search off.
  • Download Cleaner: off.

Delete Private off is the rule that keeps private torrents seeding. Cleanuparr then removes only the item from the arr queue and leaves the torrent in qBittorrent.

Tdarr

https://tdarr.local.bigd.no. The server has an internal node on genesis-worker-01. The node uses the iGPU through Quick Sync, shared with Plex (see k8s/talos/infra/intel-gpu-plugin/daemonset.yaml).

Flow h264ToHevcQsv, stored in tdarr-flow-h264-to-hevc-qsv.json next to this file:

  1. Skip files with more than one link (still seeding) and anything with remux in the path.
  2. In an MKV with a usable English track, make that track the first audio track and the default. Commentary and audio-description tracks do not count. Clear the default flag on subtitles in other languages. All streams are copied, so quality does not change. Stop with an error if the duration changes by more than 0.5 percent.
  3. Only H.264 video continues.
  4. Encode video to HEVC with QSV, preset slow, quality 23, audio and subtitles copied.
  5. Stop with an error if the new file's duration is not within 0.5 percent of the original's. This catches a truncated or broken encode.
  6. Replace the original only when the new file is under 85 percent of the old size. Otherwise switch back to the original and end without an error. There is no lower size bound. QSV quality 23 gives 6 to 20 percent on clean H.264 WEB-DL and Bluray sources at 42 to 50 dB PSNR. The duration check is what guards against a broken output.

Restore it with:

jq -c '{data:{collection:"FlowsJSONDB", mode:"insert", docID:._id, obj:.}}' docs/apps/media-stack/tdarr-flow-h264-to-hevc-qsv.json \
  | curl -s -H 'Content-Type: application/json' https://tdarr.local.bigd.no/api/v2/cruddb -d @-

Libraries TV (/data/series), Movies (/data/movies) and Movies 4K (/data/movies-4k): flow h264ToHevcQsv, cache /data/transcode_cache/tdarr, health checks off, "skip hardlinked files" on, folder watching on, @eaDir ignored, schedule 00:00 to 07:00 every day. Scanner: 4 threads, MediaInfo scan off. The flow reads only ffprobe data, and each extra scanner reads every file again over NFS. FFprobe and ExifTool always run. The UI shows them as fixed ON.

Node: 1 GPU transcode worker, no CPU workers. Set the worker limit on the live node (Nodes page, or POST /api/v2/alter-worker-limit). A write to the database alone does nothing.

A re-encode of a file that is hardlinked to a seeding torrent would split it into two copies again. That is why both the library setting and the flow skip such files.