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.
81 lines
3.8 KiB
Python
81 lines
3.8 KiB
Python
#!/opt/pw/bin/python
|
|
"""escrow.py — the household's recovery code ceremony in real Chromium (rehearsal 2026-10-20, B2).
|
|
|
|
Env: PW (the dashboard password). The recovery code is read off the page into /root/work/rc (0600) — never printed —
|
|
and DooPlex moves it into `sec` and shreds the file. Before every screenshot the code block is replaced by black
|
|
bars (the page keeps the code in a JS closure, so the check still works).
|
|
usage: escrow.py OUTPREFIX [--size phone|desktop]
|
|
"""
|
|
import os, re, sys, time
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
out = sys.argv[1]
|
|
size = sys.argv[sys.argv.index("--size") + 1] if "--size" in sys.argv else "phone"
|
|
vp = {"phone": dict(viewport={"width": 390, "height": 844}, device_scale_factor=2, is_mobile=True, has_touch=True),
|
|
"desktop": dict(viewport={"width": 1366, "height": 768})}[size]
|
|
T0 = time.time()
|
|
|
|
|
|
def say(*a):
|
|
print(f"+{time.time() - T0:6.1f}s", *a, flush=True)
|
|
|
|
|
|
def shot(pg, name):
|
|
pg.evaluate("""() => { const b = document.getElementById('code-block'); if (b && b.textContent) b.textContent = '█████-█████-█████-█████-█████-█████-█████-█████-█████-█████';
|
|
document.querySelectorAll('input').forEach(e => { if (e.type !== 'checkbox') e.value = ''; }); }""")
|
|
pg.screenshot(path=f"{out}-{name}-{size}-hu.png", full_page=True)
|
|
|
|
|
|
with sync_playwright() as p:
|
|
b = p.chromium.launch(executable_path="/usr/bin/chromium", args=["--no-sandbox"])
|
|
ctx = b.new_context(locale="hu-HU", timezone_id="Europe/Budapest", ignore_https_errors=True,
|
|
storage_state="/root/work/state-dash.json", **vp)
|
|
pg = ctx.new_page()
|
|
pg.goto("https://felhom.tester4.test/backup/escrow", wait_until="networkidle")
|
|
pg.wait_for_selector("#warn-card", state="visible", timeout=60000)
|
|
pg.fill("#reauth-password", os.environ["PW"])
|
|
pg.click("#start-btn")
|
|
say("start pressed")
|
|
pg.wait_for_timeout(1500)
|
|
shot(pg, "2-progress")
|
|
try:
|
|
pg.wait_for_selector("#reveal-card, #void-card", state="visible", timeout=300000)
|
|
except Exception:
|
|
say("no reveal within 300 s:", re.sub(r"\s+", " ", pg.inner_text("#progress-card"))[:400])
|
|
shot(pg, "2b-stuck")
|
|
sys.exit(2)
|
|
if pg.is_visible("#void-card"):
|
|
say("VOID card:", re.sub(r"\s+", " ", pg.inner_text("#void-card"))[:400])
|
|
shot(pg, "3-void")
|
|
sys.exit(3)
|
|
say("reveal card shown")
|
|
shot(pg, "3-reveal-gate")
|
|
pg.click("#reveal-btn")
|
|
pg.wait_for_selector("#reveal-shown", state="visible", timeout=60000)
|
|
code = pg.inner_text("#code-block").strip()
|
|
fd = os.open("/root/work/rc", os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
os.write(fd, code.encode())
|
|
os.close(fd)
|
|
words = code.split("-")
|
|
say(f"code read off the page: {len(words)} words, {len(code)} characters (stored 0600, not printed)")
|
|
label = pg.inner_text("#verify-label")
|
|
pos = [int(x) for x in re.findall(r"\d+", label)][:2]
|
|
say("the page asks for words at positions", pos, "| label:", label)
|
|
shot(pg, "4-code-shown-REDACTED")
|
|
pg.fill("#verify-w1", words[pos[0] - 1])
|
|
pg.fill("#verify-w2", words[pos[1] - 1])
|
|
pg.click("#verify-btn")
|
|
pg.wait_for_timeout(4000)
|
|
if pg.is_visible("#verify-error"):
|
|
say("verify error:", pg.inner_text("#verify-error"))
|
|
pg.wait_for_selector("#finish-block", state="visible", timeout=60000)
|
|
say("words accepted; finish block:", re.sub(r"\s+", " ", pg.inner_text("#finish-block"))[:400])
|
|
shot(pg, "5-words-ok")
|
|
pg.check("#finish-check")
|
|
pg.click("#finish-btn")
|
|
pg.wait_for_load_state("networkidle")
|
|
pg.wait_for_timeout(3000)
|
|
say("after finish:", pg.url, re.sub(r"\s+", " ", pg.inner_text("main") if pg.locator("main").count() else pg.inner_text("body"))[:900])
|
|
shot(pg, "6-after-finish")
|
|
b.close()
|