#!/usr/bin/env python3 """Switch the FIFA client's Blaze ports between PRODUCTION and STAGING, reversibly. The staging sold experiment needs the client to talk to the staging Blaze (which advertises the staging UTAS base). The ONLY client change required is the two Blaze port lines in `openfut.cfg`; `host` and `https_port` are left alone. Safety properties, in order of importance: * The production values are recorded to a sidecar file on the client BEFORE the first edit, and `restore` reads that sidecar rather than assuming what production was. If the sidecar is missing, restore refuses. * `restore` is idempotent and safe to run at any time, including after a crash. * Every operation re-reads the file afterwards and prints it, so the result is verified rather than assumed. * It refuses to edit while a FIFA client is running: the hook reads this file at connect time, so changing it under a live session is unsafe. * Only the two known keys are rewritten. Unknown lines are passed through untouched, and a missing key is an error rather than a silent append. python3 scripts/sold-client-ports.py show python3 scripts/sold-client-ports.py staging # 42327 / 42330 python3 scripts/sold-client-ports.py restore # back to the recorded values """ import subprocess import sys CLIENT = "alex@10.10.0.105" CFG = "/mnt/games/FIFA 17/openfut.cfg" SIDECAR = "/mnt/games/FIFA 17/openfut.cfg.openfut-prod-ports" KEYS = ("blaze_redirector_port", "blaze_main_port") STAGING = {"blaze_redirector_port": "42327", "blaze_main_port": "42330"} def ssh(script): r = subprocess.run(["ssh", "-o", "BatchMode=yes", CLIENT, script], capture_output=True, text=True, timeout=60) if r.returncode != 0: raise SystemExit(f"ssh failed ({r.returncode}): {r.stderr.strip()}") return r.stdout def read_cfg(): return ssh(f'cat "{CFG}"') def parse(text): out = {} for line in text.splitlines(): if "=" in line and not line.strip().startswith("#"): k, _, v = line.partition("=") out[k.strip()] = v.strip() return out def fifa_running(): return bool(ssh("pgrep -f FIFA17.exe || true").strip()) def show(): text = read_cfg() print(f"--- {CFG}") print(text.rstrip()) cur = parse(text) print("--- blaze ports:", {k: cur.get(k) for k in KEYS}) side = ssh(f'cat "{SIDECAR}" 2>/dev/null || true').strip() print("--- recorded production ports:", side or "(none recorded yet)") print("--- FIFA client running:", "YES" if fifa_running() else "no") return cur def write_ports(values, label): if fifa_running(): raise SystemExit( "REFUSING to edit openfut.cfg while a FIFA client is running.\n" "The hook reads this file at connect time; exit FIFA first." ) text = read_cfg() cur = parse(text) for k in KEYS: if k not in cur: raise SystemExit(f"key {k!r} is absent from {CFG}; refusing to guess it") # Record production values once, before the first mutation, so restore never # has to assume what they were. existing_sidecar = ssh(f'cat "{SIDECAR}" 2>/dev/null || true').strip() if not existing_sidecar and label == "staging": rec = "\n".join(f"{k}={cur[k]}" for k in KEYS) ssh(f'cat > "{SIDECAR}" <<\'EOF\'\n{rec}\nEOF') print(f"recorded production ports to {SIDECAR}:\n{rec}") out = [] for line in text.splitlines(): k = line.partition("=")[0].strip() out.append(f"{k}={values[k]}" if k in values else line) body = "\n".join(out) + "\n" ssh(f'cat > "{CFG}" <<\'EOF\'\n{body.rstrip()}\nEOF') after = parse(read_cfg()) for k, v in values.items(): if after.get(k) != v: raise SystemExit(f"VERIFY FAILED: {k} is {after.get(k)!r}, expected {v!r}") print(f"--- now pointing at {label}") print(read_cfg().rstrip()) def main(): if len(sys.argv) != 2 or sys.argv[1] not in ("show", "staging", "restore"): print(__doc__) return 2 cmd = sys.argv[1] if cmd == "show": show() return 0 if cmd == "staging": write_ports(STAGING, "staging") return 0 side = ssh(f'cat "{SIDECAR}" 2>/dev/null || true').strip() if not side: raise SystemExit( f"no recorded production ports at {SIDECAR}; refusing to guess.\n" "Set them by hand and verify with `show`." ) write_ports(parse(side), "production (restored)") return 0 if __name__ == "__main__": sys.exit(main())