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>
109 lines
5.2 KiB
Markdown
109 lines
5.2 KiB
Markdown
# CLAUDE.md — media-acquisition
|
|
|
|
Read this at session start. Rules for managing the nullstone media-acquisition
|
|
pipeline.
|
|
|
|
## What this repo is
|
|
|
|
The BitTorrent + arr-stack + catalog-update pipeline that feeds the ARRFLIX
|
|
library on **nullstone** (Debian 13, `192.168.0.100`). Consumed by:
|
|
|
|
- **Jellyfin** at `tv.s8n.ru` (container `jellyfin-stock`).
|
|
- **Catalog** at `git.s8n.ru/s8n/beta-flix` → `playbooks/import-media/MEDIA-LIST.md`.
|
|
|
|
## Source map
|
|
|
|
```
|
|
docs/architecture.md Plan + reasoning. Read this BEFORE editing compose.
|
|
docs/migration.md onyx-qbt → nullstone-qbt migration runbook.
|
|
docs/trackers.md Tracker schema + IP-pinning risks (user-curated).
|
|
compose/docker-compose.yml gluetun + qbt + sonarr + radarr + prowlarr + catalog.
|
|
compose/.env.example Env template — secrets live in .env (gitignored).
|
|
compose/traefik/arr.yml File-provider routing for arr stack.
|
|
catalog/ betaflix-catalog Python service (Flask + webhooks).
|
|
scripts/ migrate-onyx.sh, add-tracker.sh, killswitch-test.sh.
|
|
```
|
|
|
|
## Deploy lifecycle
|
|
|
|
1. **Edit** files locally under `/home/admin/projects/media-acquisition/`.
|
|
2. **Push to Forgejo** (this repo's authoritative remote is
|
|
`git.s8n.ru/s8n/media-acquisition.git`).
|
|
3. **On nullstone**: `cd /opt/docker/media-acquisition && git pull && docker compose up -d`.
|
|
4. **CRITICAL — verify kill-switch after every gluetun change**:
|
|
`bash scripts/killswitch-test.sh`. If the second curl succeeds, you have a leak;
|
|
tear down before re-trying.
|
|
|
|
## Rules paid for in blood (mirrored from beta-flix where applicable)
|
|
|
|
### Rule 1 — SSH user
|
|
`user@nullstone`. **NOT** `admin@nullstone`. AllowUsers was tightened
|
|
2026-05-03; uid 1000 only. Memory: `[[feedback_nullstone_ssh_user]]`.
|
|
|
|
### Rule 2 — Commit + push to **my git**
|
|
Authoritative remote is `git.s8n.ru/s8n/media-acquisition.git` (Forgejo).
|
|
No GitHub mirror. Always `git remote -v` before push. Memory:
|
|
`[[feedback_always_commit_to_my_git]]`, `[[feedback_check_remote_before_push]]`,
|
|
`[[feedback_my_git_is_forgejo]]`.
|
|
|
|
### Rule 3 — Commit identity
|
|
- Human commits: `s8n <admin@s8n.ru>`.
|
|
- Bot/automation commits (e.g. catalog service, scripted edits): `obsidian-ai <obsidian-ai@s8n.ru>`.
|
|
|
|
Memory: `[[user_git_identity]]`.
|
|
|
|
### Rule 4 — Kill-switch is non-negotiable
|
|
Every change to `gluetun` service or VPN env vars MUST be followed by
|
|
`scripts/killswitch-test.sh`. A torrent client leaking outside the VPN is the
|
|
single failure mode that defines this project — do not "trust" the firewall
|
|
based on config inspection. Run the test.
|
|
|
|
### Rule 5 — No secrets in repo
|
|
`.env`, WireGuard keys, Forgejo PATs, deploy keys: all gitignored. Use
|
|
`.env.example` to document variable names with placeholders. If you commit a
|
|
secret by accident, rotate it (Proton WG: regenerate key, update gluetun;
|
|
Forgejo PAT: revoke at `git.s8n.ru/-/user/settings/applications`).
|
|
|
|
### Rule 6 — Tracker IP pinning
|
|
Private trackers may pin sessions to a single source IP. Switching from
|
|
onyx public IP → Proton exit IP will trip them. Before adding a new tracker
|
|
or migrating an old torrent, check `docs/trackers.md` for the per-tracker
|
|
policy. Update `docs/trackers.md` whenever a new tracker is on-boarded.
|
|
|
|
### Rule 7 — XFS reflinks / hardlinks
|
|
`/home/user/media` is XFS, single device. Sonarr/Radarr "Use Hardlinks
|
|
instead of Copy" = ON. Catalog service may use `cp --reflink=always` for
|
|
divergent-perm scenarios (free inodes, zero block cost). Never `cp` plain;
|
|
that doubles disk usage and breaks seeding atomicity.
|
|
|
|
## Canonical naming
|
|
|
|
Catalog rows pushed to `beta-flix/playbooks/import-media/MEDIA-LIST.md` follow
|
|
the ARRFLIX house style:
|
|
|
|
- TV: `Series Title (Year)` — alphabetic by title, year tiebreaker.
|
|
- Movies: `Movie Title (Year)` — alphabetic by title.
|
|
- "Source / Version" column = raw Sonarr/Radarr `sourceTitle` (release name).
|
|
Human edits to "Why on arrflix" stay; bot never overwrites that column.
|
|
|
|
The catalog service is **append + merge only** — never overwrites human-authored
|
|
notes.
|
|
|
|
## How to start a session
|
|
|
|
1. Read this file.
|
|
2. Read `docs/architecture.md` if working on compose or catalog code.
|
|
3. Check `git status` and `git remote -v` (must show
|
|
`git.s8n.ru/s8n/media-acquisition.git`).
|
|
4. Owner says what they want; ship + verify kill-switch + commit to Forgejo.
|
|
5. End every turn: commit + push to `git.s8n.ru/s8n/media-acquisition.git`.
|
|
|
|
## Glossary
|
|
|
|
| Term | Means |
|
|
|-----------------------|----------------------------------------------------------------------------------|
|
|
| **ship** / **deploy** | git push to Forgejo → on nullstone, `git pull && docker compose up -d`. Kill-switch test on any gluetun change. |
|
|
| **migrate** | Phase-2 onyx→nullstone runbook in `docs/migration.md`. Read `scripts/migrate-onyx.sh` first; dry-run mode mandatory. |
|
|
| **add tracker** | `scripts/add-tracker.sh <name> <url>`; then update `docs/trackers.md` with IP-pinning policy + ratio requirements. |
|
|
| **killswitch test** | `bash scripts/killswitch-test.sh`. NEVER claim "VPN works" without running this. |
|
|
| **owner** | P (xynki.dev@gmail.com). Final say. Executive-override pattern from `[[feedback_s8n_executive_override]]` applies. |
|