09ae93db05
gates / gates (push) Successful in 31s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
122 lines
9.6 KiB
Python
122 lines
9.6 KiB
Python
#!/usr/bin/env python3
|
|
"""sparky_box.py — SparkyFitness's checklist rows on scratch 9202 (live catalog, controller 0.286.1), through the product's
|
|
own endpoints. Strangers through traefik with no session and no gate cookie; the tunnel SIMULATED by a curl container at
|
|
172.16.253.2 (Part A's method). Secrets never printed. Env: SC, EV."""
|
|
import json, os, re, subprocess, sys, time
|
|
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
|
|
import box_walk as w
|
|
import upgrade_fixtures_box as fixtures
|
|
APP, SUB = "sparkyfitness", "sparky"
|
|
HOST = f"{SUB}.{w.DOMAIN}"; FX = fixtures.FIXTURES[APP]
|
|
log = open(f"{w.EV}/sparky-box.txt", "a", buffering=1)
|
|
def say(*a):
|
|
w.say(*a); log.write(" ".join(map(str, a)) + "\n")
|
|
def lan(path, *a):
|
|
r = subprocess.run(["curl", "-sk", "--max-time", "10", "-o", "/dev/null", "-w", "%{http_code}", "-H", f"Host: {HOST}"] + list(a) + [f"{w.BASE}{path}"],
|
|
capture_output=True, text=True)
|
|
return r.stdout.strip()
|
|
def tun(visitor, forged, path, data):
|
|
cmd = (f"docker run --rm --network felhom-tunnel --ip 172.16.253.2 curlimages/curl:8.11.1 -sk -o /dev/null -w '%{{http_code}}' "
|
|
f"'https://traefik{path}' -H 'Host: {HOST}' -H 'X-Forwarded-For: {forged}, {visitor}' -H 'CF-Connecting-IP: {visitor}' "
|
|
f"-H 'Origin: https://{HOST}' -H 'Content-Type: application/json' --data '{data}'")
|
|
return w.guest(cmd).strip()[-3:]
|
|
def state(): return w.stack(APP).get("state")
|
|
|
|
w.login()
|
|
say(f"##### SparkyFitness on 9202 — controller {w.guest('docker inspect felhom-controller --format {{.Config.Image}}').strip()} | catalog "
|
|
+ w.guest("cd /var/lib/docker/volumes/felhom-controller-data/_data/data/catalog-cache && git log --oneline -1").strip()[:9]
|
|
+ f" | {time.strftime('%FT%TZ', time.gmtime())}")
|
|
# 3.5 / 4.3 — a stranger polls from the press; the install timed
|
|
w.guest(f"""cat > /root/sp-poll.sh <<'PEOF'
|
|
out=/root/sp-poll.txt; : > $out; t0=$(date +%s)
|
|
while [ $(( $(date +%s) - t0 )) -lt 240 ]; do
|
|
r=$(curl -sk --max-time 4 -o /root/sp-poll.body -w '%{{http_code}}' -H 'Host: {HOST}' -H 'Content-Type: application/json' -X POST --data '{{"email":"s@x.hu","password":"Stranger-1234","name":"s"}}' https://127.0.0.1/api/auth/sign-up/email)
|
|
echo "$(date +%H:%M:%S) $r $(head -c 60 /root/sp-poll.body | tr '\\n' ' ')" >> $out; sleep 1
|
|
done
|
|
PEOF
|
|
(nohup sh /root/sp-poll.sh >/dev/null 2>&1 &)""")
|
|
t0 = time.time(); ok = w.deploy(APP, SUB); say(f"deploy -> {ok} ({round(time.time()-t0)} s to `deployed`)")
|
|
healthy = None
|
|
for _ in range(150):
|
|
hs = w.guest("for c in sparkyfitness sparkyfitness-server sparkyfitness-db; do docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}nohc{{end}}' $c; done").split()
|
|
if hs and all(h in ("healthy", "nohc") for h in hs): healthy = time.time() - t0; break
|
|
time.sleep(2)
|
|
say(f"4.3 every container healthy {round(healthy) if healthy else 'NEVER'} s after the press; restarts:",
|
|
w.guest("for c in sparkyfitness sparkyfitness-server sparkyfitness-db; do echo -n \"$c=$(docker inspect -f '{{.RestartCount}}' $c) \"; done").strip(),
|
|
"| state:", state())
|
|
say("3.3 before the setup, a stranger (LAN) on / , /api/auth/sign-up/email:", lan("/", "-H", "Accept: text/html"), lan("/api/auth/sign-up/email", "-X", "POST", "-H", "Content-Type: application/json", "--data", "{}"))
|
|
tok = FX.seed(w, SUB, say) # 3.1: the household through the gate, the first account
|
|
say("3.1 the household's first account through the gate ->", "ok" if tok else "FAILED " + str(getattr(FX, "tried", "")))
|
|
time.sleep(3); w.guest("pkill -f sp-poll.sh || true")
|
|
poll = w.guest("cat /root/sp-poll.txt").strip().splitlines(); codes = {}
|
|
for l in poll: codes[l.split(" ")[1]] = codes.get(l.split(" ")[1], 0) + 1
|
|
say(f"3.5 a stranger's {len(poll)} sign-up tries (1/s) from the press until the household's account: codes {codes}; any 200: {any(' 200 ' in l for l in poll)}")
|
|
# the household's 'Done, I set it up' (no probe in the template)
|
|
csrf = open(f"{w.SC}/csrf{os.getpid()}.txt").read().strip(); sess = open(f"{w.SC}/sess{os.getpid()}.txt").read().strip()
|
|
r = subprocess.run(["curl", "-sk", "-o", "/dev/null", "-w", "%{http_code}", "-H", w.HOSTHDR, "-H", f"Cookie: {sess}", "-H", f"X-CSRF-Token: {csrf}",
|
|
"-X", "POST", f"{w.BASE}/apps/{APP}/setup-gate/open"], capture_output=True, text=True)
|
|
say("the household presses 'Done, I set it up' ->", r.stdout)
|
|
for _ in range(40):
|
|
if "native_lock: applied" in w.guest(f"grep -A8 '^setup_gate:' /opt/docker/stacks/{APP}/app.yaml"): break
|
|
time.sleep(5)
|
|
say("3.4 lock record:", " | ".join(x.strip() for x in w.guest(f"grep -A8 '^setup_gate:' /opt/docker/stacks/{APP}/app.yaml").splitlines()),
|
|
"| app env SPARKY_FITNESS_DISABLE_SIGNUP =", w.guest("docker exec sparkyfitness-server printenv SPARKY_FITNESS_DISABLE_SIGNUP").strip())
|
|
w.wait_app(SUB, "/", want=("200",), tries=40)
|
|
body = json.dumps({"email": "stranger@example.com", "password": "Stranger-pass-1234", "name": "S"})
|
|
for p in ("/api/auth/sign-up/email", "/API/Auth/Sign-Up/email", "//api//auth/sign-up/email"):
|
|
say(f"3.4 a stranger signs up at {p} (LAN) ->", lan(p, "-X", "POST", "-H", "Content-Type: application/json", "-H", f"Origin: https://{HOST}", "--data", body))
|
|
say("3.4 the same straight at the server (behind the block — the app's own switch):",
|
|
w.guest("docker run --rm --network traefik-public curlimages/curl:8.11.1 -s -o /dev/null -w '%{http_code}' http://sparkyfitness-server:3010/api/auth/sign-up/email "
|
|
f"-H 'Content-Type: application/json' -H 'Origin: https://{HOST}' --data '{body}' || true").strip()[-3:])
|
|
say("3.9 browser-shaped sign-in (Origin, JSON) right / wrong:", FX._signin(w, SUB, tok["email"], tok["pw"])[0], FX._signin(w, SUB, tok["email"], "wrong-x")[0])
|
|
# 3.6 — lock-out through the simulated tunnel
|
|
say("3.6 waiting 20 s (better-auth's own window)"); time.sleep(20)
|
|
seq = []
|
|
for i in range(1, 9):
|
|
seq.append(tun("198.51.100.66", f"10.6.0.{i}", "/api/auth/sign-in/email", json.dumps({"email": tok["email"], "password": f"wrong-{i}"})))
|
|
say(f"3.6 stranger 198.51.100.66, 8 wrong sign-ins for the household's e-mail, a new forged leftmost each: {seq}")
|
|
t1 = time.time(); hh = []
|
|
for _ in range(20):
|
|
c = tun("203.0.113.10", "203.0.113.10", "/api/auth/sign-in/email", json.dumps({"email": tok["email"], "password": tok["pw"]}))
|
|
hh.append((round(time.time() - t1), c))
|
|
if c == "200": break
|
|
time.sleep(3)
|
|
say(f"3.6 the household from 203.0.113.10, right password, from the stranger's last try on (s, code): {hh}")
|
|
# 1.5 / 1.8 / 0.4
|
|
say("1.5 processes:", w.guest("for c in sparkyfitness-server sparkyfitness; do echo -n \"$c: \"; docker exec $c sh -c 'cat /proc/1/cmdline | tr \"\\000\" \" \"'; echo; done").strip())
|
|
say("1.8 an unknown page ->", lan("/felhom-no-such-page-xyz/", "-H", "Accept: text/html"), "| an unknown API ->", lan("/api/felhom-no-such"))
|
|
say("0.4 outbound hosts named in the server's first-start log:", sorted(set(re.findall(r"https?://([a-z0-9.-]+\.[a-z]{2,})", w.guest("docker logs sparkyfitness-server 2>&1 | head -400"))))[:12])
|
|
# 4.4 — negative control
|
|
w.guest("docker stop sparkyfitness-server >/dev/null"); seen = []
|
|
for i in range(9):
|
|
time.sleep(5); s = state()
|
|
if not seen or seen[-1][1] != s: seen.append((5 * (i + 1), s))
|
|
say(f"4.4 sparkyfitness-server STOPPED — states (s, state): {seen}; the front door / -> {lan('/')} ; /api/health -> {lan('/api/health')}")
|
|
w.guest("docker start sparkyfitness-server >/dev/null")
|
|
for i in range(40):
|
|
time.sleep(5)
|
|
if state() == "running": say(f" running again {5*(i+1)} s after docker start"); break
|
|
# 2.7 size
|
|
say("2.7 volumes after the seed:", w.guest("for v in $(docker volume ls -q | grep sparkyfitness); do echo -n \"$v=$(du -sh /var/lib/docker/volumes/$v/_data | cut -f1) \"; done").strip())
|
|
# 2.5 backup → remove keeping backups → restore → read back
|
|
code, d = w.ctl("POST", "/api/debug/backup/night-chain"); say("2.5 the night chain (debug action) ->", code)
|
|
for _ in range(60):
|
|
if "finished in" in w.guest("docker logs --since 3m felhom-controller 2>&1 | grep 'night-chain\\] finished' | tail -1"): break
|
|
time.sleep(5)
|
|
say(" restore points:", [(s.get("id") or s.get("snapshot_id"), s.get("time") or s.get("created")) for s in w.snapshots(APP)][:2])
|
|
w.ctl("POST", f"/api/stacks/{APP}/stop"); time.sleep(10)
|
|
code, d = w.ctl("POST", f"/api/stacks/{APP}/remove", {"remove_hdd_data": False, "remove_backups": False})
|
|
say(f"2.6 remove KEEPING backups -> {code} {str(d)[:220]}")
|
|
time.sleep(6); say(" left after:", w.guest(f"docker ps -a --format '{{{{.Names}}}}' | grep -E '^{APP}'; docker volume ls -q | grep -i {APP}").strip() or "nothing")
|
|
r = w.restore(APP); say("2.5 restore:", {k: r.get(k) for k in ("ok", "snapshot_id", "seconds", "state_after", "hold_after")})
|
|
w.wait_app(SUB, "/", want=("200",), tries=60)
|
|
say("2.5 the seed reads back after remove + restore:", FX.verify(w, SUB, tok, say))
|
|
say(" R-773 on a restored sparkyfitness: record", " | ".join(x.strip() for x in w.guest(f"grep -A8 '^setup_gate:' /opt/docker/stacks/{APP}/app.yaml").splitlines()[:7]),
|
|
"| a stranger signs up ->", lan("/api/auth/sign-up/email", "-X", "POST", "-H", "Content-Type: application/json", "-H", f"Origin: https://{HOST}", "--data", body))
|
|
# 2.6 remove with data
|
|
w.ctl("POST", f"/api/stacks/{APP}/stop"); time.sleep(10)
|
|
code, d = w.ctl("POST", f"/api/stacks/{APP}/remove", {"remove_hdd_data": True, "remove_backups": True})
|
|
say(f"2.6 remove WITH data and backups -> {code} {str(d)[:220]}")
|
|
time.sleep(6); say(" left after:", w.guest(f"ls -d /opt/docker/stacks/{APP} 2>/dev/null; docker ps -a --format '{{{{.Names}}}}' | grep -E '^{APP}'; docker volume ls -q | grep -i {APP}").strip() or "nothing")
|
|
w.guest("rm -f /root/sp-poll.sh /root/sp-poll.txt /root/sp-poll.body")
|