55 lines
3.5 KiB
PowerShell
55 lines
3.5 KiB
PowerShell
[CmdletBinding(SupportsShouldProcess)]
|
|
param(
|
|
[ValidateRange(1,65535)][int]$Port = 15888,
|
|
[string]$TaskName = 'AIGame-AIServer',
|
|
[switch]$NoStart
|
|
)
|
|
$ErrorActionPreference = 'Stop'
|
|
$serverRoot = $PSScriptRoot
|
|
$executable = Join-Path $serverRoot 'aiserver.exe'
|
|
if (-not (Test-Path -LiteralPath $executable)) { throw 'Unzip the complete release before installing.' }
|
|
# Caddy may use this port on a NIC, but must not occupy wildcard/loopback addresses.
|
|
$listeners = @(Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue)
|
|
foreach ($listener in $listeners) {
|
|
if ($listener.LocalAddress -notin @('0.0.0.0','::','127.0.0.1')) { continue }
|
|
$owner = Get-CimInstance Win32_Process -Filter ("ProcessId={0}" -f $listener.OwningProcess)
|
|
if ($listener.LocalAddress -eq '127.0.0.1' -and $owner.ExecutablePath -and
|
|
[IO.Path]::GetFullPath($owner.ExecutablePath) -eq [IO.Path]::GetFullPath($executable)) { continue }
|
|
throw "Port $Port at $($listener.LocalAddress) is occupied by $($owner.Name) (PID $($listener.OwningProcess)). Configure Caddy bind to the NIC IPv4, validate/reload Caddy, then retry."
|
|
}
|
|
$powershell = Join-Path $env:SystemRoot 'System32/WindowsPowerShell/v1.0/powershell.exe'
|
|
$runner = Join-Path $serverRoot 'Run-Server.ps1'
|
|
$arguments = '-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "{0}" -Port {1}' -f $runner,$Port
|
|
$action = New-ScheduledTaskAction -Execute $powershell -Argument $arguments -WorkingDirectory $serverRoot
|
|
$trigger = New-ScheduledTaskTrigger -AtStartup
|
|
$principal = New-ScheduledTaskPrincipal -UserId 'S-1-5-19' -LogonType ServiceAccount -RunLevel Limited
|
|
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) `
|
|
-RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -MultipleInstances IgnoreNew `
|
|
-StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
|
if ($PSCmdlet.ShouldProcess($TaskName, "Register AIServer startup task on loopback port $Port")) {
|
|
$data = Join-Path $serverRoot 'data'; $logs = Join-Path $serverRoot 'logs'
|
|
New-Item -ItemType Directory -Path $data,$logs -Force | Out-Null
|
|
& icacls.exe $serverRoot /grant '*S-1-5-19:(OI)(CI)RX' | Out-Null
|
|
if ($LASTEXITCODE -ne 0) { throw 'Cannot grant executable read permission to Local Service.' }
|
|
foreach ($directory in @($data,$logs)) {
|
|
& icacls.exe $directory /grant '*S-1-5-19:(OI)(CI)M' | Out-Null
|
|
if ($LASTEXITCODE -ne 0) { throw "Cannot grant write permission: $directory" }
|
|
}
|
|
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal `
|
|
-Settings $settings -Description 'AIGame HTTP, gameplay and WebSocket server' -Force | Out-Null
|
|
if ($NoStart) { Write-Output "Installed task $TaskName; not started (-NoStart)."; return }
|
|
Start-ScheduledTask -TaskName $TaskName
|
|
$healthy = $false
|
|
for ($attempt=0; $attempt -lt 25; $attempt++) {
|
|
try {
|
|
$health = Invoke-RestMethod -Uri "http://127.0.0.1:$Port/health" -TimeoutSec 1
|
|
if ($health.ok -and $health.service -eq 'aiserver' -and $health.gameProtocol -eq 1) { $healthy=$true; break }
|
|
} catch { }
|
|
Start-Sleep -Milliseconds 200
|
|
}
|
|
if (-not $healthy) {
|
|
throw "Task $TaskName was registered but AIServer is not healthy on 127.0.0.1:$Port. Check logs/ and Get-ScheduledTaskInfo -TaskName $TaskName. An HTTP 400 may mean this port is still handled by Caddy HTTPS."
|
|
}
|
|
Write-Output "AIServer healthy at http://127.0.0.1:$Port/health; startup task $TaskName installed."
|
|
}
|