Files
felhom.eu/documentation/audits/night-2026-10-28/tools/h5_live.py
T

65 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""h5_live.py seed | prove — R-1256 H5 live proof on scratch 9202 (night 2026-10-28), through the front doors.
seed : install vikunja, seed project C1 through its API, put a 1 GiB ballast file in its second volume (vikunja_db, so
the restore's second volume takes seconds — the put-back then comes long after the first volume's refusal).
The night backup (02:30) then makes the restore point.
prove: seed project C2 (newer than the backup); a created-only container holds the FIRST volume (vikunja_data) so
`docker volume rm -f` fails; a watcher removes it as soon as the controller logs the refusal; press the
household's restore. Want: the first volume is NOT written into (log), the restore fails, the put-back brings the
live data back (C1 AND C2 read back), the app runs, NO hold.
State (passwords) stays in the scratchpad, never printed."""
import json, os, sys, time
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
os.environ.setdefault("SC", "/tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/0b76c4d7-9b39-4e4e-869d-d0d2c6006331/scratchpad/rt-9202")
os.environ.setdefault("TARGET", "9202")
import box_walk as w
import upgrade_fixtures_box as fb
EV = "/tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/0b76c4d7-9b39-4e4e-869d-d0d2c6006331/scratchpad/night/p1/h5"
STATE = os.path.join(EV, ".state.json")
fx = fb.FIXTURES["vikunja"]
SUB = getattr(fx, "sub", "vikunja")
APP = "vikunja"
w.login()
if sys.argv[1] == "seed":
assert w.deploy(APP, SUB), "deploy failed"
t1 = fx.seed(w, SUB, w.say)
assert t1, "seed C1 failed"
vols = w.guest("docker volume ls --format '{{.Name}}' | grep -E '^vikunja' | sort").split()
w.say(f"volumes: {vols}")
dbv = [v for v in vols if v.endswith("_db")][0]
w.say(w.guest(f"docker run --rm -v {dbv}:/x alpine sh -c 'dd if=/dev/urandom of=/x/r1256-ballast.bin bs=1M count=1024 2>&1 | tail -1; ls -la /x'", timeout=900))
json.dump({"c1": t1, "vols": vols}, open(STATE, "w")); os.chmod(STATE, 0o600)
w.say(f"C1 readback: {fx.verify(w, SUB, t1, w.say)}")
elif sys.argv[1] == "prove":
st = json.load(open(STATE))
snaps = w.snapshots(APP)
w.say(f"snapshots offered: {json.dumps(snaps)[:400]}")
assert snaps, "no restore point yet"
t2 = fx.seed(w, SUB, w.say) # C2: newer than the backup — the put-back must bring it back
assert t2, "seed C2 failed"
st["c2"] = t2; json.dump(st, open(STATE, "w"))
datav = [v for v in st["vols"] if v.endswith("_data")][0]
w.say("blocker: " + w.guest(f"docker rm -f r1256-blocker >/dev/null 2>&1; docker create --name r1256-blocker -v {datav}:/x alpine true"))
w.say("rm -f with the blocker (premise): " + w.guest(f"docker volume rm -f {datav} 2>&1; echo rc=$?"))
w.guest("rm -f /root/r1256-watch.log; nohup sh -c 'timeout 1800 docker logs -f --since 1s felhom-controller 2>&1 | "
f"grep -m1 \"Failed to remove volume {datav} before the restore\" > /root/r1256-watch.log; date -u +%T.%N >> /root/r1256-watch.log; "
"docker rm -f r1256-blocker >> /root/r1256-watch.log 2>&1; date -u +%T.%N >> /root/r1256-watch.log' >/dev/null 2>&1 &")
time.sleep(2)
t0 = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
res = w.restore(APP)
w.say(f"restore result: {json.dumps(res)[:1500]}")
w.say("watcher: " + w.guest("cat /root/r1256-watch.log"))
w.say("controller log: " + w.guest(f"docker logs --since {t0} felhom-controller 2>&1 | grep -E 'vikunja|R-1256|put-back|put back|Failed to remove' | tail -40"))
st_now = w.stack(APP)
w.say(f"after: state={st_now.get('state')} hold={st_now.get('hold_reason')!r}")
w.say(f"C1 readback: {fx.verify(w, SUB, st['c1'], w.say)}")
w.say(f"C2 readback: {fx.verify(w, SUB, t2, w.say)}")
code, d = w.ctl("GET", "/api/backup/restore-status")
w.say(f"restore record: {json.dumps(d)[:800]}")
w.say("blocker left: " + w.guest("docker ps -a --filter name=r1256-blocker --format '{{.Names}}'"))
w.say("copies: " + w.guest("find /mnt/felhom-drives/scratch_hdd/backups/restore-undo -maxdepth 2"))
elif sys.argv[1] == "teardown":
w.guest("docker rm -f r1256-blocker >/dev/null 2>&1; rm -f /root/r1256-watch.log")
w.remove(APP)
open(os.path.join(EV, f"{sys.argv[1]}.log"), "a").write("\n".join(w.LOG) + "\n")