<# .SYNOPSIS Start a frozen meshbay-node as a daemon and check it answers as the version it claims to be. .DESCRIPTION `meshbay-node --help` imports the parser and nothing else, so it passes for a bundle that cannot start the daemon at all. This starts the real daemon, in a throwaway profile (its own LOCALAPPDATA, an unused port, a hub URL nothing listens on -- it never touches a real hub or the developer's own node), waits for its control API, and checks: - /api/status answers, with the expected version; - the Windows log file was written (a service-mode daemon has no console, and this file is the only place its errors go). Also useful against an installed build: smoke-node-runtime.ps1 -Exe "$env:LOCALAPPDATA\Programs\MeshBay\resources\node-runtime\meshbay-node.exe" -ExpectVersion 0.16.0 .PARAMETER Exe The meshbay-node.exe to start. .PARAMETER ExpectVersion The version /api/status must report. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)][string]$Exe, [Parameter(Mandatory = $true)][string]$ExpectVersion, [int]$TimeoutSeconds = 45 ) $ErrorActionPreference = "Stop" Set-StrictMode -Version Latest $profileDir = Join-Path ([IO.Path]::GetTempPath()) ("meshbay-smoke-" + [guid]::NewGuid().ToString("N")) $meshbay = Join-Path $profileDir "meshbay" New-Item -ItemType Directory -Force $meshbay | Out-Null # A free loopback port, so a node already running here on 18000 is untouched. $listener = [Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback, 0) $listener.Start() $port = $listener.LocalEndpoint.Port $listener.Stop() $bytes = New-Object byte[] 32 [Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes) $unlock = Join-Path $meshbay "unlock.key" Set-Content -Encoding ascii -LiteralPath $unlock ([Convert]::ToBase64String($bytes)) $unlockToml = $unlock.Replace([char]92, [char]47) Set-Content -Encoding ascii -LiteralPath (Join-Path $meshbay "node.toml") @" [hub] url = "http://127.0.0.1:1" username = "smoke" [node] quic_enabled = false ui_port = $port [keystore] unlock_file = "$unlockToml" "@ $oldLocal = $env:LOCALAPPDATA $env:LOCALAPPDATA = $profileDir $stderr = Join-Path $profileDir "stderr.txt" $proc = $null try { $proc = Start-Process -FilePath $Exe -PassThru -WindowStyle Hidden ` -RedirectStandardError $stderr -RedirectStandardOutput (Join-Path $profileDir "stdout.txt") $env:LOCALAPPDATA = $oldLocal $tokenFile = Join-Path $meshbay "data\ui-token" $status = $null $deadline = (Get-Date).AddSeconds($TimeoutSeconds) while ((Get-Date) -lt $deadline -and -not $proc.HasExited) { if (Test-Path -LiteralPath $tokenFile) { try { $token = (Get-Content -LiteralPath $tokenFile -Raw).Trim() $status = Invoke-RestMethod "http://127.0.0.1:$port/api/status?t=$token" -TimeoutSec 3 break } catch { } } Start-Sleep -Milliseconds 500 } $tail = if (Test-Path -LiteralPath $stderr) { (Get-Content -LiteralPath $stderr -Tail 30) -join "`n" } else { "" } if ($proc.HasExited) { throw "the frozen daemon exited (code $($proc.ExitCode)) before its control API answered:`n$tail" } if ($null -eq $status) { throw "the frozen daemon's control API did not answer within ${TimeoutSeconds}s:`n$tail" } if ($status.version -ne $ExpectVersion) { throw "the frozen daemon reports version '$($status.version)', expected '$ExpectVersion'" } $log = Join-Path $meshbay "state\node.log" if (-not (Test-Path -LiteralPath $log) -or (Get-Item -LiteralPath $log).Length -eq 0) { throw "the frozen daemon wrote no log file at $log" } Write-Host "OK daemon answered: version $($status.version), status $($status.status), log $log" -ForegroundColor Green } finally { $env:LOCALAPPDATA = $oldLocal if ($proc -and -not $proc.HasExited) { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue } if ($proc) { $proc.WaitForExit(10000) | Out-Null } Remove-Item -LiteralPath $profileDir -Recurse -Force -ErrorAction SilentlyContinue }