parked relay: a supervisor that keeps it alive + a REMOTE restart the operator can actually reach
Completes the travelling-operator deployment. The relay stays parked on the
home machine (so no player ever edits join.bat) and the console dials in from
anywhere -- but until now two things needed hands on that machine: bringing the
relay back when it died, and restarting it (the only way to clear a wedge or
pick up a roster resize). Both are covered now.
tools/btrelay_park.py -- the supervisor:
* runs the relay with cwd=content\ , which is what pins WHICH
operator_secret.txt is live (the trap documented last commit);
* relaunches on ANY exit, with 2/5/15/30/60s backoff when a relay dies inside
20s, so a permanent fault (port taken, missing egg) cannot become a spin;
* rotates content\parked_relay.log to .1 first, so the dead generation's
evidence survives the relaunch that replaces it;
* Ctrl-C stops it for good. NOT a Windows service on purpose: session 0
would hide the window an operator wants to tail.
tools/park_relay.cmd -- double-click to park; a shortcut to it in shell:startup
gives start-after-reboot with no admin rights.
`restart` on the control port (btconsole.py): sets restart_requested, the run
loop returns, relay_main exits RESTART_EXIT_CODE=42 and the supervisor relaunches
-- re-reading the egg. REFUSED while a mission is running (launches_sent >= 2
and not stop_sent): bouncing then would drop every pod out of a live round.
Local stdin gets the same command for parity.
btoperator.py: Restart Session in REMOTE mode now sends `restart` and reconnects
6s later instead of just tearing down our own link -- the old behaviour looked
like "Restart does nothing" against a parked relay. (QTimer had to be imported;
it was missing, which py_compile does not catch -- it would have been a runtime
NameError on the first click.)
VERIFIED by scratchpad/test_parked_supervisor.py, 12/12, and the full
test_remote_console_e2e.py re-run green afterwards (15/15):
* kill the relay -> a NEW pid is serving again, and the old log is kept as .1;
* remote `restart` -> ACKed, new pid, egg re-read, serving again;
* the supervisor distinguishes the two -- "operator requested a restart" vs
the crash/backoff path -- proven from its own log, not assumed;
* the mid-mission guard is present and wired to launches_sent/stop_sent.
Two harness defects fixed while proving it, both mine: a check written with
`or True` that could never fail (replaced with two real assertions against the
supervisor's log), and a recv that treated the relay's correct
close-after-restart as a ConnectionResetError failure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
05f1ffb194
commit
05fdd319d6
@@ -313,14 +313,21 @@ The production topology: **the relay stays parked on one machine forever** (so p
|
||||
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):
|
||||
**Park the relay** — use the supervisor `tools/btrelay_park.py` (or double-click
|
||||
`tools\park_relay.cmd`, which is also what goes in `shell:startup` for start-at-boot):
|
||||
|
||||
```
|
||||
cd C:\git\bt411\content
|
||||
python ..\tools\btconsole.py --relay 1500 OPERATOR.EGG
|
||||
python tools\btrelay_park.py --egg OPERATOR.EGG --port 1500
|
||||
```
|
||||
|
||||
It runs the relay with `cwd = content\` (which fixes *which* `operator_secret.txt` is live — see
|
||||
the trap below), **relaunches it on any exit**, backs off 2→5→15→30→60 s when a relay dies faster
|
||||
than 20 s so a permanent fault (port taken, missing egg) cannot spin, and rotates
|
||||
`content\parked_relay.log` to `.1` so the dead generation's evidence survives its replacement.
|
||||
Ctrl-C stops it for good. Deliberately **not** a Windows service — session 0 would hide the very
|
||||
window an operator wants to tail. A bare `python tools\btconsole.py --relay 1500 OPERATOR.EGG` from
|
||||
`content\` still works for a one-off; it just has nothing watching it.
|
||||
|
||||
**What a remote console CAN do** (control port = console port + 7, i.e. **1507**; `AUTH <secret>`):
|
||||
|
||||
| Command | Effect |
|
||||
@@ -330,6 +337,7 @@ python ..\tools\btconsole.py --relay 1500 OPERATOR.EGG
|
||||
| `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 |
|
||||
| `restart` | **exits the relay with code 42 so the supervisor relaunches it, re-reading the egg** — the only route to a roster resize or a wedge-clear from the road. Refused mid-mission (`launches_sent >= 2 and not stop_sent`): it would drop every pod out of a live round. The GUI's **Restart Session** sends this in remote mode and reconnects ~6 s later (it previously just disconnected you, which read as "Restart does nothing"). |
|
||||
| `ping` | liveness |
|
||||
| *(implicit)* | the full live log stream + the roster, re-issued from live state at every AUTH |
|
||||
|
||||
@@ -337,10 +345,12 @@ So a remote operator can run an entire night: stage, launch, end, re-arm, change
|
||||
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.
|
||||
- **Start the relay** — remote mode *attaches*; it cannot create one. That is the supervisor's job.
|
||||
- **Change the seat count in one step.** The running relay refuses a resize
|
||||
(`roster changed N->M seats -- IGNORED`); the egg on the parked machine has to change *and* the
|
||||
relay has to restart. `restart` now supplies the second half from the road — writing that egg's
|
||||
roster still needs local access, since only the six mission keys travel over the wire.
|
||||
(`Stop Session` remains disconnect-only, which is what you want against a parked relay.)
|
||||
- **Edit the roster** — callsign / mech / colour / badge / patch / experience are *not* in
|
||||
`CONTROL_SET_KEYS`; only the six mission keys travel over the wire.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user