1749e73e50
gates / gates (push) Successful in 27s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
122 lines
5.1 KiB
Python
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()
|