[CmdletBinding()] param([Parameter(Mandatory=$true)][string]$ReleaseDirectory, [int]$Port = 18183, [switch]$CheckSamePortBind) $ErrorActionPreference = 'Stop' $release = (Resolve-Path -LiteralPath $ReleaseDirectory).Path $repo = Split-Path -Parent $PSScriptRoot $probe = Join-Path $repo ('Tools/output/server_release/probe-' + [Guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Path $probe -Force | Out-Null foreach ($script in Get-ChildItem -LiteralPath $release -Filter '*.ps1') { $syntaxErrors = $null; $tokens = $null [System.Management.Automation.Language.Parser]::ParseFile($script.FullName,[ref]$tokens,[ref]$syntaxErrors) | Out-Null if ($syntaxErrors.Count) { throw ($syntaxErrors | Out-String) } } $probeProcess = Start-Process -FilePath (Join-Path $release 'aiserver.exe') -ArgumentList @( '--host','127.0.0.1','--port',"$Port",'--db',(Join-Path $probe 'aiserver.db'),'--daemon' ) -WorkingDirectory $release -WindowStyle Hidden -PassThru ` -RedirectStandardOutput (Join-Path $probe 'stdout.log') -RedirectStandardError (Join-Path $probe 'stderr.log') try { $ready = $false for ($attempt=0; $attempt -lt 30; $attempt++) { if ($probeProcess.HasExited) { throw 'Packaged server exited.' } try { $health = Invoke-RestMethod -Uri "http://127.0.0.1:$Port/health" -TimeoutSec 1 if ($health.service -eq 'aiserver') { $ready = $true; break } } catch { } Start-Sleep -Milliseconds 200 } if (-not $ready) { throw 'Packaged server did not become ready.' } & (Join-Path $release 'Test-Server.ps1') -BaseUrl "http://127.0.0.1:$Port" -ExerciseAccount & (Join-Path $release 'Install-StartupTask.ps1') -Port $Port -WhatIf if ($CheckSamePortBind) { $nic = Get-NetIPAddress -AddressFamily IPv4 | Where-Object { $_.AddressState -eq 'Preferred' -and $_.IPAddress -notlike '127.*' -and $_.IPAddress -notlike '169.254.*' } | Select-Object -First 1 if (-not $nic) { throw 'No non-loopback IPv4 NIC available for bind verification.' } & (Join-Path $release 'New-CaddySite.ps1') -ListenIp $nic.IPAddress -OutputPath (Join-Path $probe 'BuddyDefense.caddyfile') $listener = New-Object Net.Sockets.TcpListener([Net.IPAddress]::Parse($nic.IPAddress),$Port) $listener.Server.ExclusiveAddressUse = $true try { $listener.Start(); Write-Output "PASS distinct loopback/NIC listeners share port $Port." } finally { $listener.Stop() } } $result = @{passed=$true; scriptSyntax=$true; health=$true; unauthenticatedState401=$true; registration=$true; login=$true; state=$true; websocket=$true; startupTaskDryRun=$true; isolatedDatabase=$true; samePortBind=[bool]$CheckSamePortBind; productionModified=$false; release=$release} [IO.File]::WriteAllText((Join-Path $repo 'Tools/output/server_release/smoke.json'), ($result | ConvertTo-Json), (New-Object Text.UTF8Encoding($false))) Write-Output 'PASS packaged release smoke test; isolated database only.' } finally { if (-not $probeProcess.HasExited) { Stop-Process -Id $probeProcess.Id -Force } }