gates / gates (push) Successful in 7m46s
Two test drives enrolled through the dashboard; no test media in any backup tier (A6, three proofs); Tester 1 follows a credential-free local mirror of the drill catalog (R-992 worked around a third way). The live-fix task may start.
85 lines
4.5 KiB
Python
85 lines
4.5 KiB
Python
#!/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/<drive>/userdata/media -> /mnt/felhom-drives/<drive>/userdata/media (rw; :ro for Plex/Jellyfin)
|
|
|
|
for <drive> 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))
|