No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Bob Parsons ed9b41538f
All checks were successful
Deploy / deploy (push) Successful in 44s
Merge pull request 'Promote develop: seerr 3.5.0' (#7) from develop into main
Reviewed-on: ⁨#7⁩
2026-10-09 09:32:53 -05:00
.forgejo/workflows chore: move workflow .gitea/ -> .forgejo/ 2026-09-29 22:06:27 -05:00
.env.example Drop prowlarr's unused media mount; correct MEDIA_ROOT docs 2026-09-13 07:56:46 -05:00
.gitignore init commit 2026-03-07 08:07:31 -06:00
docker-compose.yml Update ghcr.io/seerr-team/seerr Docker tag to v3.5.0 2026-10-05 08:04:51 +00:00
README.md chore: move workflow .gitea/ -> .forgejo/ 2026-09-29 22:06:15 -05:00
renovate.json chore: onboard to Renovate 2026-09-29 20:50:03 -05:00

watchlist

Automated media stack for Jellyfin, deployed separately from Jellyfin itself.

Services in this project:

  • gluetun (ProtonVPN WireGuard gateway)
  • qbittorrent (torrent download client)
  • sabnzbd (usenet download client)
  • prowlarr (indexer manager)
  • sonarr (TV)
  • radarr (movies)
  • seerr (request UI for users)

Architecture

  • VPN-routed services (through gluetun): qbittorrent, sabnzbd, sonarr, radarr, prowlarr
  • Non-VPN services: seerr
  • Shared media/download storage: host ${MEDIA_ROOT} mounted as /data in qbittorrent, sabnzbd, sonarr, radarr. prowlarr has no media mount — it only manages indexers.
  • Jellyfin stays separate, only reads the library directories

This keeps playback stable even if VPN or automation services fail.

Prerequisites

  • Docker + Docker Compose installed
  • Existing Traefik network named traefik (or create it)
  • ProtonVPN WireGuard config values:
    • WIREGUARD_PRIVATE_KEY
    • WIREGUARD_ADDRESSES

Directory Layout

Default MEDIA_ROOT is /mnt/media (NFSv3, volume2). The older /mnt/nas-direct (CIFS/SMB, volume1) is being phased out — see NAS-SMB-TO-NFS-MIGRATION.md on the host.

Expected structure:

  • media/library/movies
  • media/library/tv
  • media/rips/movies
  • media/rips/tv
  • downloads/torrents/incomplete
  • downloads/torrents/complete
  • downloads/usenet/incomplete
  • downloads/usenet/complete

Create it:

mkdir -p \
  /mnt/media/media/library/movies \
  /mnt/media/media/library/tv \
  /mnt/media/media/rips/movies \
  /mnt/media/media/rips/tv \
  /mnt/media/downloads/torrents/incomplete \
  /mnt/media/downloads/torrents/complete \
  /mnt/media/downloads/usenet/incomplete \
  /mnt/media/downloads/usenet/complete

Configuration

  1. Copy env template:
cp .env.example .env
  1. Fill required values in .env:
  • WIREGUARD_PRIVATE_KEY
  • WIREGUARD_ADDRESSES
  1. Optional VPN filtering:
  • SERVER_COUNTRIES=United States
  • SERVER_CITIES= (blank is usually best initially)
  1. Optional LAN allow-list from VPN containers:
  • FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24

Use only if VPN-contained apps must call LAN IPs.

Deploy

Pushing to main triggers .forgejo/workflows/deploy.yml, which writes .env from Forgejo secrets and redeploys. It runs docker compose down plus docker rm -f on all seven containers, so every deploy briefly takes the whole stack offline and forces a ProtonVPN reconnect. Deploy when nobody is mid-download.

Manual deploy:

cd /home/bobparsons/Development/watchlist
docker network inspect traefik >/dev/null 2>&1 || docker network create traefik
docker compose -p watchlist pull
docker compose -p watchlist up -d --remove-orphans
docker compose -p watchlist ps

Service URLs

  • Seerr: https://${SEERR_DOMAIN}
  • Prowlarr: http://<host-ip>:9696
  • Sonarr: http://<host-ip>:8989
  • Radarr: http://<host-ip>:7878
  • SABnzbd: http://<host-ip>:8085
  • qBittorrent: http://<host-ip>:8080

Authentication

Sonarr and Radarr run with <AuthenticationMethod>External</AuthenticationMethod> (built-in login disabled) as of 2026-09-13. Neither has a Traefik route; both are reachable only on the LAN through the ports gluetun publishes.

This setting lives in config/<app>/config.xml, which is gitignored and bind-mounted, so it persists across redeploys and is not represented in this repo. To re-enable logins, set the value back to Forms and set a username/password under Settings → General → Security. The pre-change files are kept alongside as config.xml.pre-authfix.*.

Prowlarr was switched to External the same way on 2026-09-20 (its Forms password had been lost). Same file, same revert path; backup kept as config.xml.bak-*.

Note the account username is deadstylebp, not admin.

First-Time App Wiring

1) Download clients in Sonarr/Radarr

Add SABnzbd:

  • Host: localhost
  • Port: 8085
  • Categories:
    • Sonarr: tv
    • Radarr: movies

(Optional) Add qBittorrent:

  • Host: localhost
  • Port: 8080
  • Matching categories if you use torrents

2) Root folders

Use container paths:

  • Sonarr: /data/media/library/tv
  • Radarr: /data/media/library/movies

Enable hardlinks where available in Media Management.

3) Prowlarr

  • Add indexers you have access to (e.g. NZBGeek)
  • Add Applications (prowlarr shares gluetun's network namespace, so these are loopback, not container names):
    • Prowlarr Server: http://localhost:9696
    • Sonarr: http://localhost:8989
    • Radarr: http://localhost:7878
  • Use each app's API key

4) Seerr

  • Connect to Jellyfin
  • Add Sonarr and Radarr in Seerr using:
    • Sonarr: http://gluetun:8989
    • Radarr: http://gluetun:7878

VPN Port Forwarding

Deliberately off. Gluetun supports ProtonVPN port forwarding, and it was briefly enabled on 2026-09-20 and then reverted.

Why it is off:

  • Only public trackers are in use, so there is no ratio to maintain and nothing that requires being dialable from the swarm.
  • Well-seeded torrents do not need it. Measured without a forwarded port: ~28 GB pulled at 20-25 MB/s from a swarm with ~950 seeders.
  • Enabling it costs something. Gluetun implicitly turns on "port forwarding only servers", which narrows server selection.

It also does not work with the current WireGuard key. Proton only answers NAT-PMP for configs generated with port forwarding enabled -- it is a property of the key, not a client-side request. With the current key gluetun logs, harmlessly and on every start:

[port forwarding] ... read udp 10.2.0.2:45081->10.2.0.1:5351:
recvfrom: connection refused

To revisit (worth it only if a private tracker with ratio requirements is added, or if poorly-seeded torrents become common):

  1. Proton dashboard -> Downloads -> WireGuard configuration, pick a P2P server, toggle NAT-PMP (Port Forwarding) on, generate a new config.
  2. Update the WIREGUARD_PRIVATE_KEY secret (and WIREGUARD_ADDRESSES if it changed) and redeploy.
  3. Add back to gluetun's environment: VPN_PORT_FORWARDING=on and VPN_PORT_FORWARDING_STATUS_FILE=/gluetun/forwarded_port
  4. Read the assigned port from config/gluetun/forwarded_port and set it as qBittorrent's listening port. Proton rotates this port, so either update it manually or drive it from VPN_PORT_FORWARDING_UP_COMMAND, which needs qBittorrent's WebUI credentials.

Torrent Seeding and Cleanup

Set in qBittorrent on 2026-09-20 (config/qbittorrent/.../qBittorrent.conf, gitignored like the other app configs):

  • Session\GlobalMaxRatio=2
  • Session\GlobalMaxSeedingMinutes=14400 (10 days)
  • Session\ShareLimitAction=Stop (pre-existing)

Sonarr and Radarr have removeCompletedDownloads=True, but that only fires once qBittorrent considers seeding finished. With no limits set, that never happened and downloads/torrents grew without bound. Without port forwarding the 2.0 ratio is rarely reached, so in practice the 10-day limit is what retires torrents.

Both apps also recycle rather than hard-delete: recycleBin=/data/.recycle (host /mnt/media/.recycle, uid 1000), 7-day retention.

Jellyfin Integration

In Jellyfin, mounts and libraries should remain read-only. Jellyfin currently reads movies/TV from the NFS mount and music from the older SMB mount:

  • /mnt/media/media/library/movies:/media/library/movies:ro
  • /mnt/media/media/library/tv:/media/library/tv:ro
  • /mnt/nas-direct/media/music:/media/music:ro (not yet migrated)
  • Libraries:
    • /media/library/movies
    • /media/library/tv

Keep manual rips under /media/rips/* as separate staging/input paths.

Operational Checks

Verify VPN routing

curl -s https://api.ipify.org && echo
docker exec media-radarr sh -lc 'curl -s https://api.ipify.org && echo'

Host IP and container IP should differ if VPN routing is active.

Common useful commands

docker compose -p watchlist logs -f gluetun
docker compose -p watchlist logs -f radarr
docker compose -p watchlist logs -f sonarr
docker compose -p watchlist ps

Common Issues

  • Download client unavailable

    • Verify SAB/qB is added with localhost inside Arr app.
  • No indexers available

    • Check Prowlarr indexer test results.
    • Re-sync app indexers from Prowlarr.
  • The /app/config volume mount was not configured properly in Seerr

    • Ensure named volume seerr-config is mounted.
  • Gluetun unhealthy

    • Start with SERVER_COUNTRIES=United States and blank SERVER_CITIES.
    • Confirm WireGuard values are correct.

Updating

cd /home/bobparsons/Development/watchlist
docker compose -p watchlist pull
docker compose -p watchlist up -d --remove-orphans
docker image prune -f