#!/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 (evidence: /_unitprobe//) """ 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])