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>
|
||
|---|---|---|
| .. | ||
| templates | ||
| catalog.py | ||
| Dockerfile | ||
| README.md | ||
| requirements.txt | ||
betaflix-catalog
Flask service that receives Sonarr/Radarr OnImport webhooks and commits
catalog updates to git.s8n.ru/s8n/beta-flix.
What it does
For each Import event:
- Pulls latest
mainofbeta-flix(rebase). - Inserts a row into
playbooks/import-media/MEDIA-LIST.md, alphabetic by title. Dedupes — if the key (Title (Year)) already exists, it skips. - Writes a per-import run log to
playbooks/import-media/runs/<slug>.mdusing the Jinja template attemplates/run.md.j2. - Commits as
obsidian-ai <obsidian-ai@s8n.ru>. - Pushes to Forgejo using
FORGEJO_PUSH_TOKENembedded in the URL.
Idempotency: payload-hash cache at /state/seen-imports.json. Sonarr/Radarr
retry transient failures; duplicates are no-ops.
Endpoints
POST /sonarr— Sonarr Connect webhook target. Set Sonarr → Settings → Connect → Webhook → URLhttp://host.docker.internal:5055/sonarr, method POST, triggers: On Import only.POST /radarr— same shape for Radarr at/radarr.GET /healthz— liveness probe.
Build
cd catalog/
docker build -t betaflix-catalog:local .
Or use compose: the parent compose/docker-compose.yml defines the
betaflix-catalog service with build: set to this directory.
Env vars
| Variable | Required | Default | What |
|---|---|---|---|
FORGEJO_REMOTE |
yes | https://git.s8n.ru/s8n/beta-flix.git |
Push target. |
FORGEJO_PUSH_TOKEN |
yes | (empty) | Forgejo PAT — scopes: repository RW. |
GIT_AUTHOR_NAME |
no | obsidian-ai |
Commit author. |
GIT_AUTHOR_EMAIL |
no | obsidian-ai@s8n.ru |
Commit author email. |
REPO_PATH |
no | /repo |
Where beta-flix gets cloned. |
STATE_DIR |
no | /state |
seen-imports.json lives here. |
LISTEN_PORT |
no | 5055 |
Flask bind port. |
Volumes
/repo— beta-flix checkout. Bind-mounted persistent volume./state—seen-imports.jsoncache./root/.ssh(optional, read-only) — for SSH deploy key (currently uses HTTPS+PAT; SSH path reserved for future).
Development
Run locally without Docker:
cd catalog/
python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
REPO_PATH=/tmp/beta-flix-test STATE_DIR=/tmp/catalog-state \
FORGEJO_PUSH_TOKEN=xxx python catalog.py
# In another shell:
curl -X POST http://localhost:5055/sonarr \
-H 'Content-Type: application/json' \
-d '{"eventType":"Test"}'