79 lines
3.3 KiB
Python
79 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""ceremony.py — one escrow ceremony on Tester 1 through the wizard's own endpoints (hub 0.175.0 check, 2026-10-29).
|
|
The recovery code lives in this process's memory only: never printed, never written, dropped at the end. The dashboard
|
|
password is read from the 0600 file and handed to curl on stdin (never on a command line)."""
|
|
import json, os, subprocess, sys, time
|
|
|
|
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
|
|
SCR = "/tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/0b76c4d7-9b39-4e4e-869d-d0d2c6006331/scratchpad"
|
|
os.environ.setdefault("SC", f"{SCR}/rt-tester1")
|
|
os.environ.setdefault("TARGET", "tester-1")
|
|
import box_walk as w
|
|
|
|
w.login()
|
|
sess = open(f"{w.SC}/sess{os.getpid()}.txt").read().strip()
|
|
csrf = open(f"{w.SC}/csrf{os.getpid()}.txt").read().strip()
|
|
BASE_ARGS = ["curl", "-sk", "-H", w.HOSTHDR, "-H", f"Cookie: {sess}", "-H", f"X-CSRF-Token: {csrf}"]
|
|
|
|
|
|
def call(method, path, form_stdin=None, body=None):
|
|
args = BASE_ARGS + ["-X", method, "-w", "\n%{http_code}"]
|
|
inp = None
|
|
if form_stdin is not None:
|
|
args += ["--data-urlencode", "password@-"]
|
|
inp = form_stdin
|
|
if body is not None:
|
|
args += ["-H", "Content-Type: application/json", "--data-binary", "@-"]
|
|
inp = json.dumps(body)
|
|
r = subprocess.run(args + [f"{w.BASE}{path}"], input=inp, capture_output=True, text=True, timeout=120)
|
|
out, _, code = r.stdout.rpartition("\n")
|
|
try:
|
|
return code, json.loads(out)
|
|
except Exception:
|
|
return code, {"raw": out[:200]}
|
|
|
|
|
|
def safe(d):
|
|
"""A copy with the code removed — the only form that is printed."""
|
|
d = json.loads(json.dumps(d))
|
|
if isinstance(d.get("data"), dict):
|
|
d["data"].pop("recovery_code", None)
|
|
return d
|
|
|
|
|
|
t0 = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
code, d = call("GET", "/api/escrow/preflight")
|
|
w.say(f"preflight → {code} {json.dumps(d)[:600]}")
|
|
pw = open(f"{w.SC}/.ctlpw").read().strip()
|
|
code, d = call("POST", "/api/escrow/start", form_stdin=pw)
|
|
pw = None
|
|
w.say(f"start → {code} {json.dumps(safe(d))[:300]}")
|
|
if code != "200":
|
|
sys.exit(1)
|
|
for _ in range(120):
|
|
time.sleep(3)
|
|
code, d = call("GET", "/api/escrow/status")
|
|
ph = (d.get("data") or {}).get("phase")
|
|
if ph not in ("running", "queued", "starting", None) or (d.get("data") or {}).get("claimable"):
|
|
break
|
|
w.say(f"status → {code} {json.dumps(safe(d))[:400]}")
|
|
code, d = call("POST", "/api/escrow/claim")
|
|
rc = (d.get("data") or {}).get("recovery_code") or ""
|
|
pos = (d.get("data") or {}).get("verify_positions") or []
|
|
w.say(f"claim → {code}; code received: {bool(rc)} ({len(rc.split('-'))} words); confirm_required={(d.get('data') or {}).get('confirm_required')} positions={pos}")
|
|
if rc and pos:
|
|
words = rc.split("-")
|
|
picked = [words[pos[0] - 1], words[pos[1] - 1]]
|
|
rc, words = None, None
|
|
code, d = call("POST", "/api/escrow/confirm", body={"words": picked})
|
|
picked = None
|
|
w.say(f"confirm → {code} {json.dumps(safe(d))[:400]}")
|
|
rc = None
|
|
for _ in range(40):
|
|
time.sleep(15)
|
|
st = w.guest("docker logs --since " + t0 + " felhom-controller 2>&1 | grep -iE 'escrow' | grep -viE 'recovery_code' | tail -12")
|
|
if "escrowed" in st or "auto-confirm" in st.lower():
|
|
break
|
|
w.say("controller escrow lines:\n" + st)
|
|
open(f"{SCR}/d63/ev/ceremony.log", "a").write("\n".join(w.LOG) + "\n")
|