gates / gates (push) Successful in 13m17s
Every catalog app installed, used, backed up, removed keeping data, reinstalled, restored and removed with data on a fresh box from the public ISO (demo-hp VM 9360, Tester4), endpoint-level. Worst: a reinstall's capture rewrites the kept unit so its restore breaks the app (R-1269, P2), the restore still reports success (R-1270, P2), and „use my kept data" loaded another box's future-dated database (R-1271, P2). Drill catalog fixes for the morning list (n8n usage data; OpenGist 256M, proven by a cgroup probe). VM 9360 and LXC 9361 destroyed; hub host Tester4-aa9466 left for the operator. REPORT-catalog-sweep-2026-10-24.md.
82 lines
4.1 KiB
Python
82 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
"""unitprobe.py — F10 of the sweep: does a reinstall's own capture rewrite the unit a „keep backups" remove kept?
|
|
|
|
The household's order, endpoint-level: install, one item, „Mentés most", remove KEEPING data and backups, install the
|
|
app again (a fresh install — no kept-data question for a volume app), WAIT for the controller's next capture of the
|
|
new install, then „Visszaállítás" of the kept unit. Observed without reading any secret: the unit's manifest (its data
|
|
time, its definition checksums) before the reinstall and after the capture, the controller's own lines, the app's state
|
|
and its database log after the restore, and the item through the front door.
|
|
|
|
usage: SC=… SWEEP_EV=… DRIVE_ROOT=… python3 unitprobe.py <app> (evidence: <SWEEP_EV>/_unitprobe/<app>/)
|
|
"""
|
|
import json, os, sys, time
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
import sweep as sw # noqa: E402
|
|
|
|
w, fxm = sw.w, sw.fxm
|
|
UNIT = "/mnt/sys_drive/felhom-data/backups/primary/{app}"
|
|
|
|
|
|
def manifest(app):
|
|
out = sw.g(f"cd {UNIT.format(app=app)} 2>/dev/null && ls -la --time-style=+%FT%T . volume-dumps db-dumps 2>/dev/null; "
|
|
f"for f in manifest.json recovery.json unit.json; do [ -f $f ] && echo \"== $f\" && cat $f; done")
|
|
return out
|
|
|
|
|
|
def main(app):
|
|
d = os.path.join(sw.EV, "_unitprobe", app)
|
|
os.makedirs(d, exist_ok=True)
|
|
log = open(os.path.join(d, "walk.log"), "a")
|
|
|
|
def say(*a):
|
|
line = time.strftime("%H:%M:%SZ", time.gmtime()) + " " + " ".join(str(x) for x in a)
|
|
print(line, flush=True)
|
|
log.write(sw.scrub(line) + "\n")
|
|
log.flush()
|
|
|
|
w.say = say
|
|
fx = fxm.FIXTURES[app]
|
|
sub = getattr(fx, "sub", app)
|
|
rec = {"app": app}
|
|
w.login()
|
|
try:
|
|
rec["install"] = w.deploy(app, sub) and w.wait_app(sub, "/", tries=90, delay=5)
|
|
tok = fx.seed(w, sub, say)
|
|
rec["seed"] = bool(tok)
|
|
sw.backup_now(rec, say)
|
|
open(os.path.join(d, "unit-1-after-backup.txt"), "w").write(sw.scrub(manifest(app)))
|
|
sw.remove(app, keep=True, say=say)
|
|
open(os.path.join(d, "unit-2-after-keep-remove.txt"), "w").write(sw.scrub(manifest(app)))
|
|
t_re = time.time()
|
|
rec["reinstall"] = w.deploy(app, sub, dict(w.GENERATED.get(app) or {}))
|
|
# wait for the controller's next capture of the NEW install (the line it logs), at most 10 minutes
|
|
captured = ""
|
|
for _ in range(60):
|
|
time.sleep(10)
|
|
captured = sw.g(f"docker logs --since {int(t_re)} felhom-controller 2>&1 | grep 'Recovery unit captured for {app} '")
|
|
if captured.strip():
|
|
break
|
|
rec["capture_after_reinstall"] = captured.strip()[:300]
|
|
say(" capture after the reinstall: " + (captured.strip()[:200] or "NONE within 10 min"))
|
|
open(os.path.join(d, "unit-3-after-reinstall-capture.txt"), "w").write(sw.scrub(manifest(app)))
|
|
sw.restore_unit(app, rec, say)
|
|
time.sleep(20)
|
|
rec["state_after_restore"] = w.stack(app).get("state")
|
|
rec["item_after_restore"] = bool(tok) and bool(fx.verify(w, sub, tok, say))
|
|
rec["db_auth_errors_after_restore"] = sw.g(
|
|
f"for c in $(docker ps -a --filter label=com.docker.compose.project={app} --format '{{{{.Names}}}}'); do "
|
|
f"docker logs --since 5m $c 2>&1 | grep -ciE 'password authentication failed|access denied for user' | sed \"s/^/$c: /\"; done").strip()
|
|
rec["controller_restore_lines"] = sw.scrub(sw.g(
|
|
f"docker logs --since {int(t_re)} felhom-controller 2>&1 | grep -E '{app}' | grep -iE 'restor|captured|health|come from the backup' | cut -c1-240"))
|
|
say(" state after restore: %s, item: %s, db auth errors: %s" % (rec["state_after_restore"], rec["item_after_restore"],
|
|
rec["db_auth_errors_after_restore"]))
|
|
finally:
|
|
sw.remove(app, keep=False, say=say)
|
|
rec["after_remove"] = sw.leftovers(app, sub)
|
|
json.dump(rec, open(os.path.join(d, "record.json"), "w"), ensure_ascii=False, indent=1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv[1])
|