This repository has been archived on 2026-05-20. You can view files and clone it, but cannot push or open issues or pull requests.
media-acquisition/scripts/migrate-onyx.sh
obsidian-ai d300d83ce1 init: media-acquisition pipeline scaffold
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>
2026-05-20 01:15:43 +01:00

109 lines
3.7 KiB
Bash
Executable file

#!/usr/bin/env bash
# scripts/migrate-onyx.sh — Phase 2 migration helper.
#
# Usage:
# ./migrate-onyx.sh <source-dir-on-onyx> <target-dir-on-nullstone>
#
# Example:
# ./migrate-onyx.sh "$HOME/Downloads/qbt/" \
# /home/user/media/_downloads/complete/
#
# What it does:
# 1. rsync <source-dir> → user@nullstone:<target-dir> over LAN.
# 2. Copies onyx's .torrent + .fastresume files to /tmp/qbt-migrate/
# on nullstone (you mass-add them via qbt webui afterwards).
# 3. Prints a checklist of remaining manual steps.
#
# Pre-reqs:
# - run from onyx (the SOURCE machine).
# - ssh user@nullstone reachable on LAN.
# - nullstone qbt stack already up (Phase 1 complete) — check with
# `bash killswitch-test.sh` first.
set -euo pipefail
SRC="${1:-}"
DST="${2:-}"
NULLSTONE="${NULLSTONE_SSH:-user@192.168.0.100}"
DRY_RUN="${DRY_RUN:-1}"
usage() {
cat <<EOF
Usage: $0 <source-dir> <target-dir-on-nullstone>
Env:
NULLSTONE_SSH default: user@192.168.0.100
DRY_RUN default: 1 (rsync --dry-run). Set DRY_RUN=0 to actually copy.
Example (dry-run):
$0 "\$HOME/Downloads/qbt/" /home/user/media/_downloads/complete/
Example (real):
DRY_RUN=0 $0 "\$HOME/Downloads/qbt/" /home/user/media/_downloads/complete/
EOF
exit 2
}
[ -z "$SRC" ] || [ -z "$DST" ] && usage
[ -d "$SRC" ] || { echo "Source dir not found: $SRC" >&2; exit 1; }
QBT_BACKUP="$HOME/.local/share/qBittorrent/BT_backup"
[ -d "$QBT_BACKUP" ] || { echo "qBittorrent BT_backup dir missing: $QBT_BACKUP" >&2; exit 1; }
RSYNC_FLAGS=(-av --info=progress2 --partial --human-readable)
if [ "$DRY_RUN" = "1" ]; then
RSYNC_FLAGS+=(--dry-run)
echo "=== DRY-RUN — no data will be copied. Set DRY_RUN=0 to run for real. ==="
fi
echo "=== Step 1/3: rsync data files to nullstone ==="
rsync "${RSYNC_FLAGS[@]}" "$SRC" "$NULLSTONE:$DST"
echo
echo "=== Step 2/3: ship .torrent + .fastresume to nullstone /tmp/qbt-migrate/ ==="
ssh "$NULLSTONE" "mkdir -p /tmp/qbt-migrate"
if [ "$DRY_RUN" = "1" ]; then
echo "(dry-run) would scp $QBT_BACKUP/*.torrent $QBT_BACKUP/*.fastresume → $NULLSTONE:/tmp/qbt-migrate/"
else
scp -q "$QBT_BACKUP"/*.torrent "$QBT_BACKUP"/*.fastresume "$NULLSTONE:/tmp/qbt-migrate/"
fi
echo
echo "=== Step 3/3: remaining manual steps ==="
cat <<EOF
Manual steps on the nullstone qbt webui (https://qbt.s8n.ru):
1. "Add Torrent" → multi-select all files in /tmp/qbt-migrate/*.torrent
Save path: $DST
[ ] UNCHECK "Start torrent" — we want them queued, not auto-resumed.
2. Select all newly-added torrents → right-click → "Force recheck"
qbt will hash-match files in $DST → mark each at 100% → start seeding.
3. Check tracker status per torrent. Private trackers may reject the new
source IP (Proton exit). See docs/trackers.md for the per-tracker
mitigation playbook.
4. On onyx (THIS machine): pause torrents one-by-one as nullstone takes
over each. Do NOT stop onyx-qbt entirely until every torrent shows
seeding on nullstone for 24h with zero tracker errors.
Fastresume path-rewrite (optional, only if save_path drift breaks recheck):
ssh $NULLSTONE 'python3 - <<PYEOF
import os, re, sys
from pathlib import Path
src_prefix = "/home/admin/Downloads/qbt" # onyx path
dst_prefix = "/downloads/complete" # nullstone path inside qbt container
for fr in Path("/tmp/qbt-migrate").glob("*.fastresume"):
data = fr.read_bytes()
if src_prefix.encode() in data:
new = data.replace(src_prefix.encode(), dst_prefix.encode())
fr.write_bytes(new)
print("rewrote:", fr.name)
PYEOF'
(Only run if force-recheck fails — qbt 5 usually handles re-pathing via
the UI's "Save path" field on add.)
EOF