#!/usr/bin/env python3 """make_drill.py — Part B2: rewrite the six media templates IN THE DRILL CATALOG to the layout under test. The layout (one bind per drive, the same path inside the container as in the guest, `userdata/media` only): /mnt/felhom-drives//userdata/media -> /mnt/felhom-drives//userdata/media (rw; :ro for Plex/Jellyfin) for in DRIVES. Images are NOT touched. Plain host paths on purpose: the controller's bind classifier knows only the ${..._PATH} roots (stacks/classify_binds.go classifyRoot), so these binds are in no app capture set (A6). Because the ${USERDATA_PATH} binds go away, the `.felhom.yml` `backup.userdata` entries and `data_paths` that named them are removed too — appbackup.ValidateBackupSpec rejects an entry that matches no compose bind, and a rejected block degrades the app to the legacy path. qBittorrent's and Transmission's `backup.volumes` (their config) stay. Default save paths: drive A's `media/torrents` (qBittorrent, seeded on first boot) and `media/transmission/complete` (Transmission, set by its entrypoint on a fresh config only). Everything else is unchanged. python3 make_drill.py [--check] # edits /mnt/5_hdd/felhom.eu/drill/app-catalog-drill/templates in place """ import re, sys D = "/mnt/5_hdd/felhom.eu/drill/app-catalog-drill/templates" DRIVES = ("media_a", "media_b") M = {d: f"/mnt/felhom-drives/{d}/userdata/media" for d in DRIVES} A = M["media_a"] def binds(indent, ro): suf = ":ro" if ro else "" return "".join(f"{indent}- {M[d]}:{M[d]}{suf}\n" for d in DRIVES) def sub1(s, old, new, app): if old not in s: sys.exit(f"{app}: expected text not found: {old!r}") return s.replace(old, new, 1) def compose(app): p = f"{D}/{app}/docker-compose.yml" s = open(p).read() if M["media_a"] in s: return f"{app}: compose already in the new layout" i = " " if app == "sonarr": s = sub1(s, f"{i}- ${{USERDATA_PATH}}/media/tv:/media/tv\n{i}- ${{USERDATA_PATH}}/downloads:/downloads\n", binds(i, False), app) elif app == "radarr": s = sub1(s, f"{i}- ${{USERDATA_PATH}}/media/movies:/media/movies\n{i}- ${{USERDATA_PATH}}/downloads:/downloads\n", binds(i, False), app) elif app == "qbittorrent": s = sub1(s, f"{i}- ${{USERDATA_PATH}}/media/torrents:/downloads\n", binds(i, False), app) s = sub1(s, "'Session\\DefaultSavePath=/downloads'", f"'Session\\DefaultSavePath={A}/torrents'", app) s = sub1(s, "'Session\\TempPath=/downloads/.temp'", f"'Session\\TempPath={A}/torrents/.temp'", app) elif app == "transmission": s = sub1(s, f"{i}- ${{USERDATA_PATH}}/media/transmission:/downloads\n", binds(i, False), app) s = sub1(s, f"{i}- ${{USERDATA_PATH}}/media/transmission/watch:/watch\n", f"{i}- {A}/transmission/watch:/watch\n", app) # the default download folders are set ONCE, on a fresh config (a household's later choice is kept) s = sub1(s, """ cp /defaults/settings.json "$$CONF"\n""", f""" cp /defaults/settings.json "$$CONF"\n jq '.["download-dir"] = "{A}/transmission/complete" | .["incomplete-dir"] = "{A}/transmission/incomplete"' "$$CONF" > /tmp/d.json && mv /tmp/d.json "$$CONF"\n""", app) elif app in ("plex", "jellyfin"): s = sub1(s, f"{i}- ${{USERDATA_PATH}}/media:/media:ro\n", binds(i, True), app) if "${USERDATA_PATH}" in re.sub(r"^#.*$", "", s, flags=re.M): sys.exit(f"{app}: a ${{USERDATA_PATH}} bind is left") open(p, "w").write(s) return f"{app}: compose rewritten" def felhom_yml(app): p = f"{D}/{app}/.felhom.yml" s = open(p).read() before = s # drop backup.userdata (the list under it) — and the whole backup block when nothing else is left in it s = re.sub(r"(^backup:\n(?: volumes:\n(?: .*\n| .*\n| .*\n)*)?) userdata:\n(?: .*\n| .*\n)+", r"\1", s, flags=re.M) s = re.sub(r"^# --- Backup classification[^\n]*\n^backup:\n(?=\n|#|[a-z])", "", s, flags=re.M) # drop data_paths (top level only; the i18n.en copy is display text and names no bind) s = re.sub(r"^# --- Customer-facing folder annotation[^\n]*\n^data_paths:\n(?: .*\n)+", "", s, flags=re.M) if s != before: open(p, "w").write(s) return f"{app}: .felhom.yml backup.userdata/data_paths removed" return f"{app}: .felhom.yml unchanged" for app in ("sonarr", "radarr", "qbittorrent", "transmission", "plex", "jellyfin"): print(compose(app)) print(felhom_yml(app))