Initial project implementation
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
param(
|
||||
[int[]]$PreferredPorts = @(8011, 8010, 8012, 8013, 8014),
|
||||
[switch]$NoBrowser,
|
||||
[switch]$ForceRestart
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Write-Info {
|
||||
param([string]$Message)
|
||||
Write-Host "[launcher] $Message"
|
||||
}
|
||||
|
||||
function Get-UvPath {
|
||||
$command = Get-Command uv -ErrorAction SilentlyContinue
|
||||
if ($command -and $command.Source) {
|
||||
return $command.Source
|
||||
}
|
||||
|
||||
$fallback = Join-Path $HOME ".local\bin\uv.exe"
|
||||
if (Test-Path -LiteralPath $fallback) {
|
||||
return $fallback
|
||||
}
|
||||
|
||||
throw "uv.exe was not found. Install uv first or add it to PATH."
|
||||
}
|
||||
|
||||
function Test-Health {
|
||||
param([int]$Port)
|
||||
|
||||
try {
|
||||
$null = Invoke-RestMethod -Uri "http://127.0.0.1:$Port/api/health" -TimeoutSec 2
|
||||
return $true
|
||||
} catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Get-FreePort {
|
||||
param([int[]]$Candidates)
|
||||
|
||||
foreach ($port in $Candidates) {
|
||||
$listener = $null
|
||||
try {
|
||||
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, $port)
|
||||
$listener.Start()
|
||||
return $port
|
||||
} catch {
|
||||
continue
|
||||
} finally {
|
||||
if ($listener) {
|
||||
$listener.Stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw "No free port was found in: $($Candidates -join ', ')"
|
||||
}
|
||||
|
||||
function Get-RunningProcess {
|
||||
param([int]$Id)
|
||||
|
||||
if ($Id -le 0) {
|
||||
return $null
|
||||
}
|
||||
|
||||
try {
|
||||
return Get-Process -Id $Id -ErrorAction Stop
|
||||
} catch {
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
function Load-State {
|
||||
param([string]$Path)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
try {
|
||||
return Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
|
||||
} catch {
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
function Save-State {
|
||||
param(
|
||||
[string]$Path,
|
||||
[int]$ProcessId,
|
||||
[int]$Port,
|
||||
[string]$Url
|
||||
)
|
||||
|
||||
$payload = [ordered]@{
|
||||
pid = $ProcessId
|
||||
port = $Port
|
||||
url = $Url
|
||||
started_at = (Get-Date).ToString("s")
|
||||
}
|
||||
|
||||
$payload | ConvertTo-Json | Set-Content -LiteralPath $Path -Encoding UTF8
|
||||
}
|
||||
|
||||
function Remove-State {
|
||||
param([string]$Path)
|
||||
|
||||
if (Test-Path -LiteralPath $Path) {
|
||||
Remove-Item -LiteralPath $Path -Force
|
||||
}
|
||||
}
|
||||
|
||||
$projectRoot = Split-Path -Parent $PSCommandPath
|
||||
$runtimeDir = Join-Path $projectRoot ".s1dl"
|
||||
$statePath = Join-Path $runtimeDir "web-ui-state.json"
|
||||
$stdoutPath = Join-Path $runtimeDir "web-ui.stdout.log"
|
||||
$stderrPath = Join-Path $runtimeDir "web-ui.stderr.log"
|
||||
|
||||
New-Item -ItemType Directory -Path $runtimeDir -Force | Out-Null
|
||||
|
||||
$state = Load-State -Path $statePath
|
||||
|
||||
if ($state -and -not $ForceRestart) {
|
||||
$existingProcess = Get-RunningProcess -Id ([int]$state.pid)
|
||||
if ($existingProcess -and (Test-Health -Port ([int]$state.port))) {
|
||||
$existingUrl = "http://127.0.0.1:$($state.port)/"
|
||||
Write-Info "UI is already running at $existingUrl"
|
||||
if (-not $NoBrowser) {
|
||||
Start-Process $existingUrl | Out-Null
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($port in $PreferredPorts) {
|
||||
if (Test-Health -Port $port) {
|
||||
$url = "http://127.0.0.1:$port/"
|
||||
Write-Info "Found an existing running UI at $url"
|
||||
Save-State -Path $statePath -ProcessId 0 -Port $port -Url $url
|
||||
if (-not $NoBrowser) {
|
||||
Start-Process $url | Out-Null
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
if ($ForceRestart -and $state) {
|
||||
$oldProcess = Get-RunningProcess -Id ([int]$state.pid)
|
||||
if ($oldProcess) {
|
||||
Write-Info "Stopping previous tracked process $($oldProcess.Id)"
|
||||
Stop-Process -Id $oldProcess.Id -Force
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
Remove-State -Path $statePath
|
||||
}
|
||||
|
||||
$uvPath = Get-UvPath
|
||||
Write-Info "Using uv at $uvPath"
|
||||
Write-Info "Syncing project environment"
|
||||
& $uvPath sync --directory $projectRoot
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "uv sync failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
|
||||
$pythonPath = Join-Path $projectRoot ".venv\Scripts\python.exe"
|
||||
if (-not (Test-Path -LiteralPath $pythonPath)) {
|
||||
throw "Python executable was not found at $pythonPath"
|
||||
}
|
||||
|
||||
$portToUse = Get-FreePort -Candidates $PreferredPorts
|
||||
$url = "http://127.0.0.1:$portToUse/"
|
||||
|
||||
if (Test-Path -LiteralPath $stdoutPath) {
|
||||
Remove-Item -LiteralPath $stdoutPath -Force
|
||||
}
|
||||
if (Test-Path -LiteralPath $stderrPath) {
|
||||
Remove-Item -LiteralPath $stderrPath -Force
|
||||
}
|
||||
|
||||
$arguments = @(
|
||||
"-m",
|
||||
"uvicorn",
|
||||
"sentinel_orbit_downloader.api.app:app",
|
||||
"--host",
|
||||
"127.0.0.1",
|
||||
"--port",
|
||||
"$portToUse"
|
||||
)
|
||||
|
||||
Write-Info "Starting UI at $url"
|
||||
$process = Start-Process `
|
||||
-FilePath $pythonPath `
|
||||
-ArgumentList $arguments `
|
||||
-WorkingDirectory $projectRoot `
|
||||
-WindowStyle Hidden `
|
||||
-RedirectStandardOutput $stdoutPath `
|
||||
-RedirectStandardError $stderrPath `
|
||||
-PassThru
|
||||
|
||||
$started = $false
|
||||
for ($i = 0; $i -lt 30; $i++) {
|
||||
Start-Sleep -Seconds 1
|
||||
if (Test-Health -Port $portToUse) {
|
||||
$started = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $started) {
|
||||
$running = Get-RunningProcess -Id $process.Id
|
||||
if ($running) {
|
||||
Stop-Process -Id $running.Id -Force
|
||||
}
|
||||
|
||||
$stderrTail = ""
|
||||
if (Test-Path -LiteralPath $stderrPath) {
|
||||
$stderrTail = (Get-Content -LiteralPath $stderrPath -Tail 20) -join [Environment]::NewLine
|
||||
}
|
||||
|
||||
if ($stderrTail) {
|
||||
throw "UI failed to start.`n$stderrTail"
|
||||
}
|
||||
|
||||
throw "UI failed to start."
|
||||
}
|
||||
|
||||
Save-State -Path $statePath -ProcessId $process.Id -Port $portToUse -Url $url
|
||||
Write-Info "UI is ready: $url"
|
||||
|
||||
if (-not $NoBrowser) {
|
||||
Start-Process $url | Out-Null
|
||||
}
|
||||
Reference in New Issue
Block a user