<# .SYNOPSIS ROA2WEB - Unified Starter Script for Windows (equivalent to start.sh) .DESCRIPTION Orchestrates startup of all ROA2WEB services on Windows: 1. SSH Tunnels (if needed for environment) 2. Unified Backend (port 8000) 3. Unified Frontend (port 3000) - optional for development This is the Windows equivalent of the Linux start.sh script. .PARAMETER Environment Target environment: prod or test - prod: Uses SSH tunnel to Oracle - test: Direct connection (no tunnel needed) .PARAMETER Action Action to perform: start or stop .PARAMETER SkipFrontend Skip starting the frontend (useful for production where IIS serves static files) .EXAMPLE .\start.ps1 prod Start all services in production mode .EXAMPLE .\start.ps1 test Start all services in test mode .EXAMPLE .\start.ps1 prod stop Stop all services .NOTES Author: ROA2WEB Team Version: 1.0 #> param( [Parameter(Position=0)] [ValidateSet("prod", "production", "test")] [string]$Environment = "prod", [Parameter(Position=1)] [ValidateSet("start", "stop")] [string]$Action = "start", [switch]$SkipFrontend ) $ErrorActionPreference = "Stop" # ============================================================================= # CONFIGURATION # ============================================================================= $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path # Normalize environment name if ($Environment -eq "production") { $Environment = "prod" } $Config = @{ prod = @{ Name = "PROD" EnvFile = ".env.prod" NeedsTunnel = $true LogDir = Join-Path $ScriptDir "logs" } test = @{ Name = "TEST" EnvFile = ".env.test" NeedsTunnel = $false LogDir = $env:TEMP } } $Env = $Config[$Environment] $BackendDir = Join-Path $ScriptDir "backend" $BackendLog = Join-Path $Env.LogDir "backend-stderr.log" $FrontendLog = Join-Path $Env.LogDir "frontend.log" $VenvPath = Join-Path $BackendDir "venv" $VenvPython = Join-Path $VenvPath "Scripts\python.exe" $TunnelScript = Join-Path $ScriptDir "deployment\windows\scripts\ssh-tunnel.ps1" # PID tracking files $PidDir = Join-Path $env:TEMP "roa2web_pids" if (-not (Test-Path $PidDir)) { New-Item -ItemType Directory -Path $PidDir -Force | Out-Null } $BackendPidFile = Join-Path $PidDir "backend.pid" $FrontendPidFile = Join-Path $PidDir "frontend.pid" # ============================================================================= # HELPER FUNCTIONS # ============================================================================= function Write-Header { Write-Host "" Write-Host "============================================" -ForegroundColor Blue Write-Host " ROA2WEB Unified Starter (Windows)" -ForegroundColor Blue Write-Host " Environment: $($Env.Name)" -ForegroundColor Cyan Write-Host "============================================" -ForegroundColor Blue Write-Host "" } function Write-Step { param([string]$Message) Write-Host "[UNIFIED-$($Env.Name)] $Message" -ForegroundColor Cyan } function Write-Success { param([string]$Message) Write-Host "[SUCCESS] $Message" -ForegroundColor Green } function Write-Warning { param([string]$Message) Write-Host "[WARNING] $Message" -ForegroundColor Yellow } function Write-Error { param([string]$Message) Write-Host "[ERROR] $Message" -ForegroundColor Red } function Test-PortOpen { param([int]$Port) try { $tcpClient = New-Object System.Net.Sockets.TcpClient $tcpClient.Connect("127.0.0.1", $Port) $tcpClient.Close() return $true } catch { return $false } } function Stop-ProcessByPidFile { param([string]$PidFile, [string]$Name) if (Test-Path $PidFile) { $pid = Get-Content $PidFile -ErrorAction SilentlyContinue if ($pid) { $process = Get-Process -Id $pid -ErrorAction SilentlyContinue if ($process) { Write-Step "Stopping $Name (PID: $pid)..." Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds 1 } } Remove-Item $PidFile -Force -ErrorAction SilentlyContinue } } # ============================================================================= # STOP SERVICES # ============================================================================= function Stop-AllServices { Write-Header Write-Step "Stopping all services..." Write-Host "" # Stop Backend Write-Step "1. Stopping Backend..." Stop-ProcessByPidFile $BackendPidFile "Backend" # Also kill any uvicorn processes Get-Process -Name "python" -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -like "*uvicorn*main:app*" } | ForEach-Object { Write-Step " Killing uvicorn process (PID: $($_.Id))..." Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue } if (Test-PortOpen 8000) { Write-Warning "Port 8000 still in use, force killing..." $netstat = netstat -ano | Select-String ":8000" | Select-String "LISTENING" if ($netstat) { $pid = ($netstat -split '\s+')[-1] Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue } } # Stop Frontend Write-Step "2. Stopping Frontend..." Stop-ProcessByPidFile $FrontendPidFile "Frontend" Get-Process -Name "node" -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -like "*vite*" } | ForEach-Object { Write-Step " Killing Vite process (PID: $($_.Id))..." Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue } # Stop SSH Tunnels Write-Step "3. Stopping SSH Tunnels..." if (Test-Path $TunnelScript) { & $TunnelScript stop } Write-Host "" Write-Success "All services stopped." exit 0 } # ============================================================================= # START SERVICES # ============================================================================= function Start-AllServices { Write-Header Write-Step "Starting ROA2WEB Ultrathin Monolith..." Write-Host "" # Ensure log directory exists if (-not (Test-Path $Env.LogDir)) { New-Item -ItemType Directory -Path $Env.LogDir -Force | Out-Null } # ------------------------------------------------------------------------- # Step 1: SSH Tunnels # ------------------------------------------------------------------------- Write-Step "1. Checking SSH Tunnels..." if (Test-Path $TunnelScript) { if ($Env.NeedsTunnel) { & $TunnelScript start Write-Success "SSH Tunnels started" Start-Sleep -Seconds 2 } else { Write-Success "$($Env.Name) uses direct connection - no tunnel needed" } } else { Write-Warning "SSH tunnel script not found: $TunnelScript" } # ------------------------------------------------------------------------- # Step 2: Backend # ------------------------------------------------------------------------- Write-Step "2. Starting Unified Backend on port 8000..." if (Test-PortOpen 8000) { Write-Warning "Port 8000 already in use - Backend may be running" } else { # Check venv exists if (-not (Test-Path $VenvPython)) { Write-Step "Creating Python virtual environment..." & python -m venv $VenvPath } # Check env file $envFilePath = Join-Path $BackendDir $Env.EnvFile if (-not (Test-Path $envFilePath)) { Write-Error "$($Env.EnvFile) not found at $envFilePath" exit 1 } # Copy env file to active .env Write-Step "Using $($Env.Name) environment ($($Env.EnvFile))..." Copy-Item -Path $envFilePath -Destination (Join-Path $BackendDir ".env") -Force # Start backend Write-Step "Starting unified backend..." $backendProcess = Start-Process -FilePath $VenvPython ` -ArgumentList "-m", "uvicorn", "main:app", "--host", "127.0.0.1", "--port", "8000", "--workers", "1" ` -WorkingDirectory $BackendDir ` -WindowStyle Hidden ` -RedirectStandardError $BackendLog ` -PassThru $backendProcess.Id | Out-File -FilePath $BackendPidFile -NoNewline # Wait for backend to start Write-Step "Waiting for Backend to initialize..." $maxWait = 45 $elapsed = 0 while ($elapsed -lt $maxWait) { if (Test-PortOpen 8000) { Write-Success "Unified Backend started on http://localhost:8000" break } Start-Sleep -Seconds 1 $elapsed++ if ($elapsed % 5 -eq 0) { Write-Step " Still initializing... ($elapsed/${maxWait}s)" } } if (-not (Test-PortOpen 8000)) { Write-Error "Backend failed to start - check $BackendLog" Get-Content $BackendLog -Tail 30 Stop-AllServices } } # ------------------------------------------------------------------------- # Step 3: Frontend (optional) # ------------------------------------------------------------------------- if (-not $SkipFrontend) { Write-Step "3. Starting Unified Frontend on port 3000..." if (Test-PortOpen 3000) { Write-Warning "Port 3000 already in use - Frontend may be running" } else { # Check node_modules $nodeModules = Join-Path $ScriptDir "node_modules" if (-not (Test-Path $nodeModules)) { Write-Step "Installing Frontend dependencies..." Push-Location $ScriptDir & npm install Pop-Location } # Start frontend Write-Step "Starting Vite development server..." $frontendProcess = Start-Process -FilePath "cmd.exe" ` -ArgumentList "/c", "npm", "run", "dev" ` -WorkingDirectory $ScriptDir ` -WindowStyle Hidden ` -RedirectStandardOutput $FrontendLog ` -PassThru $frontendProcess.Id | Out-File -FilePath $FrontendPidFile -NoNewline # Wait for frontend Write-Step "Waiting for Vite to initialize..." $maxWait = 15 $elapsed = 0 while ($elapsed -lt $maxWait) { if (Test-PortOpen 3000) { Write-Success "Unified Frontend started on http://localhost:3000" break } Start-Sleep -Seconds 2 $elapsed += 2 } if (-not (Test-PortOpen 3000)) { Write-Error "Frontend failed to start - check $FrontendLog" Get-Content $FrontendLog } } } else { Write-Step "3. Skipping Frontend (use IIS for static files)" } # ------------------------------------------------------------------------- # Summary # ------------------------------------------------------------------------- Write-Host "" Write-Success "ROA2WEB Ultrathin Monolith ($($Env.Name)) is now running!" Write-Host "" Write-Host "Services:" -ForegroundColor Blue if ($Env.NeedsTunnel) { Write-Host " * SSH Tunnel: Active (Oracle DB connection)" } else { Write-Host " * Oracle Connection: Direct (no SSH tunnel needed)" } Write-Host " * Unified Backend: http://localhost:8000" Write-Host " +-- Reports API: http://localhost:8000/api/reports/*" Write-Host " +-- Data Entry: http://localhost:8000/api/data-entry/*" Write-Host " +-- Telegram: http://localhost:8000/api/telegram/*" if (-not $SkipFrontend) { Write-Host " * Unified Frontend: http://localhost:3000" } Write-Host "" Write-Host "API Documentation:" -ForegroundColor Blue Write-Host " * API Docs: http://localhost:8000/docs" Write-Host " * Health Check: http://localhost:8000/health" Write-Host "" Write-Host "Log Files:" -ForegroundColor Blue Write-Host " * Backend: $BackendLog" if (-not $SkipFrontend) { Write-Host " * Frontend: $FrontendLog" } Write-Host "" Write-Host "To stop services: .\start.ps1 $Environment stop" -ForegroundColor Yellow Write-Host "" } # ============================================================================= # MAIN # ============================================================================= switch ($Action) { "start" { Start-AllServices } "stop" { Stop-AllServices } }