# boot-public-runtime.ps1 # 목적: PC 재부팅/재로그온 후 퍼블릭 런타임을 자동으로 복구한다. # 순서: Docker Desktop 데몬 대기 -> postgres(vignette-dev-db) 기동 -> # 관리자/인증 API 가 이미 healthy 이고 엔진도 healthy 면 스킵, 아니면 # start-public-runtime.ps1(-SkipWebRestart) 로 필요한 프로세스만 복구한다. # 멱등: 어느 단계든 이미 살아있으면 건드리지 않는다. 수동으로 여러 번 실행해도 안전. # # 등록(로그온 시 자동 실행, 숨김 창): # powershell -NoProfile -ExecutionPolicy Bypass -File scripts\register-boot-task.ps1 # 또는 수동: # powershell -NoProfile -ExecutionPolicy Bypass -File scripts\boot-public-runtime.ps1 param( [string]$Workspace = "D:\workspace\vignette", [string]$DockerDesktop = "C:\Program Files\Docker\Docker\Docker Desktop.exe", [int]$DaemonTimeoutSec = 360, [int]$DbTimeoutSec = 90, [int]$DbPort = 55432, [int]$ApiPort = 8001, [string]$BootLog = "D:\workspace\vignette\boot-public-runtime.log" ) $ErrorActionPreference = "Continue" # 부트 스크립트는 끝까지 로깅하고 종료한다. function Write-BootLog([string]$Message) { $line = "[{0}] {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $Message try { Add-Content -LiteralPath $BootLog -Value $line -Encoding UTF8 } catch { # 로그 파일이 잠겨도 부팅은 계속한다. } } function Test-DockerDaemon([int]$TimeoutSec = 10) { try { $ver = & docker.exe info --format '{{.ServerVersion}}' 2>$null if ($LASTEXITCODE -eq 0 -and $ver) { return $true } } catch {} return $false } function Test-Tcp([string]$Host_, [int]$Port) { try { $t = (Test-NetConnection -ComputerName $Host_ -Port $Port -WarningAction SilentlyContinue) return [bool]$t.TcpTestSucceeded } catch { return $false } } function Test-ApiControlPlaneHealthy { # HttpWebRequest + Proxy=$null: WININET/시스템 프록시에 영향받지 않는 가장 직결적인 검사. # 비대화형 스케줄러 컨텍스트에서도 127.0.0.1 로 직접 연결한다. 3회 재시도. for ($i = 1; $i -le 3; $i++) { try { $req = [System.Net.HttpWebRequest]::Create("http://127.0.0.1:$ApiPort/health") $req.Timeout = 5000 $req.ReadWriteTimeout = 5000 $req.Proxy = $null $resp = $req.GetResponse() $reader = New-Object System.IO.StreamReader($resp.GetResponseStream()) $body = $reader.ReadToEnd() $reader.Close(); $resp.Close() $h = $body | ConvertFrom-Json if ($h.environment -eq "prod" -and $h.db -eq $true) { return $true } Write-BootLog (" health probe attempt {0}: not-healthy body={1}" -f $i, $body) return $false } catch { Write-BootLog (" health probe attempt {0} failed: {1}" -f $i, $_.Exception.Message) Start-Sleep -Seconds 2 } } return $false } function Test-EngineHealthy { try { $response = Invoke-RestMethod -Uri "http://127.0.0.1:9099/health" -TimeoutSec 10 return $response.ok -eq $true } catch { return $false } } Write-BootLog "================ boot start ================" # 1) Docker 데몬(내려가 있으면 Docker Desktop 기동 후 대기) if (-not (Test-DockerDaemon)) { Write-BootLog "docker daemon down; launching Docker Desktop" if (Test-Path -LiteralPath $DockerDesktop) { Start-Process -FilePath $DockerDesktop | Out-Null } else { Write-BootLog "ERROR: Docker Desktop.exe not found at $DockerDesktop" exit 1 } $deadline = (Get-Date).AddSeconds($DaemonTimeoutSec) while ((Get-Date) -lt $deadline) { Start-Sleep -Seconds 5 if (Test-DockerDaemon) { break } } } if (-not (Test-DockerDaemon)) { Write-BootLog "ERROR: docker daemon did not come up within ${DaemonTimeoutSec}s" exit 1 } Write-BootLog "docker daemon up" # 2) postgres 컨테이너(restart 정책 백업 + 명시 기동) 후 포트 대기 $null = & docker.exe update --restart unless-stopped vignette-dev-db 2>$null $null = & docker.exe start vignette-dev-db 2>$null $dbDeadline = (Get-Date).AddSeconds($DbTimeoutSec) while ((Get-Date) -lt $dbDeadline) { if (Test-Tcp -Host_ "127.0.0.1" -Port $DbPort) { break } Start-Sleep -Seconds 3 } if (-not (Test-Tcp -Host_ "127.0.0.1" -Port $DbPort)) { Write-BootLog "ERROR: postgres not listening on 127.0.0.1:$DbPort" exit 1 } Write-BootLog "postgres 127.0.0.1:$DbPort up" # 3) 엔진/API/cloudflared — 이미 healthy 면 스킵(불필요한 재시작/다운타임 방지) if ((Test-ApiControlPlaneHealthy) -and (Test-EngineHealthy)) { Write-BootLog "control plane and engine already healthy; skipping runtime restart" } else { $pub = Join-Path $Workspace "scripts\start-public-runtime.ps1" if (-not (Test-Path -LiteralPath $pub)) { Write-BootLog "ERROR: start-public-runtime.ps1 not found at $pub" exit 1 } Write-BootLog "running start-public-runtime.ps1 -SkipWebRestart" $out = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $pub -SkipWebRestart 2>&1 $out | ForEach-Object { Write-BootLog (" pub> " + $_) } if ($LASTEXITCODE -ne 0) { Write-BootLog "ERROR: start-public-runtime.ps1 exit $LASTEXITCODE" exit 1 } } # 4) 최종 확인 if (Test-ApiControlPlaneHealthy) { if (Test-EngineHealthy) { Write-BootLog "boot OK: control plane and engine healthy" } else { Write-BootLog "boot OK: admin/auth control plane healthy; engine remains degraded" } exit 0 } else { Write-BootLog "WARN: boot finished but admin/auth control plane failed — see apps/api/api.public.err.log" exit 2 }