212 lines
6.9 KiB
PowerShell
212 lines
6.9 KiB
PowerShell
<#
|
||
AZA – Clean Local Start (no Docker)
|
||
|
||
Purpose:
|
||
- Ensure port 8000 is free (kills existing listener on :8000)
|
||
- Load auth tokens from deploy\.env
|
||
- Ensure AZA_SECRET_KEY is set (from .env or auto-generated for this run)
|
||
- Start the backend deterministically:
|
||
python -m uvicorn workforce_planner.api.app:app --host 127.0.0.1 --port 8000
|
||
|
||
Run (from project root):
|
||
powershell -ExecutionPolicy Bypass -File .\deploy\local_reset_and_start.ps1
|
||
|
||
Then (in a second terminal):
|
||
cd .\deploy
|
||
powershell -ExecutionPolicy Bypass -File .\authorized_test.ps1
|
||
#>
|
||
|
||
[CmdletBinding()]
|
||
param(
|
||
[string]$ProjectRoot = ".",
|
||
[string]$EnvFile = ".\deploy\.env",
|
||
[string]$BindHost = "127.0.0.1",
|
||
[int]$Port = 8000
|
||
)
|
||
|
||
function Load-DotEnv([string]$Path) {
|
||
if (-not (Test-Path -LiteralPath $Path)) {
|
||
throw "Missing .env file at: $Path"
|
||
}
|
||
$map = @{}
|
||
Get-Content -LiteralPath $Path | ForEach-Object {
|
||
$line = $_.Trim()
|
||
if ($line.Length -eq 0) { return }
|
||
if ($line.StartsWith("#")) { return }
|
||
$idx = $line.IndexOf("=")
|
||
if ($idx -lt 1) { return }
|
||
$k = $line.Substring(0, $idx).Trim()
|
||
$v = $line.Substring($idx + 1).Trim()
|
||
if (($v.StartsWith('"') -and $v.EndsWith('"')) -or ($v.StartsWith("'") -and $v.EndsWith("'"))) {
|
||
$v = $v.Substring(1, $v.Length - 2)
|
||
}
|
||
$map[$k] = $v
|
||
}
|
||
return $map
|
||
}
|
||
|
||
function Kill-PortListener([int]$p) {
|
||
$pids = @()
|
||
|
||
# Preferred (more reliable): Get-NetTCPConnection
|
||
try {
|
||
$conns = Get-NetTCPConnection -LocalPort $p -State Listen -ErrorAction Stop
|
||
foreach ($c in $conns) {
|
||
if ($c.OwningProcess -and $c.OwningProcess -is [int]) { $pids += $c.OwningProcess }
|
||
elseif ($c.OwningProcess -and ($c.OwningProcess.ToString() -match "^\d+$")) { $pids += [int]$c.OwningProcess }
|
||
}
|
||
} catch {
|
||
# Fallback: netstat parsing
|
||
try {
|
||
$lines = & netstat -ano | Select-String -Pattern (":$p\s") | ForEach-Object { $_.Line }
|
||
foreach ($ln in $lines) {
|
||
$parts = ($ln -split "\s+") | Where-Object { $_ -ne "" }
|
||
if ($parts.Count -ge 5) {
|
||
$state = $parts[3]
|
||
$listenerPid = $parts[4]
|
||
if ($state -eq "LISTENING" -and $listenerPid -match "^\d+$") {
|
||
$pids += [int]$listenerPid
|
||
}
|
||
}
|
||
}
|
||
} catch { }
|
||
}
|
||
|
||
$pids = $pids | Select-Object -Unique
|
||
if (-not $pids -or $pids.Count -eq 0) {
|
||
Write-Host "Port ${p}: no LISTENING pid found."
|
||
return
|
||
}
|
||
|
||
foreach ($procId in $pids) {
|
||
try {
|
||
Write-Host "Killing PID $procId on port ${p} ..."
|
||
& taskkill /PID $procId /F | Out-Null
|
||
} catch {
|
||
Write-Host "WARNING: failed to kill PID $procId"
|
||
}
|
||
}
|
||
|
||
Start-Sleep -Milliseconds 600
|
||
|
||
# Re-check
|
||
try {
|
||
$still = Get-NetTCPConnection -LocalPort $p -State Listen -ErrorAction SilentlyContinue
|
||
if ($still) {
|
||
$stillPids = @($still | ForEach-Object { $_.OwningProcess } | Select-Object -Unique)
|
||
Write-Host "WARNING: Port ${p} still has listener(s): $($stillPids -join ', ')"
|
||
} else {
|
||
Write-Host "Port ${p}: listener cleared."
|
||
}
|
||
} catch {
|
||
# ignore
|
||
}
|
||
}
|
||
|
||
Set-Location -LiteralPath $ProjectRoot
|
||
|
||
Write-Host "[AZA] Clean Local Start"
|
||
Write-Host " Root: $(Get-Location)"
|
||
Write-Host " Env: $EnvFile"
|
||
Write-Host " Bind: http://$BindHost`:$Port"
|
||
Write-Host ""
|
||
|
||
# 1) Free the port
|
||
Kill-PortListener -p $Port
|
||
Write-Host ""
|
||
|
||
# 2) Load .env
|
||
try {
|
||
$envMap = Load-DotEnv $EnvFile
|
||
} catch {
|
||
Write-Host "ERROR: $($_.Exception.Message)"
|
||
exit 1
|
||
}
|
||
|
||
# 3) Auth tokens (do NOT print them)
|
||
if ($envMap.ContainsKey("MEDWORK_API_TOKENS")) {
|
||
$env:MEDWORK_API_TOKENS = $envMap["MEDWORK_API_TOKENS"]
|
||
Write-Host " MEDWORK_API_TOKENS: set"
|
||
# Compatibility: some startup checks still require MEDWORK_API_TOKEN (singular).
|
||
$first = ($envMap["MEDWORK_API_TOKENS"] -split "[,\r\n]+" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" } | Select-Object -First 1)
|
||
if ($first) {
|
||
$env:MEDWORK_API_TOKEN = $first
|
||
Write-Host " MEDWORK_API_TOKEN: set (first token, compatibility)"
|
||
}
|
||
} elseif ($envMap.ContainsKey("MEDWORK_API_TOKEN")) {
|
||
$env:MEDWORK_API_TOKEN = $envMap["MEDWORK_API_TOKEN"]
|
||
Write-Host " MEDWORK_API_TOKEN: set"
|
||
$first = $envMap["MEDWORK_API_TOKEN"].Trim()
|
||
} else {
|
||
Write-Host "ERROR: No MEDWORK_API_TOKENS or MEDWORK_API_TOKEN found in $EnvFile"
|
||
exit 1
|
||
}
|
||
|
||
# Ensure backend_token.txt matches the token used for local runs (some code paths may read this file).
|
||
try {
|
||
if ($first -and $first.Trim().Length -gt 0) {
|
||
$tokenFile = ".\backend_token.txt"
|
||
$desired = $first.Trim() + "`n"
|
||
$existing = ""
|
||
if (Test-Path -LiteralPath $tokenFile) {
|
||
try { $existing = (Get-Content -LiteralPath $tokenFile -Raw) } catch { $existing = "" }
|
||
if ($existing -ne $desired) {
|
||
$bak = ".\backend_token.txt.bak"
|
||
try { Copy-Item -LiteralPath $tokenFile -Destination $bak -Force | Out-Null } catch { }
|
||
}
|
||
}
|
||
Set-Content -LiteralPath $tokenFile -Value $desired -Encoding ASCII
|
||
Write-Host " backend_token.txt: synced (local auth consistency)"
|
||
}
|
||
} catch {
|
||
Write-Host "WARN: Could not write backend_token.txt (continuing)."
|
||
}
|
||
|
||
# 4) Secret key (hard guarantee for local run)
|
||
if ($envMap.ContainsKey("AZA_SECRET_KEY") -and $envMap["AZA_SECRET_KEY"].Trim().Length -ge 32) {
|
||
$env:AZA_SECRET_KEY = $envMap["AZA_SECRET_KEY"].Trim()
|
||
Write-Host " AZA_SECRET_KEY: set (from .env)"
|
||
} else {
|
||
$gen = & python -c "import secrets; print(secrets.token_hex(64))"
|
||
if (-not $gen -or $gen.Trim().Length -lt 32) {
|
||
Write-Host "ERROR: Could not generate AZA_SECRET_KEY via python."
|
||
exit 1
|
||
}
|
||
$env:AZA_SECRET_KEY = $gen.Trim()
|
||
Write-Host " AZA_SECRET_KEY: generated for this local run"
|
||
}
|
||
|
||
# Optional: build label if present
|
||
if ($envMap.ContainsKey("AZA_BUILD")) { $env:AZA_BUILD = $envMap["AZA_BUILD"] }
|
||
|
||
Write-Host ""
|
||
Write-Host "Starting backend:"
|
||
Write-Host " python -m uvicorn backend_main:app --host $BindHost --port $Port"
|
||
Write-Host ""
|
||
Write-Host "NOTE: This window must stay open while you run authorized_test.ps1 in a second terminal."
|
||
Write-Host ""
|
||
|
||
# 5) Optionally launch Dev Status window
|
||
$devStatusScript = ".\tools\dev_status_window.py"
|
||
$devStatusEnabled = $true
|
||
if ($envMap.ContainsKey("DEV_STATUS_WINDOW") -and $envMap["DEV_STATUS_WINDOW"] -eq "0") {
|
||
$devStatusEnabled = $false
|
||
}
|
||
if ($devStatusEnabled -and (Test-Path -LiteralPath $devStatusScript)) {
|
||
$Root = (Get-Location).Path
|
||
Start-Process -FilePath "powershell.exe" -ArgumentList @("-NoExit","-ExecutionPolicy","Bypass","-Command","`$host.ui.RawUI.WindowTitle = 'AZA Dev Status'; cd `"$Root`"; python .\tools\dev_status_window.py") -WindowStyle Normal
|
||
Write-Host " Dev status window: started"
|
||
} elseif ($devStatusEnabled) {
|
||
Write-Host " Dev status window: missing ($devStatusScript not found)"
|
||
} else {
|
||
Write-Host " Dev status window: disabled"
|
||
}
|
||
|
||
# 6) Start AZA backend (foreground)
|
||
$target = "backend_main:app"
|
||
Write-Host "Uvicorn target: $target"
|
||
Write-Host ""
|
||
& python -m uvicorn $target --host $BindHost --port $Port
|
||
exit $LASTEXITCODE
|
||
|