- Add ssh-tunnel.ps1: Windows SSH tunnel manager (equivalent to ssh-tunnel.sh) - Supports password auth via plink.exe (PuTTY) - Supports ssh_hostkey for non-interactive batch mode - Commands: start, stop, restart, status - Add start-backend-service.ps1: NSSM service wrapper - Starts SSH tunnels before uvicorn - Waits for tunnel ports to be accessible (30s timeout) - Configured by Install-ROA2WEB.ps1 - Add start.ps1: Windows equivalent of start.sh - Orchestrates SSH tunnel + backend + frontend startup - Add backend/shared/ssh_tunnel_manager.py: Python monitoring - Background asyncio task monitors tunnel health every 30s - Auto-restarts tunnels after 2 consecutive failures - Exposes status to /health endpoint - Update ROA2WEB-Console.ps1: - Add Deploy-Scripts function - Update Update-ServiceToUseVenv to use wrapper script - Fix PowerShell reserved variable ($PID -> $tunnelPid) - Fix script path detection (scripts/ vs deployment/windows/scripts/) - Update README.md with ssh_hostkey documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
392 lines
13 KiB
PowerShell
392 lines
13 KiB
PowerShell
<#
|
|
.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 }
|
|
}
|