Network-agnostic, air-gapped pod install for the two DOSBox titles (BT/RP 4.10), fitting the existing TeslaConsole/TeslaLauncher pod-bay architecture. emulator/DEPLOYMENT-PLAN.md Full design: bridge on the one NIC (pods form a source-proven P2P TCP mesh, so NAT/slirp is out); launcher keeps the bay IP, the DOSBox guest bridges at bayIP+100 (egg/mesh/game endpoint); two-edit +100 convention (postinstall + console DOSBox flag); static, air-gapped, <=32 pods. emulator/pod-launch/ (C# net8 supervising entry-point) Creates a Job Object (KILL_ON_JOB_CLOSE), launches DOSBox-X + the render bridge into it and blocks -- kill this process and both die (kernel-enforced, even on a hard TerminateProcess of a hung session; verified). Mode dispatch: bt/rp wired (also serve camera + live mission-review via egg hostType); review + diagnostics recognized but fail clean until their DOS launch args are known. emulator/deploy/ (install side) postinstall.bat (thin elevated wrapper) + configure.ps1 (NIC detect, realnic bind as a contiguous letter-leading GUID fragment, game IP = bayIP+100, stable MAC, render net_*.conf templates, stamp WATTCP.CFG my_ip) + tokenized conf templates + package.ps1 (assemble the zip). Render/bind/stamp + packaging verified against a scratch tree. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
87 lines
4.1 KiB
PowerShell
87 lines
4.1 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)
|
|
$ErrorActionPreference = 'Stop'
|
|
function Log($m) { Write-Host "[configure] $m" }
|
|
|
|
# --- 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) ----------------
|
|
$ip = 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" }
|
|
$bayIp = $ip.IPAddress
|
|
$ifIndex = $ip.InterfaceIndex
|
|
$adapter = Get-NetAdapter -InterfaceIndex $ifIndex
|
|
$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: 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 { }
|
|
if (-not $gw) { $gw = $bayIp }
|
|
Log "gateway/nameserver = $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)"
|