docs: update production workflow design and runtime changes

This commit is contained in:
2026-06-14 18:47:00 +08:00
parent 58a87706ef
commit b1c59051b9
41 changed files with 2042 additions and 134 deletions
+207 -1
View File
@@ -29,6 +29,14 @@ $CondaEnvName = ""
$NginxExe = "C:/nginx-1.29.4/nginx.exe"
$ServerHost = ""
$ServerPort = 18000
$NginxAllowedClientIps = ""
$BackendReadyTimeoutSeconds = 120
$TileServerAutoStart = $false
$TileServerAutoStop = $true
$TileServerRoot = ""
$TileServerStartScript = "start-all.bat"
$TileServerStopScript = "stop-all.bat"
$TileServerUrl = ""
$envLines = Get-Content -LiteralPath "$EnvPath"
foreach ($line in $envLines) {
@@ -43,7 +51,18 @@ foreach ($line in $envLines) {
if ($key -eq "CONDA_EXE") { if ($val) { $CondaExe = $val } }
if ($key -eq "CONDA_ENV_NAME") { if ($val) { $CondaEnvName = $val } }
if ($key -eq "NGINX_PATH") { if ($val) { $NginxExe = $val } }
if ($key -eq "NGINX_ALLOWED_CLIENT_IPS") { $NginxAllowedClientIps = $val }
if ($key -eq "BACKEND_BIND_HOST") { if ($val) { $ServerHost = $val } }
if ($key -eq "BACKEND_READY_TIMEOUT_SECONDS") {
$parsed = 0
if ([int]::TryParse($val, [ref]$parsed) -and $parsed -gt 0) { $BackendReadyTimeoutSeconds = $parsed }
}
if ($key -eq "TILE_SERVER_AUTO_START") { $TileServerAutoStart = $val -match '^(?i)(true|1|yes|on)$' }
if ($key -eq "TILE_SERVER_AUTO_STOP") { $TileServerAutoStop = -not ($val -match '^(?i)(false|0|no|off)$') }
if ($key -eq "TILE_SERVER_ROOT") { if ($val) { $TileServerRoot = $val } }
if ($key -eq "TILE_SERVER_START_SCRIPT") { if ($val) { $TileServerStartScript = $val } }
if ($key -eq "TILE_SERVER_STOP_SCRIPT") { if ($val) { $TileServerStopScript = $val } }
if ($key -eq "VITE_TILE_SERVER_URL") { if ($val) { $TileServerUrl = $val.TrimEnd("/") } }
if ($key -eq "PORT") {
$parsed = 0
if ([int]::TryParse($val, [ref]$parsed)) { $ServerPort = $parsed }
@@ -315,11 +334,140 @@ function Test-PortAvailable {
}
}
function Write-NginxClientAllowFile {
param([string]$Path)
$entries = @()
$raw = "$NginxAllowedClientIps".Trim()
if ($raw) {
$entries = @($raw -split '[;,\s]+' | ForEach-Object { "$_".Trim() } | Where-Object { $_ })
}
$lines = @(
"# Generated by scripts/start_app.ps1.",
"# Configure NGINX_ALLOWED_CLIENT_IPS in .env. Empty means allow all clients."
)
if ($entries.Count -gt 0) {
$allowed = @("127.0.0.1", "::1") + $entries
$allowed = @($allowed | Sort-Object -Unique)
foreach ($item in $allowed) {
$lines += " allow $item;"
}
$lines += " deny all;"
}
$dir = Split-Path -Parent "$Path"
if (-not (Test-Path -LiteralPath "$dir")) {
New-Item -ItemType Directory -Path "$dir" -Force | Out-Null
}
$Utf8NoBom = New-Object System.Text.UTF8Encoding $false
[System.IO.File]::WriteAllText("$Path", (($lines -join [Environment]::NewLine) + [Environment]::NewLine), $Utf8NoBom)
if ($entries.Count -gt 0) {
Write-Host ">>> Nginx client IP whitelist enabled: $($entries -join ', ')" -ForegroundColor Yellow
} else {
Write-Host ">>> Nginx client IP whitelist disabled." -ForegroundColor DarkGray
}
}
function Test-TileServerReady {
if (-not $TileServerUrl) {
return $false
}
try {
$response = Invoke-WebRequest -Uri "$TileServerUrl/health" -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop
return ($response.StatusCode -ge 200 -and $response.StatusCode -lt 500)
} catch {
return $false
}
}
function Get-TileServerScriptPath {
param([string]$ScriptName)
$root = "$TileServerRoot".Trim().Trim('"').Trim("'")
if (-not $root) {
return $null
}
$script = "$ScriptName".Trim().Trim('"').Trim("'")
if (-not $script) {
return $null
}
if ([System.IO.Path]::IsPathRooted($script)) {
return $script
}
return (Join-Path -Path $root -ChildPath $script)
}
function Stop-TileServer {
if (-not $TileServerAutoStop) {
return
}
$stopScript = Get-TileServerScriptPath -ScriptName "$TileServerStopScript"
if (-not $stopScript -or -not (Test-Path -LiteralPath "$stopScript")) {
return
}
Write-Host ">>> Stopping tile-server..." -ForegroundColor Yellow
$previousNoPause = $env:NO_PAUSE
try {
$env:NO_PAUSE = "1"
& "$stopScript"
} catch {
Write-Warning "tile-server stop failed: $($_.Exception.Message)"
} finally {
if ($null -eq $previousNoPause) {
Remove-Item Env:\NO_PAUSE -ErrorAction SilentlyContinue
} else {
$env:NO_PAUSE = $previousNoPause
}
}
}
function Start-TileServer {
if (-not $TileServerAutoStart) {
return
}
if (Test-TileServerReady) {
Write-Host ">>> tile-server already responding: $TileServerUrl" -ForegroundColor Green
return
}
$startScript = Get-TileServerScriptPath -ScriptName "$TileServerStartScript"
if (-not $startScript -or -not (Test-Path -LiteralPath "$startScript")) {
Write-Error "tile-server start script not found. TILE_SERVER_ROOT=$TileServerRoot TILE_SERVER_START_SCRIPT=$TileServerStartScript"
$global:LASTEXITCODE = 1
return
}
Write-Host ">>> Launching tile-server..." -ForegroundColor Green
$previousNoPause = $env:NO_PAUSE
try {
$env:NO_PAUSE = "1"
& "$startScript"
} finally {
if ($null -eq $previousNoPause) {
Remove-Item Env:\NO_PAUSE -ErrorAction SilentlyContinue
} else {
$env:NO_PAUSE = $previousNoPause
}
}
for ($i = 0; $i -lt 20; $i++) {
if (Test-TileServerReady) {
Write-Host ">>> tile-server ready: $TileServerUrl" -ForegroundColor Green
return
}
Start-Sleep -Milliseconds 500
}
Write-Warning "tile-server was started but did not pass health check: $TileServerUrl/health"
}
Stop-Backend-By-Cmdline -MatchText "run_backend.py"
Stop-Backend-By-Cmdline -MatchText "run_worker.py"
$NginxProcName = Split-Path -Leaf $NginxExe
$NginxProcName = $NginxProcName -replace '\.exe$', ''
Stop-Process-By-Name -Name $NginxProcName -ExeName $NginxExe
if ($TileServerAutoStart -and $TileServerAutoStop) {
Stop-TileServer
}
$PortAvailable = Test-PortAvailable -Port $ServerPort
if (-not $PortAvailable) {
@@ -455,6 +603,38 @@ function Assert-ProcessAlive {
return $true
}
function Wait-BackendReady {
param(
[int]$Port,
[int]$TimeoutSeconds
)
$deadline = (Get-Date).AddSeconds([Math]::Max(1, $TimeoutSeconds))
# Use the lightweight root route for readiness. /api/health runs a full
# operational self-check and can legitimately take longer during startup.
$url = "http://127.0.0.1:$Port/"
$lastError = $null
Write-Host ">>> Waiting for backend readiness: $url" -ForegroundColor Yellow
while ((Get-Date) -lt $deadline) {
try {
$response = Invoke-WebRequest -Uri "$url" -UseBasicParsing -TimeoutSec 3 -ErrorAction Stop
if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 500) {
Write-Host ">>> Backend ready (status=$($response.StatusCode))." -ForegroundColor Green
return $true
}
$lastError = "HTTP $($response.StatusCode)"
} catch {
$lastError = $_.Exception.Message
}
Start-Sleep -Milliseconds 800
}
Write-Error "Backend did not become ready within $TimeoutSeconds seconds. Last error: $lastError"
$global:LASTEXITCODE = 1
return $false
}
Invoke-PythonScript -ScriptPath "$CheckRuntimeScript"
if ($LastExitCode -ne 0) {
Write-Host "`n[ERROR] Deployment configuration check failed." -ForegroundColor Red
@@ -506,16 +686,29 @@ if (Test-Path -LiteralPath "$NginxConfPath") {
$ForwardRoot = $ProjectRoot.Replace([char]92, [char]47)
$FrontendDist = "$ForwardRoot/frontend/dist"
$ImageCache = "$ForwardRoot/backend/image_cache"
$NginxClientAllowFile = Join-Path -Path $ProjectRoot -ChildPath "nginx\client_allow.conf"
if (-not (Test-Path -LiteralPath "$ProjectRoot/backend/image_cache")) {
New-Item -ItemType Directory -Path "$ProjectRoot/backend/image_cache" -Force | Out-Null
}
Write-NginxClientAllowFile -Path "$NginxClientAllowFile"
$ConfContent = Get-Content -LiteralPath "$NginxConfPath" -Raw
$NewConfContent = $ConfContent -replace 'root\s+[^;]+;', "root `"$FrontendDist`";"
$NewConfContent = $NewConfContent -replace 'alias\s+[^;]+;', "alias `"$ImageCache/`";"
$ClientAllowForwardPath = "$ForwardRoot/nginx/client_allow.conf"
$NewConfContent = $NewConfContent -replace 'include\s+"[^"]*client_allow\.conf";', "include `"$ClientAllowForwardPath`";"
$BackendProxy = "http://127.0.0.1:$ServerPort"
$NewConfContent = $NewConfContent -replace 'proxy_pass\s+http://(127\.0\.0\.1|localhost):\d+;', "proxy_pass $BackendProxy;"
$NewConfContent = [regex]::Replace(
$NewConfContent,
'(location\s+/api/\s*\{[\s\S]*?proxy_pass\s+)http://(127\.0\.0\.1|localhost):\d+(;)',
"`${1}$BackendProxy`${3}"
)
$NewConfContent = [regex]::Replace(
$NewConfContent,
'(location\s+/api/tasks/active/stream\s*\{[\s\S]*?proxy_pass\s+)http://(127\.0\.0\.1|localhost):\d+(;)',
"`${1}$BackendProxy`${3}"
)
# 使用 UTF8 无 BOM 编码写入
$Utf8NoBom = New-Object System.Text.UTF8Encoding $false
[System.IO.File]::WriteAllText("$NginxConfPath", $NewConfContent, $Utf8NoBom)
@@ -533,6 +726,9 @@ $BackendProc = Start-PythonBackground -ScriptPath "run_backend.py"
if (-not (Assert-ProcessAlive -Process $BackendProc -DisplayName "Backend")) {
return
}
if (-not (Wait-BackendReady -Port $ServerPort -TimeoutSeconds $BackendReadyTimeoutSeconds)) {
return
}
# 6.5 Start job worker
Write-Host ">>> Launching job worker..." -ForegroundColor Green
@@ -541,6 +737,12 @@ if (-not (Assert-ProcessAlive -Process $WorkerProc -DisplayName "Worker")) {
return
}
# 6.6 Start tile-server
Start-TileServer
if ($global:LASTEXITCODE -eq 1) {
return
}
# 7. Start Nginx
if (Test-Path -LiteralPath "$NginxExe") {
Write-Host ">>> Launching Nginx..." -ForegroundColor Green
@@ -575,6 +777,9 @@ if (Test-Path -LiteralPath "$NginxExe") {
Write-StatusLine "Frontend (via Nginx): http://$DisplayHost"
Write-StatusLine "Backend (internal): http://127.0.0.1`:$ServerPort"
Write-StatusLine "API Docs (internal): http://127.0.0.1`:$ServerPort/docs"
if ($TileServerAutoStart -and $TileServerUrl) {
Write-StatusLine "Tile Server: $TileServerUrl"
}
Write-StatusLine "============================================================" Green
Write-Host ""
Write-StatusLine "System is running. Press Ctrl+C to stop all services." Yellow
@@ -599,5 +804,6 @@ try {
Stop-Backend-By-Cmdline -MatchText "run_backend.py"
Stop-Backend-By-Cmdline -MatchText "run_worker.py"
Stop-Process-By-Name -Name $NginxProcName -ExeName $NginxExe
Stop-TileServer
Write-Host "Done." -ForegroundColor Green
}