#!/usr/bin/env python3 """r773_live.py — R-773 on 9202 through the product's own endpoints: install Karakeep (the household passes the setup gate and makes the first account — the fixture), see sign-up closed, take the app's backup with the night chain's debug action (the sanctioned by-day trigger), REMOVE keeping backups, press restore, and ask as a STRANGER (no session, no gate cookie, through traefik) whether sign-up is closed again. Env: SC (0600 scratch with .ctlpw), EV. Secrets never printed.""" import json, os, subprocess, sys, time sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts") import box_walk as w import upgrade_fixtures_box as fixtures APP, SUB = "karakeep", "bookmarks" HOST = f"{SUB}.{w.DOMAIN}" os.makedirs(f"{w.EV}", exist_ok=True) log = open(f"{w.EV}/r773-live.txt", "a", buffering=1) def say(*a): w.say(*a); log.write(" ".join(map(str, a)) + "\n") def stranger(): def c(*a): r = subprocess.run(["curl", "-sk", "--max-time", "10", "-o", "/dev/null", "-w", "%{http_code}", "-H", f"Host: {HOST}"] + list(a), capture_output=True, text=True) return r.stdout.strip() body = json.dumps({"0": {"json": {"name": "stranger", "email": "stranger@example.com", "password": "Stranger-pass-123", "confirmPassword": "Stranger-pass-123"}}}) return {"GET /signup": c(f"{w.BASE}/signup"), "POST users.create": c("-X", "POST", "-H", "Content-Type: application/json", "--data", body, f"{w.BASE}/api/trpc/users.create?batch=1")} def record(): out = w.guest(f"grep -A8 '^setup_gate:' /opt/docker/stacks/{APP}/app.yaml 2>/dev/null; echo ---; " f"ls /opt/docker/stacks/traefik/dynamic/ | grep -E 'signup-block-{APP}|setup-gate-{APP}' || echo 'no block/gate file'") return " | ".join(l.strip() for l in out.strip().splitlines() if l.strip()) w.login() say(f"##### R-773 live on 9202 — controller {w.guest('docker inspect felhom-controller --format {{.Config.Image}}').strip()} ({time.strftime('%FT%TZ', time.gmtime())})") ok = w.deploy(APP, SUB) say("deploy ->", ok) tok = fixtures.FIXTURES[APP].seed(w, SUB, say) # the household: through the gate, the first account say("seed (first account) ->", "ok" if tok else "FAILED") for _ in range(60): # the gate opens by its probe → the block goes up if "signup-block" in record(): break time.sleep(5) say("BEFORE remove — lock record + files:", record()) say("BEFORE remove — a stranger:", stranger()) code, d = w.ctl("POST", "/api/debug/backup/night-chain") say("night chain (debug action) ->", code, str(d)[:160]) for _ in range(120): if w.snapshots(APP): break time.sleep(10) say("restore points offered:", [(s.get("id") or s.get("snapshot_id"), s.get("time") or s.get("created")) for s in w.snapshots(APP)][:3]) for _ in range(90): # let the chain finish before the remove out = w.guest("docker logs --since 30m felhom-controller 2>&1 | grep -c 'night-chain\\] update leg: done\\|night-chain.*chain done\\|night-chain\\] manual run.*done'").strip() if out not in ("", "0"): break time.sleep(10) 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(f"remove KEEPING backups -> {code} {str(d)[:200]}") time.sleep(8) say("AFTER remove — lock record + files:", record()) r = w.restore(APP) say("restore:", {k: r.get(k) for k in ("ok", "snapshot_id", "http", "seconds", "state_after", "hold_after", "why")}) w.wait_app(SUB, "/", tries=40) say("AFTER remove + restore — lock record + files:", record()) say("AFTER remove + restore — a stranger:", stranger()) say("the household's data back:", fixtures.FIXTURES[APP].verify(w, SUB, tok, say) if tok else "no seed") say("controller log:", w.guest("docker logs --since 40m felhom-controller 2>&1 | grep -E 'karakeep: (restored after a removal|the household|sign-up)|signup' | tail -6"))