Files
TeslaRel410/emulator/deploy/configure.ps1
T
CydandClaude Fable 5 4adfaf740a 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>
2026-07-10 23:47:24 -05:00

110 lines
5.6 KiB
PowerShell

# configure.ps1 -- the per-pod install step, called by postinstall.bat (elevated).
# On THIS machine it: finds the NIC + its static IP (the bay IP), derives the
# 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,
[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) {
$bits = ('1' * $p).PadRight(32, '0')
$o = 0..3 | ForEach-Object { [Convert]::ToInt32($bits.Substring($_ * 8, 8), 2) }
return ($o -join '.')
}
# --- 1. the pod's NIC + bay IP (the one real static 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.*' } |
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
$adapter = $cands[0].Adapter
$guid = $adapter.InterfaceGuid.Trim('{', '}')
$mask = PrefixToMask $ip.PrefixLength
Log "bay IP $bayIp/$($ip.PrefixLength) on '$($adapter.Name)' guid $guid"
# --- 2. realnic fragment. Must be a CONTIGUOUS substring of the pcap device
# name (\Device\NPF_{GUID}, hyphens included), so take it from ONE
# hyphen-delimited GUID block -- letter-leading (DOSBox reads a leading
# DIGIT as an interface index) and length >= 6 for uniqueness. Longest. --
$realnic = $null
foreach ($blk in ($guid -split '-')) {
$mm = [regex]::Match($blk, '[A-Fa-f][0-9A-Fa-f]{5,}')
if ($mm.Success -and (-not $realnic -or $mm.Value.Length -gt $realnic.Length)) { $realnic = $mm.Value }
}
if (-not $realnic) { throw "no letter-leading >=6-char realnic fragment in any block of GUID $guid" }
Log "realnic fragment = $realnic"
# --- 3. game IP = bay IP + 100 on the last octet -------------------------
$o = $bayIp.Split('.')
$last = [int]$o[3] + 100
if ($last -gt 254) { throw "bay last octet $($o[3]) + 100 = $last overflows 254; keep bays <= .100" }
$gameIp = "$($o[0]).$($o[1]).$($o[2]).$last"
Log "game IP = $gameIp (bay + 100)"
# --- 4. stable locally-administered MAC from the game IP ------------------
$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: 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 (console PC) = $gw"
# --- 6. render the conf templates (@@ROOT@@/@@REALNIC@@/@@MACADDR@@) ------
$tokens = @{ '@@ROOT@@' = $Root; '@@REALNIC@@' = $realnic; '@@MACADDR@@' = $mac }
$tmpls = Get-ChildItem -Path $Root -Filter '*.conf.tmpl' -ErrorAction SilentlyContinue
if (-not $tmpls) { throw "no *.conf.tmpl found in $Root" }
foreach ($t in $tmpls) {
$text = Get-Content -Path $t.FullName -Raw
foreach ($k in $tokens.Keys) { $text = $text.Replace($k, $tokens[$k]) }
$out = Join-Path $Root ($t.Name -replace '\.tmpl$', '')
Set-Content -Path $out -Value $text -Encoding Ascii -NoNewline
Log "rendered $($t.Name -replace '\.tmpl$','')"
}
# --- 7. stamp WATTCP.CFG my_ip = game IP (all copies the game may read) ---
$wattcp = @(
(Join-Path $Root 'ALPHA_1\REL410\BT\WATTCP.CFG'),
(Join-Path $Root 'ALPHA_1\REL410\RP\WATTCP.CFG'),
(Join-Path $Root 'ALPHA_1\VGL_LABS\THISPOD\WATTCP.CFG')
)
$lines = @("my_ip = $gameIp", "netmask = $mask", "nameserver = $gw", "gateway = $gw")
$stamped = 0
foreach ($w in $wattcp) {
if (Test-Path $w) { Set-Content -Path $w -Value $lines -Encoding Ascii; $stamped++; Log "stamped $w" }
}
if ($stamped -eq 0) { throw "no WATTCP.CFG found under $Root\ALPHA_1" }
Log "OK: bay=$bayIp game=$gameIp mac=$mac realnic=$realnic ($stamped WATTCP.CFG)"