Files
felhom.eu/documentation/audits/gate-rollout-2026-09-29/B/ro.py
T

122 lines
5.1 KiB
Python

# Rollout harness (2026-09-29 afternoon, 9202, controller 0.281.0, drill catalog). Evidence, not product.
# python3 ro.py deploy app... fresh installs through the product; the gate file + record right after
# python3 ro.py stranger app... a stranger: API-style request and a browser request
# python3 ro.py household app... the household (dashboard session) opens the app through the gate
# python3 ro.py probe app URL read a candidate status URL from inside the box (the controller's own network)
# python3 ro.py state app... gate record, gate file, sign-up block file
# python3 ro.py press app the household's "Done" press (the product's endpoint)
# python3 ro.py after app... a stranger after the gate opened
# python3 ro.py remove app...
# Household test passwords are generated per run and kept in ro_creds.json (scratch, 0600, deleted at teardown).
import json, os, secrets, sys, time
sys.path.insert(0, '/mnt/5_hdd/felhom.eu/git/felhom.eu/documentation/audits/pg-calcom-claper-2026-09-28/tools')
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import walk as w
from browser import Browser, hopstr
D = "enkisfelhom.hu"
PW = open(os.path.join(w.SC, ".ctlpw")).read().strip()
JAR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ro_jar.json")
CREDS = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ro_creds.json")
def sub(app):
return "r-" + app
def host(app):
return f"{sub(app)}.{D}"
def household():
b = Browser("household")
if os.path.exists(JAR):
b.jar = json.load(open(JAR))
if "felhom." + D not in b.jar:
b.login_dashboard(D, PW)
return b
def save(b):
old = os.umask(0o077)
json.dump(b.jar, open(JAR, "w"))
os.umask(old)
def creds(app):
c = json.load(open(CREDS)) if os.path.exists(CREDS) else {}
if app not in c:
c[app] = {"user": "family", "email": "family@spike.hu", "pw": "Hh" + secrets.token_hex(10) + "7"}
old = os.umask(0o077)
json.dump(c, open(CREDS, "w"))
os.umask(old)
return c[app]
def gstate(app):
f = w.guest(f"for f in setup-gate-{app}.yml signup-block-{app}.yml; do test -f /opt/docker/stacks/traefik/dynamic/$f && echo -n \"$f \"; done; echo").strip()
rec = (w.stack(app).get("app_config") or {}).get("setup_gate")
return f"files=[{f}] record={rec}"
def main():
cmd, args = sys.argv[1], sys.argv[2:]
w.login()
if cmd == "deploy":
for app in args:
v = w.deploy_values(app, sub(app))
code, d = w.ctl("POST", f"/api/stacks/{app}/deploy", {"values": v, "kept_data": "fresh"})
w.say(app, "deploy ->", code, "" if code == "202" else str(d)[:200])
for app in args:
st = {}
for _ in range(120):
st = w.stack(app)
if st.get("deployed") and (st.get("app_config") or {}).get("pinned_images") and st.get("state") in ("running", "unhealthy", "degraded"):
break
time.sleep(5)
w.say(app, "state", st.get("state"), "|", gstate(app))
elif cmd == "stranger":
for app in args:
s = Browser("stranger")
st, body, hops = s.req(f"https://{host(app)}/")
gp = "Jelentkezz be a Felhom" in body or "waiting for its first setup" in body
st2, body2, _ = s.req(f"https://{host(app)}/api/x", "POST", body={"a": 1}, accept="application/json")
w.say(app, f"STRANGER browser -> {st} {hopstr(hops)} gate-page={gp} | API POST -> {st2} {body2[:52]!r}")
elif cmd == "household":
b = household()
for app in args:
t0 = time.time()
st, body, hops = b.req(f"https://{host(app)}/")
gp = "Jelentkezz be a Felhom" in body or "waiting for its first setup" in body
title = body[body.find("<title>") + 7: body.find("</title>")][:40] if "<title>" in body else body[:40].replace("\n", " ")
w.say(app, f"HOUSEHOLD -> {st} in {time.time() - t0:.2f}s | {hopstr(hops)[-90:]} | gate page {gp} | app says {title!r}")
save(b)
elif cmd == "probe":
app, url = args[0], args[1]
out = w.guest(f"docker exec felhom-controller curl -s -m 8 {url} | head -c 400")
w.say(app, "PROBE", url, "->", out.strip()[:400])
elif cmd == "state":
for app in args:
w.say(app, gstate(app))
elif cmd == "press":
app = args[0]
code, d = w.ctl("POST", f"/apps/{app}/setup-gate/open", {})
w.say(app, "PRESS 'Done' ->", code, str(d)[:120])
time.sleep(2)
w.say(app, gstate(app))
elif cmd == "after":
for app in args:
s = Browser("stranger")
st, body, hops = s.req(f"https://{host(app)}/")
gp = "Jelentkezz be a Felhom" in body or "waiting for its first setup" in body
w.say(app, f"AFTER, stranger browser -> {st} {hopstr(hops)[-80:]} | gate page {gp} | len {len(body)}")
elif cmd == "remove":
for app in args:
w.remove(app)
else:
sys.exit("unknown command")
if __name__ == "__main__":
main()