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.
46 lines
2.4 KiB
Python
46 lines
2.4 KiB
Python
#!/opt/pw/bin/python
|
|
"""bind.py — the household's self-bind page, in real Chromium (rehearsal 2026-10-20, B2).
|
|
|
|
Env (handed in by brun.sh, never on a command line): BIND_TOKEN, PAIRING, PASS.
|
|
--typo : change ONE character of PASS before sending (a household's typo; the value is never printed)
|
|
Screenshots are taken BEFORE the fields are filled and AFTER the answer page loads — the passphrase field
|
|
is type=text, so a filled-form screenshot would show it. Prints the answer page's visible text.
|
|
usage: bind.py OUTPREFIX [--typo] [--size phone|desktop] [--lang hu|en] [--get-only]
|
|
"""
|
|
import os, re, sys
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
out = sys.argv[1]; args = sys.argv[2:]
|
|
size = args[args.index("--size") + 1] if "--size" in args else "phone"
|
|
lang = args[args.index("--lang") + 1] if "--lang" in args else "hu"
|
|
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]
|
|
loc = {"hu": "hu-HU", "en": "en-US"}[lang]
|
|
url = "https://hub.felhom.eu/bind/" + os.environ["BIND_TOKEN"]
|
|
with sync_playwright() as p:
|
|
b = p.chromium.launch(executable_path="/usr/bin/chromium", args=["--no-sandbox"])
|
|
ctx = b.new_context(locale=loc, extra_http_headers={"Accept-Language": loc}, timezone_id="Europe/Budapest", **vp)
|
|
pg = ctx.new_page()
|
|
r = pg.goto(url, wait_until="networkidle")
|
|
pg.screenshot(path=f"{out}-1-open-{size}-{lang}.png", full_page=True)
|
|
print("GET", r.status, pg.title())
|
|
body = pg.inner_text("body")
|
|
print(re.sub(r"\s+", " ", body)[:600])
|
|
if "--get-only" in args or not pg.locator("#pairing_code").count():
|
|
b.close(); sys.exit(0)
|
|
pw = os.environ["PASS"]
|
|
if "--typo" in args: # swap one inner letter for its neighbour in the alphabet
|
|
i = len(pw) // 2
|
|
while not pw[i].isalpha():
|
|
i += 1
|
|
pw = pw[:i] + ("b" if pw[i] != "b" else "c") + pw[i + 1:]
|
|
pg.fill("#pairing_code", os.environ["PAIRING"])
|
|
pg.fill("#passphrase", pw)
|
|
with pg.expect_navigation(wait_until="networkidle"):
|
|
pg.click("button[type=submit]")
|
|
# blank both inputs before the screenshot, in case a re-rendered form echoes a value
|
|
pg.evaluate("document.querySelectorAll('input').forEach(e => e.value = '')")
|
|
pg.screenshot(path=f"{out}-2-answer-{size}-{lang}.png", full_page=True)
|
|
print("POST answer:", re.sub(r"\s+", " ", pg.inner_text("body"))[:600])
|
|
b.close()
|