# 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"