Self-hosted BitTorrent + arr-stack + catalog-update pipeline targeting
nullstone (Debian 13). Replaces the legacy onyx -> rsync -> import
round-trip.
Contents:
- README.md headline + ASCII architecture diagram + quickstart
- CLAUDE.md project rules (mirrors beta-flix style)
- .gitignore secrets dirs (.env, gluetun, qbt config, ssh keys)
- .gitleaksignore allowlist nullstone LAN addr + Tailscale CGNAT
- docs/architecture.md the plan in detail (gluetun + qbt + arr + catalog)
- docs/migration.md onyx-qbt -> nullstone-qbt runbook (3 phases)
- docs/trackers.md tracker schema + IP-pinning + ratio notes (user-curated)
- compose/docker-compose.yml gluetun v3.40 + qbt 5.0.5 (netns=gluetun) +
sonarr/radarr/prowlarr (hotio) + betaflix-catalog
- compose/.env.example documented env-var template (no secrets)
- compose/traefik/arr.yml file-provider for qbt/sonarr/radarr/prowlarr
.s8n.ru subdomains, LAN+TS only via
trusted-only@file + authentik-forwardauth@file
- catalog/catalog.py Flask service, ~340 LoC, /sonarr + /radarr +
/healthz; pulls beta-flix, inserts alphabetic
row into MEDIA-LIST.md, writes run log, commits
+ pushes as obsidian-ai. Idempotent via
payload-hash cache.
- catalog/Dockerfile python:3.12-slim + git + tini
- catalog/requirements.txt flask + jinja2 + requests + gitpython + pyyaml (pinned)
- catalog/templates/*.j2 run log + catalog row Jinja templates
- catalog/README.md service docs
- scripts/migrate-onyx.sh phase-2 helper (rsync + .torrent ship, dry-run by default)
- scripts/add-tracker.sh Prowlarr API helper
- scripts/killswitch-test.sh gluetun kill-switch verification (3 steps)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
141 lines
3.9 KiB
YAML
141 lines
3.9 KiB
YAML
# nullstone media-acquisition stack
|
|
#
|
|
# Compose file for: gluetun (VPN + kill-switch) + qBittorrent + Sonarr +
|
|
# Radarr + Prowlarr + betaflix-catalog (Forgejo committer).
|
|
#
|
|
# Place this directory under /opt/docker/media-acquisition/ on nullstone.
|
|
# Run: docker compose up -d
|
|
# Verify kill-switch: bash ../scripts/killswitch-test.sh
|
|
|
|
services:
|
|
gluetun:
|
|
image: qmcgaw/gluetun:v3.40
|
|
container_name: gluetun
|
|
cap_add:
|
|
- NET_ADMIN
|
|
devices:
|
|
- /dev/net/tun:/dev/net/tun
|
|
environment:
|
|
- VPN_SERVICE_PROVIDER=protonvpn
|
|
- VPN_TYPE=wireguard
|
|
- WIREGUARD_PRIVATE_KEY=${PVPN_WG_PRIVKEY}
|
|
- WIREGUARD_ADDRESSES=${PVPN_WG_ADDRESSES}
|
|
- SERVER_COUNTRIES=${PVPN_SERVER_COUNTRIES:-Netherlands}
|
|
- VPN_PORT_FORWARDING=on
|
|
- VPN_PORT_FORWARDING_PROVIDER=protonvpn
|
|
- FIREWALL_OUTBOUND_SUBNETS=192.168.0.0/24,172.16.0.0/12,100.64.0.0/10
|
|
- DOT=off
|
|
- TZ=${TZ:-Europe/London}
|
|
ports:
|
|
# All published on 127.0.0.1 — Traefik file-provider picks them up.
|
|
- "127.0.0.1:8080:8080" # qBittorrent WebUI
|
|
- "127.0.0.1:9696:9696" # Prowlarr
|
|
- "127.0.0.1:8989:8989" # Sonarr
|
|
- "127.0.0.1:7878:7878" # Radarr
|
|
volumes:
|
|
- ./gluetun:/gluetun
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-qO-", "--tries=1", "--timeout=10", "https://ipinfo.io"]
|
|
interval: 30s
|
|
timeout: 15s
|
|
retries: 3
|
|
start_period: 30s
|
|
|
|
qbittorrent:
|
|
image: qbittorrentofficial/qbittorrent-nox:5.0.5
|
|
container_name: qbittorrent
|
|
depends_on:
|
|
gluetun:
|
|
condition: service_healthy
|
|
network_mode: "service:gluetun"
|
|
user: "1000:1000"
|
|
environment:
|
|
- QBT_LEGAL_NOTICE=confirm
|
|
- QBT_WEBUI_PORT=8080
|
|
- UMASK=022
|
|
- TZ=${TZ:-Europe/London}
|
|
volumes:
|
|
- ./qbittorrent/config:/config
|
|
- /home/user/media/_downloads:/downloads
|
|
- /home/user/media:/media
|
|
restart: unless-stopped
|
|
|
|
prowlarr:
|
|
image: ghcr.io/hotio/prowlarr:release
|
|
container_name: prowlarr
|
|
depends_on:
|
|
gluetun:
|
|
condition: service_healthy
|
|
network_mode: "service:gluetun"
|
|
environment:
|
|
- PUID=1000
|
|
- PGID=1000
|
|
- UMASK=022
|
|
- TZ=${TZ:-Europe/London}
|
|
volumes:
|
|
- ./prowlarr:/config
|
|
restart: unless-stopped
|
|
|
|
sonarr:
|
|
image: ghcr.io/hotio/sonarr:release
|
|
container_name: sonarr
|
|
depends_on:
|
|
gluetun:
|
|
condition: service_healthy
|
|
network_mode: "service:gluetun"
|
|
environment:
|
|
- PUID=1000
|
|
- PGID=1000
|
|
- UMASK=022
|
|
- TZ=${TZ:-Europe/London}
|
|
volumes:
|
|
- ./sonarr:/config
|
|
- /home/user/media:/media
|
|
restart: unless-stopped
|
|
|
|
radarr:
|
|
image: ghcr.io/hotio/radarr:release
|
|
container_name: radarr
|
|
depends_on:
|
|
gluetun:
|
|
condition: service_healthy
|
|
network_mode: "service:gluetun"
|
|
environment:
|
|
- PUID=1000
|
|
- PGID=1000
|
|
- UMASK=022
|
|
- TZ=${TZ:-Europe/London}
|
|
volumes:
|
|
- ./radarr:/config
|
|
- /home/user/media:/media
|
|
restart: unless-stopped
|
|
|
|
betaflix-catalog:
|
|
image: betaflix-catalog:local
|
|
container_name: betaflix-catalog
|
|
build:
|
|
context: ../catalog
|
|
dockerfile: Dockerfile
|
|
# NOT bound to gluetun — needs to reach Forgejo + Sonarr/Radarr
|
|
network_mode: bridge
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
environment:
|
|
- FORGEJO_REMOTE=${FORGEJO_REMOTE:-https://git.s8n.ru/s8n/beta-flix.git}
|
|
- FORGEJO_PUSH_TOKEN=${FORGEJO_PUSH_TOKEN}
|
|
- GIT_AUTHOR_NAME=obsidian-ai
|
|
- GIT_AUTHOR_EMAIL=obsidian-ai@s8n.ru
|
|
- GIT_COMMITTER_NAME=obsidian-ai
|
|
- GIT_COMMITTER_EMAIL=obsidian-ai@s8n.ru
|
|
- SONARR_API_KEY=${SONARR_API_KEY}
|
|
- RADARR_API_KEY=${RADARR_API_KEY}
|
|
- TZ=${TZ:-Europe/London}
|
|
- LISTEN_PORT=5055
|
|
ports:
|
|
- "127.0.0.1:5055:5055"
|
|
volumes:
|
|
- ./catalog/repo:/repo
|
|
- ./catalog/ssh:/root/.ssh:ro
|
|
- ./catalog/state:/state
|
|
restart: unless-stopped
|