Files
felhom.eu/documentation/audits/n100-lived-in-2026-10-15/tools/install_media.py
T
admin 06e7cf8c3f
gates / gates (push) Successful in 6m59s
The N100 becomes the lived-in box (09 §3 decision 254): media apps installed and wired through the dashboard; films proven never off-site; the USB split NOT done (it is the whole-guest backup target)
- 09 §3 decision 254 (operator decisions 240/243 written down); target-selection: a "lived-in" tier, Tier 0 = demo-hp
  alone; nodes.md role + drive; 11-os-updates ring 0 wording; CLAUDE.md + docs.md: the diary.
- documentation/lived-in/n100.md (new diary); audits/n100-lived-in-2026-10-15/ (state, drive read, Part C capture sets,
  installs, wiring, stranger probes, demo-hp clean-up, the operator's SHEET.md).
- Register: R-1062 (the split, operator decides), R-1063 (decommission half-state on an agent refusal), R-1064 (path
  removal silent about cross-drive copies), R-1065 (a claimed Plex gets no libraries until a wiring run).
- demo-hp: /mnt/sizing-bench removed through the dashboard; the hub shows demo-hp OK (14:56 UTC).

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-15 16:59:15 +02:00

27 lines
1.7 KiB
Python

"""install_media.py — Part D1: qBittorrent, Prowlarr, Sonarr, Radarr through the dashboard's deploy call, with the fields
the wizard sends (no HDD_PATH for a media app — the box picks its drive). Admins and indexers are the operator's."""
import json, os, time, n100
w = n100.w
w.login()
for app in ("qbittorrent", "prowlarr", "sonarr", "radarr"):
if w.stack(app).get("deployed"):
print(f"{app}: already installed — skipped"); continue
c, d = w.ctl("GET", f"/api/stacks/{app}/deploy-fields")
fields = (((d.get("data") or {}).get("metadata") or {}).get("deploy_fields")) or []
sub = next((f.get("default") for f in fields if f.get("env_var") == "SUBDOMAIN"), None) or app
values = {"DOMAIN": w.DOMAIN, "SUBDOMAIN": sub}
if any(f.get("env_var") == "QBT_ADMIN_PASSWORD" for f in fields):
values["QBT_ADMIN_PASSWORD"] = open(os.path.expanduser("~/.felhom-demofelhom/qbittorrent-admin")).read().strip()
t0 = time.time()
code, d = w.ctl("POST", f"/api/stacks/{app}/deploy", {"values": values})
print(f"{time.strftime('%H:%M:%S')} {app}: deploy (fields sent: {sorted(values)}) -> {code} {json.dumps(d, ensure_ascii=False)[:120]}")
for _ in range(120):
time.sleep(5)
st = w.stack(app)
if st.get("deployed") and (st.get("app_config") or {}).get("pinned_images") and st.get("state") in ("running", "unhealthy"):
break
st = w.stack(app)
hdd = w.guest(f"grep -E '^\\s*HDD_PATH:' /opt/docker/stacks/{app}/app.yaml | head -1").strip()
print(f" {app}: state={st.get('state')} after {time.time()-t0:.0f}s; pinned={(st.get('app_config') or {}).get('pinned_images')}; {hdd}")
print(w.guest("docker ps --format '{{.Names}} {{.Status}}' | sort"))