67b3fd23d1
gates / gates (push) Failing after 13m1s
Part A: claim table documentation/audits/website-refresh-2026-10-08/claims.md; "100% open source" corrected from the licence read; unbacked claims cut (firewall, RAID, snapshots, new-machine restore, self-managed mode, household VPN, "never lost"). Part B: dawarich, docmost, grimmory, homebox, karakeep, mealie, metube, radicale, recipe-importer, sparkyfitness cards; plant-it and wger cut (not offered). Part C: /en/ twins, nav language switch, hreflang both ways, sitemap with xhtml:link, og-image-en.png. Part D: site_gates.py twins/lang/same-apps/no-Hungarian/FAQ-JSON-LD/tail. Operator ruling 9 + choice B in 10-localisation.md §11, §10.7. Register: R-902 (contact mailer source in no repo); R-813, R-784, R-559 annotated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
129 lines
5.4 KiB
Python
129 lines
5.4 KiB
Python
"""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 <app> <sub>
|
|
python3 wr.py shoot <app> <sub> <script.js> [KEY=VAL ...] # secrets.json in the guest, 0600, removed after
|
|
python3 wr.py fetch <app> <guest-path> <local-path> # one file off the guest
|
|
python3 wr.py remove <app>
|
|
python3 wr.py verify <app>
|
|
|
|
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/<app>/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,}}/<long-value-redacted>/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]]()
|