Files
insar-management-system-v2/scripts/start_landsar_cluster_worker.ps1
T

289 lines
9.8 KiB
PowerShell

param(
[string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path,
[string]$PythonPath = "",
[string]$WorkerId = "",
[switch]$Background
)
$ErrorActionPreference = "Stop"
function Read-DotEnvValue {
param(
[string]$Path,
[string]$Name
)
if (-not (Test-Path -LiteralPath $Path)) {
return ""
}
$line = Get-Content -LiteralPath $Path |
Where-Object { $_ -match "^\s*$([regex]::Escape($Name))\s*=" } |
Select-Object -Last 1
if (-not $line) {
return ""
}
$value = ($line -split "=", 2)[1].Trim()
if (($value.StartsWith('"') -and $value.EndsWith('"')) -or ($value.StartsWith("'") -and $value.EndsWith("'"))) {
$value = $value.Substring(1, $value.Length - 2)
}
return $value
}
function Resolve-PythonPath {
param(
[string]$ExplicitPath,
[string]$EnvPath
)
$candidates = @(
$ExplicitPath,
$EnvPath,
"C:\ProgramData\anaconda3\envs\InSAR\python.exe",
"python.exe"
) | Where-Object { $_ -and $_.Trim() } | Select-Object -Unique
foreach ($candidate in $candidates) {
if (Test-Path -LiteralPath $candidate) {
return (Resolve-Path -LiteralPath $candidate).Path
}
$command = Get-Command $candidate -ErrorAction SilentlyContinue
if ($command) {
return $command.Source
}
}
throw "Python interpreter not found. Set PYTHON_PATH in .env or pass -PythonPath."
}
function Read-BoolDotEnvValue {
param(
[string]$Path,
[string]$Name,
[bool]$DefaultValue
)
$value = Read-DotEnvValue -Path $Path -Name $Name
if (-not $value) {
return $DefaultValue
}
return @("1", "true", "yes", "on") -contains $value.Trim().ToLowerInvariant()
}
function Read-IntDotEnvValue {
param(
[string]$Path,
[string]$Name,
[int]$DefaultValue
)
$value = Read-DotEnvValue -Path $Path -Name $Name
if (-not $value) {
return $DefaultValue
}
$parsed = 0
if ([int]::TryParse($value.Trim(), [ref]$parsed)) {
return $parsed
}
return $DefaultValue
}
function Test-TcpPort {
param(
[string]$HostName,
[int]$Port
)
try {
$client = [System.Net.Sockets.TcpClient]::new()
$async = $client.BeginConnect($HostName, $Port, $null, $null)
$ok = $async.AsyncWaitHandle.WaitOne(1000, $false)
if ($ok) {
$client.EndConnect($async)
}
$client.Close()
return $ok
} catch {
return $false
}
}
function Get-LandSARConfigEndpoint {
param(
[string]$Path
)
$row = Read-DotEnvValue -Path $Path -Name "LANDSAR_CONFIG_ROW"
if ($row) {
$parts = $row.Split(",") | ForEach-Object { $_.Trim() }
if ($parts.Count -ge 4) {
$port = 6666
[void][int]::TryParse($parts[3], [ref]$port)
return [pscustomobject]@{
Mode = $parts[0]
Host = $(if ($parts[2]) { $parts[2] } else { "127.0.0.1" })
Port = $port
}
}
}
return [pscustomobject]@{
Mode = $(Read-DotEnvValue -Path $Path -Name "LANDSAR_LICENSE_MODE")
Host = $(Read-DotEnvValue -Path $Path -Name "LANDSAR_LICENSE_HOST")
Port = $(Read-IntDotEnvValue -Path $Path -Name "LANDSAR_LICENSE_PORT" -DefaultValue 6666)
}
}
function Resolve-LandSARAuthServerPath {
param(
[string]$RepoRoot,
[string]$EnvPath
)
$explicit = Read-DotEnvValue -Path $EnvPath -Name "LANDSAR_AUTH_SERVER_EXE"
$candidates = @(
$explicit,
(Join-Path $RepoRoot "third_party\LandSAR\tools\_portable_release\LandSAR_auth_tools_win64\landsar_net_auth_server.exe"),
(Join-Path $RepoRoot "third_party\LandSAR\landsar_net_auth_server.exe")
) | Where-Object { $_ -and $_.Trim() } | Select-Object -Unique
foreach ($candidate in $candidates) {
if (Test-Path -LiteralPath $candidate) {
return (Resolve-Path -LiteralPath $candidate).Path
}
}
return $candidates | Select-Object -First 1
}
function Start-LandSARAuthServerIfNeeded {
param(
[string]$RepoRoot,
[string]$EnvPath
)
$endpoint = Get-LandSARConfigEndpoint -Path $EnvPath
$mode = $(if ($endpoint.Mode) { $endpoint.Mode } else { "netVersion" })
if ($mode.Trim().ToLowerInvariant() -ne "netversion") {
Write-Host "LandSAR auth: skipped for license mode $mode"
return
}
$clientHost = $(if ($endpoint.Host) { $endpoint.Host } else { "127.0.0.1" })
$clientPort = [int]$endpoint.Port
if (Test-TcpPort -HostName $clientHost -Port $clientPort) {
Write-Host "LandSAR auth: already listening on $clientHost`:$clientPort"
return
}
$autoStart = Read-BoolDotEnvValue -Path $EnvPath -Name "LANDSAR_AUTH_SERVER_AUTO_START" -DefaultValue $true
if (-not $autoStart) {
throw "LandSAR auth server is not listening on $clientHost`:$clientPort and LANDSAR_AUTH_SERVER_AUTO_START=false."
}
$authExe = Resolve-LandSARAuthServerPath -RepoRoot $RepoRoot -EnvPath $EnvPath
if (-not $authExe -or -not (Test-Path -LiteralPath $authExe)) {
throw "LandSAR auth server executable not found: $authExe. Set LANDSAR_AUTH_SERVER_EXE in .env."
}
$serverDir = Split-Path -Parent $authExe
$memoryBin = Join-Path $serverDir "dongle_0xa0.bin"
if (-not (Test-Path -LiteralPath $memoryBin)) {
$fallbackBin = Join-Path $RepoRoot "third_party\LandSAR\tools\dongle_0xa0.bin"
if (Test-Path -LiteralPath $fallbackBin) {
Copy-Item -LiteralPath $fallbackBin -Destination $memoryBin -Force
} else {
throw "LandSAR auth memory image missing: $memoryBin"
}
}
$bindHost = Read-DotEnvValue -Path $EnvPath -Name "LANDSAR_AUTH_SERVER_HOST"
if (-not $bindHost) {
$bindHost = $clientHost
}
$bindPort = Read-IntDotEnvValue -Path $EnvPath -Name "LANDSAR_AUTH_SERVER_PORT" -DefaultValue $clientPort
Write-Host "LandSAR auth: starting $authExe on $bindHost`:$bindPort"
Start-Process `
-FilePath $authExe `
-ArgumentList @("--host", $bindHost, "--port", [string]$bindPort) `
-WorkingDirectory $serverDir `
-WindowStyle Hidden | Out-Null
$deadline = (Get-Date).AddSeconds(5)
while ((Get-Date) -lt $deadline) {
if (Test-TcpPort -HostName $clientHost -Port $clientPort) {
Write-Host "LandSAR auth: started and reachable on $clientHost`:$clientPort"
return
}
Start-Sleep -Milliseconds 250
}
throw "LandSAR auth server was started but $clientHost`:$clientPort is not reachable."
}
$RepoRoot = (Resolve-Path -LiteralPath $RepoRoot).Path
$envPath = Join-Path $RepoRoot ".env"
$templatePath = Join-Path $RepoRoot "config\landsar_cluster_worker.env.example"
$workerScript = Join-Path $RepoRoot "run_landsar_cluster_worker.py"
if (-not (Test-Path -LiteralPath $workerScript)) {
throw "Worker script not found: $workerScript"
}
if (-not (Test-Path -LiteralPath $envPath)) {
if (Test-Path -LiteralPath $templatePath) {
Copy-Item -LiteralPath $templatePath -Destination $envPath
throw ".env was created from config\landsar_cluster_worker.env.example. Review paths and credentials, then run this launcher again."
}
throw ".env not found: $envPath"
}
$dotenvPythonPath = Read-DotEnvValue -Path $envPath -Name "PYTHON_PATH"
$python = Resolve-PythonPath -ExplicitPath $PythonPath -EnvPath $dotenvPythonPath
$allowedTypes = Read-DotEnvValue -Path $envPath -Name "JOB_WORKER_ALLOWED_TYPES"
if (-not $allowedTypes) {
$env:JOB_WORKER_ALLOWED_TYPES = "LANDSAR_CLUSTER_ITEM"
}
$clusterToken = Read-DotEnvValue -Path $envPath -Name "CLUSTER_SHARED_TOKEN"
if (-not $clusterToken) {
throw "CLUSTER_SHARED_TOKEN is required for LandSAR cluster input download and result upload."
}
$concurrency = Read-DotEnvValue -Path $envPath -Name "JOB_WORKER_CONCURRENCY"
if (-not $concurrency) {
$env:JOB_WORKER_CONCURRENCY = "1"
}
if ($WorkerId) {
$env:LANDSAR_CLUSTER_WORKER_ID = $WorkerId
}
$logDir = Join-Path $RepoRoot "logs\landsar_cluster_worker"
New-Item -ItemType Directory -Force -Path $logDir | Out-Null
$runtimeDir = Join-Path $RepoRoot "runtime\landsar_cluster_worker"
New-Item -ItemType Directory -Force -Path $runtimeDir | Out-Null
$pidFile = Join-Path $runtimeDir "worker.pid"
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$stdoutLog = Join-Path $logDir "worker_$timestamp.log"
$stderrLog = Join-Path $logDir "worker_$timestamp.err.log"
Write-Host "LandSAR cluster worker launcher"
Write-Host "RepoRoot: $RepoRoot"
Write-Host "Python: $python"
Write-Host "Env: $envPath"
Write-Host "Log: $stdoutLog"
Write-Host "Mode: $(if ($Background) { 'background' } else { 'foreground' })"
Set-Location $RepoRoot
Start-LandSARAuthServerIfNeeded -RepoRoot $RepoRoot -EnvPath $envPath
if ($Background) {
if (Test-Path -LiteralPath $pidFile) {
$existingPid = (Get-Content -LiteralPath $pidFile -ErrorAction SilentlyContinue | Select-Object -First 1).Trim()
if ($existingPid -and (Get-Process -Id ([int]$existingPid) -ErrorAction SilentlyContinue)) {
throw "LandSAR cluster worker already appears to be running. PID=$existingPid. Use scripts\stop_landsar_cluster_worker.ps1 first."
}
}
$process = Start-Process `
-FilePath $python `
-ArgumentList @($workerScript) `
-WorkingDirectory $RepoRoot `
-RedirectStandardOutput $stdoutLog `
-RedirectStandardError $stderrLog `
-WindowStyle Hidden `
-PassThru
Set-Content -LiteralPath $pidFile -Value $process.Id -Encoding ASCII
Write-Host "Started background worker. PID=$($process.Id)"
Write-Host "PID file: $pidFile"
return
}
& $python $workerScript 2>&1 | Tee-Object -FilePath $stdoutLog -Append
exit $LASTEXITCODE