"""sp.py — the permanent-gate spike's client (2026-10-01, 9202). curl through 9202's traefik over the LAN, or through the SIMULATED tunnel (a curl container at 172.16.253.2 on 9202, sending what Cloudflare sends). Secrets live in the 0600 scratch files named by $SPIKE; never printed.""" import json, os, re, subprocess, time SPIKE = os.environ["SPIKE"] # scratch dir: family-pw.json, secrets.json BASE = "https://192.168.0.114" DOM = "enkisfelhom.hu" GM, MT = f"gm-spike.{DOM}", f"mt-spike.{DOM}" FAM = json.load(open(f"{SPIKE}/family-pw.json")) SEC = json.load(open(f"{SPIKE}/secrets.json")) GSH = "/tmp/claude-1000/-mnt-5-hdd-felhom-eu-git/05c3d295-b388-481a-8c36-44a2a80b7d7d/scratchpad/g.sh" def curl(host, path, *a, method=None, data=None, cookie=None, hdrs=(), out_body=True, timeout=15): """LAN request. Returns (code, headers-text, body).""" args = ["curl", "-sk", "--max-time", str(timeout), "-D", "-", "-H", f"Host: {host}"] for h in hdrs: args += ["-H", h] if cookie: args += ["-H", f"Cookie: {cookie}"] if method: args += ["-X", method] if data is not None: args += ["--data", data] args += list(a) + [f"{BASE}{path}"] r = subprocess.run(args, capture_output=True, text=True, errors="replace") raw = r.stdout raw = raw.replace("\r\n", "\n") head, _, body = raw.partition("\n\n") while head.startswith("HTTP/") and (" 100 " in head.split("\n")[0]) and body: head, _, body = body.partition("\n\n") m = re.match(r"HTTP/\S+ (\d+)", head) return (m.group(1) if m else "000"), head, body def tunnel(host, path, visitor, forged_left=None, *a): """Through the SIMULATED tunnel: a curl container at 172.16.253.2 with Cloudflare's headers. Returns (code, headers, body).""" xff = f"{forged_left}, {visitor}" if forged_left else visitor extra = " ".join("'" + x.replace("'", "'\\''") + "'" for x in a) cmd = (f"docker run --rm --network felhom-tunnel --ip 172.16.253.2 curlimages/curl:8.11.1 -sk --max-time 15 -D - " f"'https://traefik{path}' -H 'Host: {host}' -H 'X-Forwarded-For: {xff}' -H 'CF-Connecting-IP: {visitor}' {extra}") r = subprocess.run([GSH, "9202", cmd], capture_output=True, text=True, errors="replace") head, _, body = r.stdout.replace("\r\n", "\n").partition("\n\n") m = re.match(r"HTTP/\S+ (\d+)", head) return (m.group(1) if m else "000"), head, body def cookie_from(head, name="felhom_family"): m = re.search(rf"(?im)^set-cookie: {name}=([^;]*)", head) return f"{name}={m.group(1)}" if m and m.group(1) else None def family_login(host, member, password=None): from urllib.parse import urlencode code, head, body = curl(host, "/__family/login", method="POST", data=urlencode({"user": member, "password": password or FAM[member], "rd": "/"}), hdrs=("Content-Type: application/x-www-form-urlencoded",)) return code, cookie_from(head), head def is_gate(code, head, body): """True when the answer is the GATE's (302 to /__family/login, or its 401 JSON) — never the app's.""" if code == "302" and re.search(r"(?im)^location: https://[^/]+/__family/login", head): return True return code == "401" and "sign in with your family login" in body