Files
2026-09-27 00:41:00 +08:00

157 lines
5.6 KiB
PowerShell

# MiroSociety background launcher (Windows / PowerShell, dev mode)
# Prereq: run deploy.ps1 first to install dependencies.
# Frontend: http://localhost:31400 Backend: http://localhost:31401
#
# Usage (PowerShell): .\start.ps1
# Already-running services will be restarted.
$ErrorActionPreference = 'Stop'
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$FrontendPort = 31400
$BackendPort = 31401
$VenvDir = Join-Path $ScriptDir 'backend\.venv'
$VenvPy = Join-Path $VenvDir 'Scripts\python.exe'
$BackendDir = Join-Path $ScriptDir 'backend'
$FrontendDir = Join-Path $ScriptDir 'frontend'
$RunDir = Join-Path $ScriptDir '.run'
$BackendPidFile = Join-Path $RunDir 'backend.pid'
$FrontendPidFile = Join-Path $RunDir 'frontend.pid'
$BackendLog = Join-Path $ScriptDir 'backend.log'
$FrontendLog = Join-Path $ScriptDir 'frontend.log'
$BackendLogErr = Join-Path $ScriptDir 'backend.err.log'
$FrontendLogErr = Join-Path $ScriptDir 'frontend.err.log'
Set-Location $ScriptDir
function Log($msg) { Write-Host "[start.ps1] $msg" -ForegroundColor Green }
function Warn($msg) { Write-Host "[start.ps1] $msg" -ForegroundColor Yellow }
function Err($msg) { Write-Host "[start.ps1] $msg" -ForegroundColor Red }
# --- Check dependencies ---
foreach ($cmd in @('npm','node','curl')) {
if (-not (Get-Command $cmd -ErrorAction SilentlyContinue)) {
Err "Missing required command: $cmd"
exit 1
}
}
if (-not (Test-Path $VenvPy)) {
Err "Not found: $VenvPy -- please run deploy.ps1 first."
exit 1
}
try {
& $VenvPy -c "import uvicorn" 2>$null | Out-Null
if ($LASTEXITCODE -ne 0) { throw }
} catch {
Err "uvicorn is missing in venv -- please run deploy.ps1 first."
exit 1
}
if (-not (Test-Path (Join-Path $FrontendDir 'node_modules'))) {
Err "Not found: frontend/node_modules -- please run deploy.ps1 first."
exit 1
}
if (-not (Test-Path $RunDir)) { New-Item -ItemType Directory -Path $RunDir | Out-Null }
# --- Helpers ---
function Get-PidFromFile($file) {
if (Test-Path $file) { return (Get-Content $file -Raw).Trim() }
return $null
}
function Stop-PidFile($pidFile, $label) {
$procId = Get-PidFromFile $pidFile
if ($procId -and (Get-Process -Id $procId -ErrorAction SilentlyContinue)) {
try {
Stop-Process -Id $procId -Force -ErrorAction SilentlyContinue
Log "$label stopped (pid=$procId)"
} catch { }
}
if (Test-Path $pidFile) { Remove-Item $pidFile -Force }
}
function Free-Port($port) {
$conns = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue
if ($conns) {
$pids = ($conns | Select-Object -ExpandProperty OwningProcess -Unique)
Warn "Port $port is occupied (pid: $($pids -join ',' )), killing..."
foreach ($p in $pids) {
try { Stop-Process -Id $p -Force -ErrorAction SilentlyContinue } catch { }
}
Start-Sleep -Seconds 1
}
}
# --- Start backend ---
Free-Port $BackendPort
Stop-PidFile $BackendPidFile 'Backend'
Log "Starting backend FastAPI @ :$BackendPort (background)"
if (Test-Path $BackendLog) { Clear-Content $BackendLog -Force }
if (Test-Path $BackendLogErr) { Clear-Content $BackendLogErr -Force }
$backendArgs = @(
'-m','uvicorn','app.main:app',
'--host','0.0.0.0','--port',"$BackendPort"
)
$backendProc = Start-Process -FilePath $VenvPy `
-ArgumentList $backendArgs `
-WorkingDirectory $BackendDir `
-RedirectStandardOutput $BackendLog `
-RedirectStandardError $BackendLogErr `
-NoNewWindow `
-PassThru
$backendProc.Id | Out-File -FilePath $BackendPidFile -Encoding ascii -Force
# --- Start frontend ---
Free-Port $FrontendPort
Stop-PidFile $FrontendPidFile 'Frontend'
Log "Starting frontend Vite @ :$FrontendPort (background)"
if (Test-Path $FrontendLog) { Clear-Content $FrontendLog -Force }
if (Test-Path $FrontendLogErr) { Clear-Content $FrontendLogErr -Force }
# On Windows, Start-Process cannot launch a `.cmd` shim by bare name.
# Resolve `npx.cmd` -> its underlying `node.exe` and run `node ./node_modules/vite/bin/vite.js`.
$npmCmd = (Get-Command npm.cmd -ErrorAction SilentlyContinue).Source
if (-not $npmCmd) { Err 'Cannot locate npm.cmd in PATH'; exit 1 }
$nodeExe = (Get-Command node -ErrorAction SilentlyContinue).Source
$viteEntry = Join-Path $FrontendDir 'node_modules\vite\bin\vite.js'
if (-not (Test-Path $viteEntry)) {
Err "Not found: $viteEntry -- please run deploy.ps1 first."
exit 1
}
$frontendProc = Start-Process -FilePath $nodeExe `
-ArgumentList @($viteEntry) `
-WorkingDirectory $FrontendDir `
-RedirectStandardOutput $FrontendLog `
-RedirectStandardError $FrontendLogErr `
-NoNewWindow `
-PassThru
$frontendProc.Id | Out-File -FilePath $FrontendPidFile -Encoding ascii -Force
# --- Health check ---
Log "Waiting for services..."
function Wait-For($url, $name) {
for ($i = 0; $i -lt 30; $i++) {
try {
$r = curl.exe -sf $url
if ($LASTEXITCODE -eq 0) {
Log "$name is up (v) ($url)"
return $true
}
} catch { }
Start-Sleep -Seconds 1
}
Err "$name failed to start in time, check $name.log"
return $false
}
Wait-For "http://localhost:$BackendPort/health" 'Backend' | Out-Null
Wait-For "http://localhost:$FrontendPort" 'Frontend' | Out-Null
Write-Host ''
Log "All services ready (v)"
Write-Host " Frontend: http://localhost:$FrontendPort"
Write-Host " Backend : http://localhost:$BackendPort (health: /health)"
Write-Host " Logs : backend.log / backend.err.log / frontend.log / frontend.err.log"