Deploy: configure.ps1 hardening + co-located smoke-test caveats in the plan
configure.ps1: bay-IP pick now filters to Up physical adapters and fails loudly on multiple candidates instead of coin-tossing by InterfaceMetric; -BayIp forces the choice (and permits binding a virtual adapter on a dev rig); -ConsoleIp sets WATTCP gateway/nameserver (default: the bay IP -- needs only to be a live on-subnet host, the console DIALS the pod); -Root is validated up front. DEPLOYMENT-PLAN: record the four first-smoke-test findings (2026-07-10, end-to-end egg->mission achieved): SendToRxAdapters on the pod NIC, host must not hold the game IP, NIC checksum offload silently kills co-located console->pod IP (ARP works, TCP silent -- disable offload+LSO), and bt/rp require sound + a packaged renderer (closes the renderer OPEN item: ship one). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -88,6 +88,38 @@ segment the console is a separate machine and hears every pod over the wire.
|
||||
Also gone: the machine-specific `realnic=DB5521D` GUID (postinstall binds the
|
||||
one NIC) and the `200.0.0.x` hardcoding (postinstall stamps the assigned IPs).
|
||||
|
||||
**Co-located smoke-test caveats (all hit 2026-07-10, first dist smoke test;
|
||||
end-to-end egg->mission achieved same night):** running TeslaConsole on the
|
||||
pod PC itself brings dev-era requirements back. On the real segment, with the
|
||||
console on its own machine, none of these apply:
|
||||
|
||||
1. Npcap `SendToRxAdapters` must list the pod NIC's `\Device\{GUID}` (restart
|
||||
the npcap service after setting it -- the stop fails silently while DOSBox
|
||||
holds a capture).
|
||||
2. The host must NOT hold the game IP. A leftover static at bayIP+100 (the
|
||||
old bridge address) makes the console dial itself -- source = destination,
|
||||
the SYN never reaches the wire, `SynSent` forever.
|
||||
3. **Disable NIC checksum offload (+ LSO) on the pod NIC**
|
||||
(`Disable-NetAdapterChecksumOffload`/`-NetAdapterLso`). The host's OWN
|
||||
outbound frames are captured BEFORE the hardware fills in IP/TCP checksums;
|
||||
WATTCP silently discards every bad-checksum IP packet while still answering
|
||||
checksum-less ARP -- so ARP resolves, the NIC accepts the frames, and the
|
||||
pod stays dead silent at the TCP layer (netnub prints "discarding...").
|
||||
This was the final console-connect blocker. Frames arriving over the real
|
||||
wire always carry completed checksums, so separate-machine deployments are
|
||||
immune.
|
||||
|
||||
Also learned same night, not co-location-specific: **bt/rp REQUIRE sound**
|
||||
(`--no-sound` strips the AWE32; the FAST SOS clock never ticks and BTL4OPT
|
||||
freezes in the RIO-reset busy-wait), and **the pod requires a packaged
|
||||
renderer** (pod-launch refuses to start without one unless `--no-bridge`;
|
||||
the smoke test ran with the dev tree's render bridge via `--root`) -- closes
|
||||
the "freeze renderer vs bundle Python" OPEN item in favor of: MUST ship one.
|
||||
Related guard: `configure.ps1` refuses to guess when several candidate bay
|
||||
IPs survive its physical-adapter filter (`-BayIp x.x.x.x` disambiguates, and
|
||||
also allows deliberately binding a virtual adapter on a dev rig);
|
||||
`-ConsoleIp` sets the WATTCP gateway/nameserver (default: the bay IP).
|
||||
|
||||
## The archive (self-contained -- air-gap forbids any download)
|
||||
|
||||
Single root folder + `postinstall.bat`. Everything the install needs is inside:
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
# DOSBox game IP = bayIP + 100, binds DOSBox's pcap to the NIC, renders the
|
||||
# net_*.conf templates, and stamps the WATTCP.CFG my_ip. Air-gapped, static,
|
||||
# <=32 pods. See ../DEPLOYMENT-PLAN.md.
|
||||
param([Parameter(Mandatory = $true)][string]$Root)
|
||||
param([Parameter(Mandatory = $true)][string]$Root,
|
||||
[string]$BayIp, # explicit bay IP; bypasses the physical-adapter filter
|
||||
[string]$ConsoleIp) # TeslaConsole's IP (default: bay IP, i.e. co-located)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
function Log($m) { Write-Host "[configure] $m" }
|
||||
if (-not (Test-Path -LiteralPath $Root -PathType Container)) { throw "-Root '$Root' is not a directory (typo?)" }
|
||||
$Root = (Resolve-Path -LiteralPath $Root).Path
|
||||
|
||||
# --- prefix length -> dotted netmask -------------------------------------
|
||||
function PrefixToMask([int]$p) {
|
||||
@@ -15,14 +19,31 @@ function PrefixToMask([int]$p) {
|
||||
}
|
||||
|
||||
# --- 1. the pod's NIC + bay IP (the one real static IPv4) ----------------
|
||||
$ip = Get-NetIPAddress -AddressFamily IPv4 |
|
||||
# A bay has exactly ONE physical NIC up with ONE static IPv4. Virtual adapters
|
||||
# (Network Bridge multiplexor, TAPs -- dev leftovers) are excluded; if several
|
||||
# candidates still survive, fail loudly instead of coin-tossing (a multi-homed
|
||||
# host once got lucky here). -BayIp forces the choice -- and skips the
|
||||
# physical filter, so a dev rig can deliberately bind a bridge.
|
||||
$cands = @(Get-NetIPAddress -AddressFamily IPv4 |
|
||||
Where-Object { $_.IPAddress -notlike '127.*' -and $_.IPAddress -notlike '169.254.*' } |
|
||||
Sort-Object -Property SkipAsSource, InterfaceMetric |
|
||||
Select-Object -First 1
|
||||
if (-not $ip) { throw "no usable static IPv4 found on any adapter" }
|
||||
ForEach-Object {
|
||||
$ad = Get-NetAdapter -InterfaceIndex $_.InterfaceIndex -ErrorAction SilentlyContinue
|
||||
if ($ad) { [pscustomobject]@{ Ip = $_; Adapter = $ad } }
|
||||
})
|
||||
if ($BayIp) {
|
||||
$cands = @($cands | Where-Object { $_.Ip.IPAddress -eq $BayIp })
|
||||
if ($cands.Count -eq 0) { throw "-BayIp $BayIp is not configured on any adapter" }
|
||||
} else {
|
||||
$cands = @($cands | Where-Object { -not $_.Adapter.Virtual -and $_.Adapter.Status -eq 'Up' })
|
||||
if ($cands.Count -eq 0) { throw "no IPv4 on an Up physical adapter; rerun with -BayIp x.x.x.x" }
|
||||
}
|
||||
if ($cands.Count -gt 1) {
|
||||
$list = ($cands | ForEach-Object { "$($_.Ip.IPAddress) on '$($_.Adapter.Name)'" }) -join ', '
|
||||
throw "ambiguous bay IP -- $($cands.Count) candidates ($list); rerun with -BayIp x.x.x.x"
|
||||
}
|
||||
$ip = $cands[0].Ip
|
||||
$bayIp = $ip.IPAddress
|
||||
$ifIndex = $ip.InterfaceIndex
|
||||
$adapter = Get-NetAdapter -InterfaceIndex $ifIndex
|
||||
$adapter = $cands[0].Adapter
|
||||
$guid = $adapter.InterfaceGuid.Trim('{', '}')
|
||||
$mask = PrefixToMask $ip.PrefixLength
|
||||
Log "bay IP $bayIp/$($ip.PrefixLength) on '$($adapter.Name)' guid $guid"
|
||||
@@ -50,13 +71,15 @@ Log "game IP = $gameIp (bay + 100)"
|
||||
$mac = "02:00:{0:X2}:{1:X2}:{2:X2}:{3:X2}" -f [int]$o[0], [int]$o[1], [int]$o[2], $last
|
||||
Log "macaddr = $mac"
|
||||
|
||||
# --- 5. gateway/nameserver: the host's real gateway if set, else the host
|
||||
# itself (always up + on-subnet -> WATTCP's boot ARP resolves; never
|
||||
# actually routed, the mesh is same-subnet L2) -----------------------
|
||||
$gw = $null
|
||||
try { $gw = (Get-NetIPConfiguration -InterfaceIndex $ifIndex).IPv4DefaultGateway.NextHop } catch { }
|
||||
# --- 5. gateway/nameserver: needs only to be a LIVE on-subnet host (WATTCP
|
||||
# boot-ARPs it; nothing is ever routed -- the mesh is same-subnet L2,
|
||||
# and the CONSOLE dials the pod, not vice versa). The console PC's IP
|
||||
# is the natural pick: always up whenever a mission can start. Default
|
||||
# = this machine's bay IP (co-located console / smoke test); real
|
||||
# deployments pass -ConsoleIp <the console PC's static IP>. ---------
|
||||
$gw = $ConsoleIp
|
||||
if (-not $gw) { $gw = $bayIp }
|
||||
Log "gateway/nameserver = $gw"
|
||||
Log "gateway/nameserver (console PC) = $gw"
|
||||
|
||||
# --- 6. render the conf templates (@@ROOT@@/@@REALNIC@@/@@MACADDR@@) ------
|
||||
$tokens = @{ '@@ROOT@@' = $Root; '@@REALNIC@@' = $realnic; '@@MACADDR@@' = $mac }
|
||||
|
||||
Reference in New Issue
Block a user