From 9df7dff78767ffa62d8b3559afb36d8ef529f070 Mon Sep 17 00:00:00 2001 From: arcattack Date: Sun, 26 Jul 2026 18:15:54 -0500 Subject: [PATCH] KB: the parked-relay/remote-console mode is COMPLETE -- record it so it stops being re-derived MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Operator caught me re-investigating a feature we already shipped today. Root cause was a self-contradicting knowledge base, not just my compaction: the §Modes entry still said "Remote relay -- KNOWN BROKEN: the LAUNCH button can never enable in this mode", while §Mode-specific traps 60 lines later documented the same thing as FIXED. Reading the summary line first is exactly what a future session does. Swept: that was the only stale copy. Now recorded once, authoritatively (context/operator-console.md §Parked relay + remote console, and a sysop recipe in docs/OPERATOR_GUIDE.md): * THE PRODUCTION TOPOLOGY the operator actually wants: the relay parked permanently on the home machine so no player ever edits join.bat, with the console dialling in from anywhere (cell internet is fine -- outbound only). * The exact standalone park command, and why the control port is what makes a GUI-less relay usable at all (backgrounded, it has no stdin). * The COMPLETE remote command set as a table, read out of _ctl_command / _ctl_get_mission / _ctl_set_mission: launch, stop, rearm|newround, get, set (the six CONTROL_SET_KEYS, written to the relay's OWN egg, effective next round), ping, plus the log stream and the roster re-issued at AUTH. Written down so the next session reads instead of re-deriving. * The hard limits: remote mode ATTACHES -- it cannot start or restart the relay, cannot resize the roster, cannot edit callsign/mech/colour. * Multiple operators ARE accepted concurrently (ctl_conns is a list, each AUTHing independently), so hand-off is seamless -- but nothing arbitrates two people pressing LAUNCH. Two live hazards found while verifying, both new: 1. THE SECRET'S CWD TRAP. control_secret() opens a bare relative "operator_secret.txt" and SILENTLY GENERATES A NEW ONE if absent, so the live secret depends on the relay's working directory. This repo really has TWO secret files with DIFFERENT values (repo root and content\) -- a remote operator holding the wrong one fails AUTH as a CONTROL_AUTH_TIMEOUT drop, with no "wrong password" anywhere. Documented in both files. 2. operator_secret.txt was untracked but NOT ignored -- one `git add -A` from publishing a live credential. Now in .gitignore. Also corrected a nearby overreach: "the console must STAY CONNECTED" is about the console channel to the PODS; an operator GUI dropping off a parked relay's control port is harmless and the pods never notice. Co-Authored-By: Claude Opus 5 (1M context) --- .gitignore | 3 ++ context/operator-console.md | 70 +++++++++++++++++++++++++++++++++++-- docs/OPERATOR_GUIDE.md | 35 +++++++++++++++++++ 3 files changed, 105 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index b5d5f05..81dc278 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ content/fe_last.ini /play_steam.bat # MP match forensic logs (matchlog.cpp) -- runtime artifacts matchlog_*.txt + +# Operator control-channel secret -- NEVER commit (plaintext shared credential) +operator_secret.txt diff --git a/context/operator-console.md b/context/operator-console.md index fb47db1..8c2f4c4 100644 --- a/context/operator-console.md +++ b/context/operator-console.md @@ -293,11 +293,75 @@ no UDP fan-out, no control channel). All sends now go through `_send_all_guarded - **Relay (internet)** — `btconsole.py --relay `; pods dial out. The normal mode. - **Mesh (LAN legacy)** — `btconsole.py ...`; the console dials OUT to each pod's `-net` port. The authentic 1995 cabinet direction. -- **Remote relay** — the GUI drives someone else's relay over the control port instead of spawning - a child. **Known broken:** the LAUNCH button can never enable in this mode. +- **Remote relay / PARKED RELAY** — the GUI drives an already-running relay over the control port + instead of spawning a child. **This is a COMPLETE, SUPPORTED deployment mode** (fixed 2026-07-26 — + do not re-derive it; see §Parked relay below). The intended production shape: the relay lives + **permanently on the operator's home machine** so every player's `join.bat` address never changes, + while the console dials in from anywhere (a laptop on cell internet, another country). **The console must STAY CONNECTED.** A disconnect trips the pod's console-loss path, which also -closes its game listener — an engine bug, not ours. +closes its game listener — an engine bug, not ours. (This is about the *console channel to the +pods* — the relay holds those. An **operator** GUI disconnecting from a parked relay's control +port is harmless: the relay keeps running and the pods never notice.) + +--- + +## Parked relay + remote console [COMPLETE 2026-07-26 — do not re-derive] + +The production topology: **the relay stays parked on one machine forever** (so players' +`join.bat` never changes), and the operator console attaches over the control port from anywhere. +Verified by reading the control handler (`tools/btconsole.py` `_ctl_command`, `_ctl_get_mission`, +`_ctl_set_mission`) — this is the authoritative list, so nobody has to re-read it again. + +**Park the relay** (standalone, no GUI — the control port is what makes this viable, since a +backgrounded relay has no stdin): + +``` +cd C:\git\bt411\content +python ..\tools\btconsole.py --relay 1500 OPERATOR.EGG +``` + +**What a remote console CAN do** (control port = console port + 7, i.e. **1507**; `AUTH `): + +| Command | Effect | +|---|---| +| `launch` | the RunMission pair — a real mission start | +| `stop` | End Mission (the stop latch) | +| `rearm` / `newround` | the re-arm escape hatch (recover a dead LAUNCH between rounds) | +| `get` | current mission settings + live seat count | +| `set map=…;time=…;weather=…;scenario=…;temperature=…;length=…` | writes **the relay's own egg on the parked machine** (`CONTROL_SET_KEYS` whitelist), effective next round | +| `ping` | liveness | +| *(implicit)* | the full live log stream + the roster, re-issued from live state at every AUTH | + +So a remote operator can run an entire night: stage, launch, end, re-arm, change the map/weather/ +length between rounds, and watch real pilot lights. + +**What a remote console CANNOT do** (these need access to the parked machine itself): +- **Start the relay** — remote mode *attaches*; it cannot create a relay. +- **Restart it.** `Stop Session` only disconnects the operator; `Restart Session` just reconnects. +- **Change the seat count** — the relay refuses (`roster changed N->M seats -- IGNORED`), and + resizing needs a relay restart. +- **Edit the roster** — callsign / mech / colour / badge / patch / experience are *not* in + `CONTROL_SET_KEYS`; only the six mission keys travel over the wire. + +**THE SECRET'S WORKING-DIRECTORY TRAP.** `control_secret()` opens the bare relative name +`operator_secret.txt`, so **the live secret depends on the relay's cwd** — and if none is found it +silently *generates a new one*. Confirmed hazard 2026-07-26: this repo had **two secret files with +different values** (`operator_secret.txt` and `content\operator_secret.txt`), so a remote operator +holding one of them would fail AUTH against a relay started from the other directory (symptom: the +control connection is dropped on the `CONTROL_AUTH_TIMEOUT`, not a clear "wrong password"). Always +park the relay from the **same** directory, and hand out the secret from *that* directory. The file +is now `.gitignore`d — it was previously untracked-but-not-ignored, one `git add -A` away from +being published in a shared repo. + +**Multiple operators** are accepted concurrently (`ctl_conns` is a list, each AUTHing independently +and receiving the same broadcast), which makes hand-off seamless — but nothing arbitrates two +people pressing LAUNCH, so treat one-operator-at-a-time as an operating discipline. + +**Exposure.** The control channel is plaintext TCP with a shared secret, so forwarding 1507 to the +open internet puts that secret on the wire in the clear. Preferred shape: leave the **player** +ports (1500/1501) forwarded exactly as they are — `join.bat` untouched — and carry only the +operator control link over a private tunnel (mesh VPN). --- diff --git a/docs/OPERATOR_GUIDE.md b/docs/OPERATOR_GUIDE.md index 91eadfc..a9c4de5 100644 --- a/docs/OPERATOR_GUIDE.md +++ b/docs/OPERATOR_GUIDE.md @@ -130,6 +130,41 @@ when you connect. Two things still differ: **Stop Session only disconnects you** keeps running, which is usually what you want), and **Restart Session** likewise just reconnects the control link rather than restarting that relay. +### Running the relay PARKED (console from anywhere) + +The recommended setup for a travelling operator: the relay lives permanently on the home machine, +so **no player ever edits `join.bat`**, and you drive it from a laptop — cell internet is fine, +since the console only makes an outbound connection. + +**On the home machine, before you leave:** + +``` +cd C:\git\bt411\content +python ..\tools\btconsole.py --relay 1500 OPERATOR.EGG +``` + +Leave it running. Note the port it prints for operator control (**1507** = console port + 7) and +copy `content\operator_secret.txt` — that string is what any console needs to connect, and it grants +full mission control, so hand it out deliberately. + +**From the road:** run `python tools/btoperator.py`, put the home address in Host, Console port +1500, paste the secret, and connect. You get launch, end, re-arm, mission changes (map / time / +weather / scenario / temperature / length, applied to the egg on the home machine, effective next +round) and live pilot lights. + +**What still needs someone at the home machine:** starting the relay if it is not running, +restarting it, changing the **seat count**, and editing callsigns / mechs / colours — only the six +mission settings travel over the control link. + +**Two traps worth knowing:** +- **The secret depends on the relay's working directory.** It is read as a bare `operator_secret.txt` + relative to wherever the relay was started, and if it is missing the relay *silently generates a + new one*. Always start the parked relay from the same folder (`content\`), or a console using + yesterday's secret will just be dropped on the auth timeout with no clear error. +- **Don't forward the control port to the open internet.** It is plaintext with a shared secret. + Keep the player ports (1500/1501) forwarded exactly as they already are and carry the operator + link over a private tunnel (Tailscale or similar) instead. + **Other sharp edges:** - **Start Session overwrites your egg file** on disk, without asking. Keep a copy of any mission you care about under a different name.