Files
felhom.eu/documentation/audits/burndown-apps-2026-10-16/tools/romm_saves.py
T
admin 836d64efa3
gates / gates (push) Successful in 6m52s
Apps burndown (controller 0.321.0 released): R-1097, R-1102, R-1101, R-1095, R-1098 closed; R-1103 opened; build-hub.sh 1.25.0
build-hub.sh refuses an existing tag and an unanswered registry (test_build_hub_push_once.sh, red-proved).
Register 179 -> 175. R-1067 stays BLOCKED: Tester-2 reports 0.292.0. Design notes in 02 and 07; STATUS;
evidence audits/burndown-apps-2026-10-16/; REPORT-burndown-apps-2026-10-16.md.

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-16 17:27:57 +02:00

60 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""romm_saves.py — R-1097 proof on demo-hp 9201 through the product's endpoints (box_walk helpers, TARGET=9201).
Sync the catalog, restart RomM (the applied compose gains romm_assets), write a save where RomM writes saves, run the
by-day night chain (the sanctioned backup trigger), remove RomM KEEPING its backups, restore from the backups page,
and read the save back. Env: SC (0600 scratch with .ctlpw), EV. The save is a dummy file; no secret is printed."""
import os, sys, time, hashlib
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
import box_walk as w
APP = "romm"
SAVE_DIR = "/romm/assets/romm/users/1/saves/gba/r1097-proof"
SAVE = SAVE_DIR + "/r1097-proof.sav"
log = open(os.path.join(w.EV, "romm-saves.txt"), "a", buffering=1)
def say(*a):
w.say(*a); log.write(" ".join(map(str, a)) + "\n")
def mounts():
return w.guest("docker inspect romm --format '{{range .Mounts}}{{.Type}} {{.Name}} -> {{.Destination}}{{println}}{{end}}' "
"| sed -E 's/volume [0-9a-f]{64}/volume <ANONYMOUS>/'").strip()
def save_state():
return w.guest(f"docker exec romm sh -c 'ls -la {SAVE_DIR} 2>&1; sha256sum {SAVE} 2>&1' | tail -3").strip()
w.login()
say("== 1. sync the catalog")
code, d = w.ctl("POST", "/api/sync"); say("sync ->", code, str(d)[:200])
say("applied compose names romm_assets:", w.guest("grep -c romm_assets /opt/docker/stacks/romm/docker-compose.yml").strip())
say("== 2. restart RomM (Újraindítás)")
code, d = w.ctl("POST", f"/api/stacks/{APP}/restart"); say("restart ->", code, str(d)[:160])
for _ in range(40):
time.sleep(5)
if w.stack(APP).get("state") == "running":
break
say("state:", w.stack(APP).get("state")); say("mounts after restart:\n" + mounts())
say("== 3. a save where RomM writes saves (dummy bytes, written with docker exec into the running container)")
w.guest(f"docker exec romm sh -c 'mkdir -p {SAVE_DIR} && head -c 32768 /dev/urandom > {SAVE}'")
before = save_state(); say(before)
say("== 4. the by-day backup (night chain)")
t0 = time.time(); code, d = w.ctl("POST", "/api/debug/backup/night-chain"); say("night-chain ->", code, str(d)[:160])
for _ in range(180):
time.sleep(10)
tail = w.guest("docker logs --since 60m felhom-controller 2>&1 | grep 'night-chain' | tail -1").strip()
if "finished in" in tail or "REFUSED" in tail:
break
say("night chain last line:", tail[-220:], f"after {round(time.time()-t0)}s")
say("romm volume dumps now:", w.guest("ls -la /mnt/felhom-drives/hdd_1/backups/*/romm/volume-dumps/ /mnt/felhom-drives/hdd_1/*/romm/recovery-unit/volume-dumps/ 2>/dev/null; find /mnt/felhom-drives/hdd_1 -name 'romm_romm_assets.tar' -newer /proc/1 2>/dev/null | head -5"))
say("== 5. remove RomM, KEEPING its backups")
w.ctl("POST", f"/api/stacks/{APP}/stop"); time.sleep(10)
code, d = w.ctl("POST", f"/api/stacks/{APP}/remove", {"remove_hdd_data": False, "remove_backups": False})
say("remove ->", code, str(d)[:200])
time.sleep(5)
say("volumes left:", w.guest("docker volume ls --format '{{.Name}}' | grep -i romm").strip() or "(none)")
say("== 6. restore from the backups page")
r = w.restore(APP); say("restore:", {k: r.get(k) for k in ("ok", "snapshot_id", "http", "seconds")})
for _ in range(60):
time.sleep(5)
if w.stack(APP).get("state") == "running":
break
say("state:", w.stack(APP).get("state")); say("mounts after restore:\n" + mounts())
after = save_state(); say(after)
same = before.split()[-2:] == after.split()[-2:]
say("== VERDICT: the save came back byte-identical:", same)