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