"""wr.py — website-refresh 2026-10-08: install ONE catalog app on scratch guest 9202 through the product, take its screenshots with headless Chrome inside 9202, copy the raw PNGs off the box, remove the app through the product, verify it is gone. python3 wr.py baseline python3 wr.py deploy python3 wr.py shoot [KEY=VAL ...] # secrets.json in the guest, 0600, removed after python3 wr.py fetch # one file off the guest python3 wr.py remove python3 wr.py verify Env: SC (0600 scratch with .ctlpw), EV (evidence dir). No secret is ever printed: the dashboard session and the invented demo account's password travel only inside files (SC on DooPlex, /root/wr-shots//secrets.json in the guest, removed by `shoot` when Chrome exits).""" import base64, json, os, secrets, sys, time sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import sw # noqa: E402 b = sw.b SC, EV = os.environ["SC"], os.environ["EV"] IMG = "ghcr.io/puppeteer/puppeteer:latest" TRAEFIK = "172.18.0.5" APPS = ["docmost", "homebox", "mealie", "recipe-importer", "sparkyfitness"] def log(app, *a): b.say(*a) os.makedirs(f"{EV}/{app}", exist_ok=True) with open(f"{EV}/{app}/run.log", "a") as f: f.write(" ".join(map(str, a)) + "\n") def secfile(app): p = f"{SC}/sec-{app}.json" if os.path.exists(p): return json.load(open(p)) d = {"user": "demo@example.com", "pw": "Demo-" + secrets.token_hex(10), "name": "Demo Csalad"} fd = os.open(p, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) os.write(fd, json.dumps(d).encode()); os.close(fd) return d def baseline(): b.login() c, d = b.ctl("GET", "/api/stacks") dep = [(s.get("name"), s.get("state")) for s in (d.get("data") or []) if isinstance(s, dict) and s.get("deployed")] out = b.guest("df -h / | tail -1; docker ps --format '{{.Names}} {{.Image}}'; " "docker volume ls --format '{{.Name}}' | grep -E 'docmost|homebox|mealie|recipe|sparky' || echo 'no volumes of the five'; " + "; ".join(f"echo {a}: $(ls -A /opt/docker/stacks/{a})" for a in APPS) + f"; docker inspect -f '{{{{.RepoDigests}}}} {{{{.Created}}}}' {IMG}") txt = f"deployed (dashboard API): {dep}\n{out}" os.makedirs(EV, exist_ok=True) open(f"{EV}/baseline-9202.txt", "w").write(txt + "\n") print(txt) def deploy(app, sub): b.login() ok = b.deploy(app, sub) if b.GENERATED.get(app): p = f"{SC}/gen-{app}.json" fd = os.open(p, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) os.write(fd, json.dumps(b.GENERATED[app]).encode()); os.close(fd) log(app, f"generated deploy secrets kept in scratch (keys {list(b.GENERATED[app])})") log(app, f"deploy {app} -> {ok}") if ok: log(app, f"app answers: {b.wait_app(sub)}") def shoot(app, sub, script, extra): b.login() sess = open(f"{SC}/sess{os.getpid()}.txt").read().strip().split("=", 1)[1] sec = dict(secfile(app)) gp = f"{SC}/gen-{app}.json" if os.path.exists(gp): sec["gen"] = json.load(open(gp)) sec["session"] = sess for kv in extra: k, v = kv.split("=", 1); sec[k] = v js = open(script).read() gdir = f"/root/wr-shots/{app}" payload = base64.b64encode(json.dumps(sec).encode()).decode() jsb = base64.b64encode(js.encode()).decode() cmd = f"""set -e umask 077; mkdir -p {gdir}; chmod 700 {gdir} echo {payload} | base64 -d > {gdir}/secrets.json; chmod 600 {gdir}/secrets.json echo {jsb} | base64 -d > {gdir}/shot.js set +e timeout 600 docker run --rm --network traefik-public --user root -e PUPPETEER_CACHE_DIR=/home/pptruser/.cache/puppeteer \ -e NODE_PATH=/home/pptruser/node_modules -e DOMAIN=enkisfelhom.hu -e SUB={sub} -e TRAEFIK={TRAEFIK} -e STOP=${{STOP:-}} \ -v {gdir}:/out {IMG} node /out/shot.js 2>&1 | sed -E "s/[A-Za-z0-9_.+/=-]{{28,}}//g" | tail -40 rc=${{PIPESTATUS[0]}} shred -u {gdir}/secrets.json echo "chrome rc=$rc"; ls -la {gdir} """ out = b.guest(cmd, timeout=900) log(app, out) def fetch(app, gpath, lpath): out = b.guest(f"base64 -w0 {gpath}") data = base64.b64decode(out.strip().split("\n")[0]) os.makedirs(os.path.dirname(lpath), exist_ok=True) open(lpath, "wb").write(data) log(app, f"fetched {gpath} -> {lpath} ({len(data)} B)") def remove(app): b.login() b.remove(app) verify(app) def verify(app): b.login() st = b.stack(app) out = b.guest(f"echo containers: $(docker ps -a --format '{{{{.Names}}}}' | grep -i '{app.split('-')[0]}' || echo none); " f"echo volumes: $(docker volume ls --format '{{{{.Name}}}}' | grep -i '{app.split('-')[0]}' || echo none); " f"echo networks: $(docker network ls --format '{{{{.Name}}}}' | grep -i '{app.split('-')[0]}' || echo none); " f"echo stackdir: $(ls -A /opt/docker/stacks/{app}); " f"echo drive: $(ls -d /mnt/felhom-drives/scratch_hdd/userdata/{app} 2>/dev/null || echo none)") log(app, f"VERIFY {app}: deployed={st.get('deployed')} state={st.get('state')}\n{out}") if __name__ == "__main__": a = sys.argv[1:] {"baseline": lambda: baseline(), "deploy": lambda: deploy(a[1], a[2]), "shoot": lambda: shoot(a[1], a[2], a[3], a[4:]), "fetch": lambda: fetch(a[1], a[2], a[3]), "remove": lambda: remove(a[1]), "verify": lambda: verify(a[1])}[a[0]]()