<# .SYNOPSIS ROA2WEB - Unified Deployment & Management Console for Windows Server .DESCRIPTION Interactive console for deploying and managing ROA2WEB application: - Deploy Backend + Frontend - Deploy Telegram Bot - Deploy all components - Start/Stop/Restart services - Check service status and health - Backup and rollback support .PARAMETER NonInteractive Run in non-interactive mode with specific action .PARAMETER Action Action to perform in non-interactive mode: - DeployBackend: Deploy Backend + Frontend - DeployTelegramBot: Deploy Telegram Bot only - DeployAll: Deploy all components - StartAll: Start all services - StopAll: Stop all services - RestartAll: Restart all services - Status: Show status .EXAMPLE .\ROA2WEB-Console.ps1 Launch interactive menu .EXAMPLE .\ROA2WEB-Console.ps1 -NonInteractive -Action DeployAll Deploy all components without menu .NOTES Author: ROA2WEB Team Version: 2.0 (Unified Console) Requires: Administrator privileges, PowerShell 5.1+ #> [CmdletBinding()] param( [switch]$NonInteractive, [ValidateSet("DeployBackend", "DeployTelegramBot", "DeployAll", "StartAll", "StopAll", "RestartAll", "Status")] [string]$Action = "" ) $ErrorActionPreference = "Stop" # ============================================================================= # GLOBAL CONFIGURATION # ============================================================================= # Auto-detect source path: if running from scripts/ subdirectory, use parent $detectedSourcePath = if ((Split-Path $PSScriptRoot -Leaf) -eq "scripts") { Split-Path $PSScriptRoot -Parent } else { $PSScriptRoot } $script:Config = @{ # General InstallPath = "C:\inetpub\wwwroot\roa2web" SourcePath = $detectedSourcePath BackupPath = "C:\inetpub\wwwroot\roa2web\backups" # Backend BackendPath = "C:\inetpub\wwwroot\roa2web\backend" BackendServiceName = "ROA2WEB-Backend" BackendServiceDisplayName = "ROA2WEB Backend API" BackendServiceDescription = "FastAPI backend service for ROA2WEB application" BackendPort = 8000 BackendHealthUrl = "http://localhost:8000/health" BackendHealthTimeout = 5 # Frontend FrontendPath = "C:\inetpub\wwwroot\roa2web\frontend" # IIS Configuration IISSiteName = "Default Web Site" IISAppName = "roa2web" IISAppPoolName = "ROA2WEB-AppPool" # Telegram Bot TelegramBotPath = "C:\inetpub\wwwroot\roa2web\telegram-bot" TelegramBotServiceName = "ROA2WEB-TelegramBot" TelegramBotServiceDisplayName = "ROA2WEB Telegram Bot" TelegramBotServiceDescription = "Telegram bot frontend for ROA2WEB application" TelegramBotPort = 8002 TelegramBotHealthUrl = "http://localhost:8002/internal/health" TelegramBotHealthTimeout = 10 # Logs LogsPath = "C:\inetpub\wwwroot\roa2web\logs" TelegramBotLogsPath = "C:\inetpub\wwwroot\roa2web\telegram-bot\logs" # Timeouts ServiceTimeout = 30 } # ============================================================================= # HELPER FUNCTIONS # ============================================================================= function Write-Step { param([string]$Message) Write-Host "`n[*] $Message" -ForegroundColor Cyan } function Write-Success { param([string]$Message) Write-Host " [OK] $Message" -ForegroundColor Green } function Write-Error { param([string]$Message) Write-Host " [ERROR] $Message" -ForegroundColor Red } function Write-Warning { param([string]$Message) Write-Host " [WARN] $Message" -ForegroundColor Yellow } function Write-Info { param([string]$Message) Write-Host " [*] $Message" -ForegroundColor Yellow } function Test-Administrator { $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($currentUser) return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } function Test-CommandExists { param([string]$Command) try { if (Get-Command $Command -ErrorAction Stop) { return $true } } catch { return $false } } # ============================================================================= # DETECTION FUNCTIONS - Check if first install or update needed # ============================================================================= function Test-BackendInstalled { Write-Step "Checking if Backend is installed..." $venvPath = Join-Path $Config.BackendPath "venv" $venvExists = Test-Path $venvPath $service = Get-ServiceSafe -ServiceName $Config.BackendServiceName $serviceExists = $null -ne $service $requirementsPath = Join-Path $Config.BackendPath "requirements.txt" $hasRequirements = Test-Path $requirementsPath if ($venvExists -and $serviceExists -and $hasRequirements) { Write-Success "Backend is installed (venv + service exist)" return $true } else { Write-Warning "Backend NOT installed (missing: $( @( if (-not $venvExists) { 'venv' } if (-not $serviceExists) { 'service' } if (-not $hasRequirements) { 'requirements.txt' } ) -join ', ' ))" return $false } } function Test-TelegramBotInstalled { Write-Step "Checking if Telegram Bot is installed..." $venvPath = Join-Path $Config.TelegramBotPath "venv" $venvExists = Test-Path $venvPath $service = Get-ServiceSafe -ServiceName $Config.TelegramBotServiceName $serviceExists = $null -ne $service $appPath = Join-Path $Config.TelegramBotPath "app" $hasApp = Test-Path $appPath if ($venvExists -and $serviceExists -and $hasApp) { Write-Success "Telegram Bot is installed (venv + service exist)" return $true } else { Write-Warning "Telegram Bot NOT installed (missing: $( @( if (-not $venvExists) { 'venv' } if (-not $serviceExists) { 'service' } if (-not $hasApp) { 'app files' } ) -join ', ' ))" return $false } } function Get-ServiceSafe { param([string]$ServiceName) try { return Get-Service -Name $ServiceName -ErrorAction Stop } catch { return $null } } function Test-HealthEndpoint { param( [string]$Url, [int]$TimeoutSec ) try { $response = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec $TimeoutSec -ErrorAction Stop if ($response.StatusCode -eq 200) { try { $content = $response.Content | ConvertFrom-Json return @{ Success = $true Status = $content.status Details = $content } } catch { return @{ Success = $true Status = "healthy" Details = $null } } } return @{ Success = $false Status = "unhealthy" Details = $null } } catch { return @{ Success = $false Status = "unreachable" Details = $null Error = $_.Exception.Message } } } function Wait-ForKeyPress { Write-Host "`nPress any key to continue..." -ForegroundColor Gray $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") } # ============================================================================= # PREREQUISITE INSTALLATION FUNCTIONS # ============================================================================= function Install-Chocolatey { Write-Step "Checking Chocolatey package manager..." if (Test-CommandExists "choco") { Write-Success "Chocolatey already installed" return $true } Write-Info "Installing Chocolatey..." try { Set-ExecutionPolicy Bypass -Scope Process -Force [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) # Refresh environment $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") Write-Success "Chocolatey installed successfully" return $true } catch { Write-Warning "Failed to install Chocolatey: $_" Write-Warning "Some automated installations may not be available" return $false } } function Install-Python { Write-Step "Checking Python installation..." # Check if Python is already installed try { $pythonCmd = Get-Command python -ErrorAction Stop $pythonVersionOutput = & python --version 2>&1 if ($pythonVersionOutput -match "Python (\d+\.\d+\.\d+)") { $installedVersion = $matches[1] $versionParts = $installedVersion -split '\.' $major = [int]$versionParts[0] $minor = [int]$versionParts[1] if ($major -ge 3 -and $minor -ge 11) { Write-Success "Python $installedVersion already installed" return $true } else { Write-Warning "Python $installedVersion found, but 3.11+ required" } } } catch { Write-Info "Python not found, will attempt installation..." } # Try to install via Chocolatey if (Test-CommandExists "choco") { try { Write-Step "Installing Python 3.11..." choco install python --version=3.11.9 -y --force # Refresh environment $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") Write-Success "Python installed successfully" return $true } catch { Write-Error "Failed to install Python via Chocolatey: $_" return $false } } else { Write-Error "Chocolatey not available, cannot auto-install Python" Write-Warning "Please download Python manually from: https://www.python.org/downloads/" return $false } } function Install-NSSM { Write-Step "Checking NSSM (service manager)..." if (Test-Path "C:\nssm\nssm.exe") { Write-Success "NSSM already installed" return $true } # Try Chocolatey first if (Test-CommandExists "choco") { try { choco install nssm -y Write-Success "NSSM installed via Chocolatey" return $true } catch { Write-Warning "Chocolatey installation failed, trying direct download..." } } # Direct download fallback try { $nssmUrl = "https://nssm.cc/release/nssm-2.24.zip" $nssmZip = "$env:TEMP\nssm.zip" $nssmExtract = "$env:TEMP\nssm" Write-Step "Downloading NSSM..." Invoke-WebRequest -Uri $nssmUrl -OutFile $nssmZip Write-Step "Extracting NSSM..." Expand-Archive -Path $nssmZip -DestinationPath $nssmExtract -Force # Copy nssm.exe to C:\nssm New-Item -ItemType Directory -Path "C:\nssm" -Force | Out-Null Copy-Item -Path "$nssmExtract\nssm-2.24\win64\nssm.exe" -Destination "C:\nssm\nssm.exe" -Force # Add to PATH $currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine") if ($currentPath -notlike "*C:\nssm*") { [Environment]::SetEnvironmentVariable("Path", "$currentPath;C:\nssm", "Machine") $env:Path += ";C:\nssm" } # Cleanup Remove-Item $nssmZip -Force Remove-Item $nssmExtract -Recurse -Force Write-Success "NSSM installed successfully" return $true } catch { Write-Error "Failed to install NSSM: $_" return $false } } function Install-IISModules { Write-Step "Checking IIS installation..." # Detect OS type (Server vs Desktop) $osInfo = Get-CimInstance -ClassName Win32_OperatingSystem $isServer = $osInfo.ProductType -eq 3 # Check if IIS is installed $iisInstalled = $false if ($isServer) { $iisFeature = Get-WindowsFeature -Name Web-Server -ErrorAction SilentlyContinue $iisInstalled = $iisFeature -and $iisFeature.InstallState -eq "Installed" if (-not $iisInstalled) { Write-Warning "IIS not installed on Windows Server" Write-Host " Install with: Install-WindowsFeature -Name Web-Server -IncludeManagementTools" -ForegroundColor Yellow return $false } } else { $iisFeature = Get-WindowsOptionalFeature -Online -FeatureName IIS-WebServer -ErrorAction SilentlyContinue $iisInstalled = $iisFeature -and $iisFeature.State -eq "Enabled" if (-not $iisInstalled) { Write-Warning "IIS not installed on Windows Desktop" Write-Host " Enable via: Control Panel -> Windows Features -> Internet Information Services" -ForegroundColor Yellow return $false } } Write-Success "IIS is installed ($($osInfo.Caption))" # Install URL Rewrite Module Write-Step "Checking IIS URL Rewrite Module..." $urlRewriteInstalled = Get-WebConfiguration -Filter "/system.webServer/rewrite" -PSPath "IIS:\" -ErrorAction SilentlyContinue if (-not $urlRewriteInstalled) { try { Write-Info "Installing URL Rewrite Module..." $urlRewriteUrl = "https://download.microsoft.com/download/1/2/8/128E2E22-C1B9-44A4-BE2A-5859ED1D4592/rewrite_amd64_en-US.msi" $urlRewritePath = "$env:TEMP\rewrite_amd64.msi" Invoke-WebRequest -Uri $urlRewriteUrl -OutFile $urlRewritePath Start-Process msiexec.exe -ArgumentList "/i", $urlRewritePath, "/quiet", "/norestart" -Wait Remove-Item $urlRewritePath -Force Write-Success "URL Rewrite Module installed" } catch { Write-Warning "Failed to install URL Rewrite: $_" Write-Warning "Download manually from: https://www.iis.net/downloads/microsoft/url-rewrite" } } else { Write-Success "URL Rewrite Module already installed" } return $true } # ============================================================================= # FIRST-TIME INSTALLATION FUNCTIONS # ============================================================================= function Install-BackendFirstTime { Write-Host "`n" + ("=" * 70) -ForegroundColor Yellow Write-Host " FIRST-TIME INSTALLATION: BACKEND + FRONTEND" -ForegroundColor Yellow Write-Host ("=" * 70) -ForegroundColor Yellow try { # Install prerequisites Write-Step "Installing prerequisites..." Install-Chocolatey | Out-Null if (-not (Install-Python)) { throw "Python installation failed" } if (-not (Install-NSSM)) { throw "NSSM installation failed" } # Create directory structure Write-Step "Creating directory structure..." $directories = @( $Config.InstallPath, $Config.BackendPath, $Config.FrontendPath, $Config.LogsPath, (Join-Path $Config.BackendPath "logs"), (Join-Path $Config.BackendPath "temp") ) foreach ($dir in $directories) { if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null Write-Success "Created: $dir" } else { Write-Success "Already exists: $dir" } } # Set permissions for IIS try { $acl = Get-Acl $Config.InstallPath $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("IIS_IUSRS", "ReadAndExecute", "ContainerInherit,ObjectInherit", "None", "Allow") $acl.SetAccessRule($accessRule) Set-Acl -Path $Config.InstallPath -AclObject $acl Write-Success "Permissions set for IIS_IUSRS" } catch { Write-Warning "Could not set permissions: $_" } # Copy backend files from source Write-Step "Copying backend files..." $sourceBackend = Join-Path $Config.SourcePath "backend" if (-not (Test-Path $sourceBackend)) { throw "Source backend path not found: $sourceBackend" } # Copy backend files (exclude venv, __pycache__, .env) $excludeDirs = @("venv", "__pycache__", ".pytest_cache", "logs", "temp", "node_modules") $excludeFiles = @("*.pyc", "*.pyo", "*.log", ".env", ".env.local") Get-ChildItem -Path $sourceBackend -Recurse | ForEach-Object { $relativePath = $_.FullName.Substring($sourceBackend.Length).TrimStart('\', '/') # Check if excluded $inExcludedDir = $false foreach ($excludeDir in $excludeDirs) { if ($relativePath -match "^$excludeDir" -or $relativePath -match "[\\/]$excludeDir[\\/]") { $inExcludedDir = $true break } } if ($inExcludedDir) { return } $isExcludedFile = $false foreach ($pattern in $excludeFiles) { if ($_.Name -like $pattern) { $isExcludedFile = $true break } } if ($isExcludedFile) { return } $destPath = Join-Path $Config.BackendPath $relativePath if ($_.PSIsContainer) { if (-not (Test-Path $destPath)) { New-Item -ItemType Directory -Path $destPath -Force | Out-Null } } else { $destDir = Split-Path $destPath -Parent if (-not (Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir -Force | Out-Null } Copy-Item -Path $_.FullName -Destination $destPath -Force } } Write-Success "Backend files copied" # Copy .env.example $sourceEnvExample = Join-Path $sourceBackend ".env.example" if (Test-Path $sourceEnvExample) { $destEnvExample = Join-Path $Config.BackendPath ".env.example" Copy-Item -Path $sourceEnvExample -Destination $destEnvExample -Force Write-Success ".env.example template copied" } # Copy shared modules $sourceShared = Join-Path $Config.SourcePath "shared" if (Test-Path $sourceShared) { $destShared = Join-Path (Split-Path $Config.BackendPath -Parent) "shared" Copy-Item -Path $sourceShared -Destination $destShared -Recurse -Force -Exclude @("__pycache__", "*.pyc") Write-Success "Shared modules copied" } # Copy frontend files Write-Step "Copying frontend files..." $sourceFrontend = Join-Path $Config.SourcePath "frontend" if (Test-Path $sourceFrontend) { Copy-Item -Path "$sourceFrontend\*" -Destination $Config.FrontendPath -Recurse -Force Write-Success "Frontend files copied" } else { Write-Warning "Frontend source not found: $sourceFrontend" } # Create virtual environment Write-Step "Creating Python virtual environment..." $venvPath = Join-Path $Config.BackendPath "venv" & python -m venv $venvPath Write-Success "Virtual environment created at: $venvPath" # Define paths (BEFORE installing dependencies) $pipPath = Join-Path $venvPath "Scripts\pip.exe" $pythonPath = Join-Path $venvPath "Scripts\python.exe" $requirementsPath = Join-Path $Config.BackendPath "requirements.txt" # Install dependencies Write-Step "Installing Python dependencies..." if (Test-Path $requirementsPath) { Write-Info "Upgrading pip..." & $pythonPath -m pip install --upgrade pip | Out-Default Write-Info "Installing dependencies from requirements.txt..." & $pipPath install -r $requirementsPath | Out-Default Write-Success "Python dependencies installed" } else { Write-Warning "requirements.txt not found at: $requirementsPath" } # Create Windows Service Write-Step "Creating Windows Service for backend..." # Remove existing service if present $oldErrorAction = $ErrorActionPreference $ErrorActionPreference = "SilentlyContinue" $nssmOutput = & nssm status $Config.BackendServiceName 2>&1 $serviceExists = $LASTEXITCODE -eq 0 $ErrorActionPreference = $oldErrorAction if ($serviceExists) { Write-Info "Removing existing service..." & nssm stop $Config.BackendServiceName 2>&1 | Out-Null Start-Sleep -Seconds 2 & nssm remove $Config.BackendServiceName confirm 2>&1 | Out-Null Start-Sleep -Seconds 2 } # Create service with venv Python & nssm install $Config.BackendServiceName $pythonPath "-m" "uvicorn" "app.main:app" "--host" "127.0.0.1" "--port" $Config.BackendPort.ToString() "--workers" "4" & nssm set $Config.BackendServiceName DisplayName $Config.BackendServiceDisplayName & nssm set $Config.BackendServiceName Description $Config.BackendServiceDescription & nssm set $Config.BackendServiceName Start SERVICE_AUTO_START & nssm set $Config.BackendServiceName AppDirectory $Config.BackendPath # Set PYTHONPATH to shared directory for module imports $sharedPath = Join-Path $Config.InstallPath "shared" & nssm set $Config.BackendServiceName AppEnvironmentExtra "PYTHONPATH=$sharedPath" # Set logging $stdoutLog = Join-Path $Config.LogsPath "backend-stdout.log" $stderrLog = Join-Path $Config.LogsPath "backend-stderr.log" & nssm set $Config.BackendServiceName AppStdout $stdoutLog & nssm set $Config.BackendServiceName AppStderr $stderrLog & nssm set $Config.BackendServiceName AppStdoutCreationDisposition 4 & nssm set $Config.BackendServiceName AppStderrCreationDisposition 4 & nssm set $Config.BackendServiceName AppExit Default Restart & nssm set $Config.BackendServiceName AppRestartDelay 5000 Write-Success "Windows Service created: $($Config.BackendServiceName)" # Configure IIS if (Install-IISModules) { Write-Step "Configuring IIS..." Import-Module WebAdministration -ErrorAction Stop # Create Application Pool if (Test-Path "IIS:\AppPools\$($Config.IISAppPoolName)") { Remove-WebAppPool -Name $Config.IISAppPoolName -ErrorAction SilentlyContinue } New-WebAppPool -Name $Config.IISAppPoolName -Force | Out-Null Set-ItemProperty -Path "IIS:\AppPools\$($Config.IISAppPoolName)" -Name "managedRuntimeVersion" -Value "" Write-Success "Application Pool created: $($Config.IISAppPoolName)" # Create/update application $existingApp = Get-WebApplication -Name $Config.IISAppName -Site $Config.IISSiteName -ErrorAction SilentlyContinue if ($existingApp) { Remove-WebApplication -Name $Config.IISAppName -Site $Config.IISSiteName -ErrorAction SilentlyContinue } New-WebApplication -Name $Config.IISAppName ` -Site $Config.IISSiteName ` -PhysicalPath $Config.FrontendPath ` -ApplicationPool $Config.IISAppPoolName ` -Force | Out-Null Write-Success "IIS Application created: /$($Config.IISAppName)" # Copy web.config $webConfigSource = Join-Path (Split-Path $Config.SourcePath -Parent) "config\web.config" $webConfigDest = Join-Path $Config.FrontendPath "web.config" if (Test-Path $webConfigSource) { Copy-Item -Path $webConfigSource -Destination $webConfigDest -Force Write-Success "web.config copied" } } else { Write-Warning "IIS modules not installed, skipping IIS configuration" } Write-Host "`n" + ("=" * 70) -ForegroundColor Green Write-Host " BACKEND + FRONTEND INSTALLATION COMPLETED" -ForegroundColor Green Write-Host ("=" * 70) -ForegroundColor Green Write-Host "`nIMPORTANT: Configure .env file before starting service" -ForegroundColor Yellow Write-Host "Location: $($Config.BackendPath)\.env" -ForegroundColor Yellow return $true } catch { Write-Host "`n[INSTALLATION FAILED] $_" -ForegroundColor Red Write-Host $_.ScriptStackTrace -ForegroundColor Red return $false } } function Install-TelegramBotFirstTime { Write-Host "`n" + ("=" * 70) -ForegroundColor Yellow Write-Host " FIRST-TIME INSTALLATION: TELEGRAM BOT" -ForegroundColor Yellow Write-Host ("=" * 70) -ForegroundColor Yellow try { # Install prerequisites Write-Step "Installing prerequisites..." Install-Chocolatey | Out-Null if (-not (Install-Python)) { throw "Python installation failed" } if (-not (Install-NSSM)) { throw "NSSM installation failed" } # Create directory structure Write-Step "Creating directory structure..." $directories = @( $Config.TelegramBotPath, (Join-Path $Config.TelegramBotPath "app"), (Join-Path $Config.TelegramBotPath "data"), $Config.TelegramBotLogsPath, (Join-Path $Config.TelegramBotPath "temp"), (Join-Path $Config.TelegramBotPath "backups") ) foreach ($dir in $directories) { if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null Write-Success "Created: $dir" } else { Write-Success "Already exists: $dir" } } # Set permissions try { $acl = Get-Acl $Config.TelegramBotPath $systemRule = New-Object System.Security.AccessControl.FileSystemAccessRule("SYSTEM", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow") $acl.SetAccessRule($systemRule) Set-Acl -Path $Config.TelegramBotPath -AclObject $acl Write-Success "Permissions set for SYSTEM" } catch { Write-Warning "Could not set permissions: $_" } # Copy telegram bot files Write-Step "Copying Telegram bot files..." $sourceTelegramBot = Join-Path $Config.SourcePath "telegram-bot" if (-not (Test-Path $sourceTelegramBot)) { throw "Source telegram-bot path not found: $sourceTelegramBot" } # Copy app directory $sourceApp = Join-Path $sourceTelegramBot "app" $destApp = Join-Path $Config.TelegramBotPath "app" if (Test-Path $destApp) { Remove-Item -Path $destApp -Recurse -Force } Copy-Item -Path $sourceApp -Destination $destApp -Recurse -Force Write-Success "Application files copied" # Copy requirements.txt $sourceReq = Join-Path $sourceTelegramBot "requirements.txt" if (Test-Path $sourceReq) { $destReq = Join-Path $Config.TelegramBotPath "requirements.txt" Copy-Item -Path $sourceReq -Destination $destReq -Force Write-Success "requirements.txt copied" } # Copy .env.example $sourceEnvExample = Join-Path $sourceTelegramBot ".env.example" if (Test-Path $sourceEnvExample) { $destEnvExample = Join-Path $Config.TelegramBotPath ".env.example" Copy-Item -Path $sourceEnvExample -Destination $destEnvExample -Force Write-Success ".env.example template copied" } # Create virtual environment Write-Step "Creating Python virtual environment..." $venvPath = Join-Path $Config.TelegramBotPath "venv" & python -m venv $venvPath Write-Success "Virtual environment created" # Define paths (BEFORE installing dependencies) $pipPath = Join-Path $venvPath "Scripts\pip.exe" $pythonPath = Join-Path $venvPath "Scripts\python.exe" $requirementsPath = Join-Path $Config.TelegramBotPath "requirements.txt" # Install dependencies Write-Step "Installing Python dependencies..." if (Test-Path $requirementsPath) { Write-Info "Upgrading pip..." & $pythonPath -m pip install --upgrade pip | Out-Default Write-Info "Installing dependencies..." & $pipPath install -r $requirementsPath | Out-Default Write-Success "Python dependencies installed" } # Create Windows Service Write-Step "Creating Windows Service for Telegram bot..." # Remove existing service if present $oldErrorAction = $ErrorActionPreference $ErrorActionPreference = "SilentlyContinue" $nssmOutput = & nssm status $Config.TelegramBotServiceName 2>&1 $serviceExists = $LASTEXITCODE -eq 0 $ErrorActionPreference = $oldErrorAction if ($serviceExists) { Write-Info "Removing existing service..." & nssm stop $Config.TelegramBotServiceName 2>&1 | Out-Null Start-Sleep -Seconds 2 & nssm remove $Config.TelegramBotServiceName confirm 2>&1 | Out-Null Start-Sleep -Seconds 2 } # Create service & nssm install $Config.TelegramBotServiceName $pythonPath "-m" "app.main" & nssm set $Config.TelegramBotServiceName DisplayName $Config.TelegramBotServiceDisplayName & nssm set $Config.TelegramBotServiceName Description $Config.TelegramBotServiceDescription & nssm set $Config.TelegramBotServiceName Start SERVICE_AUTO_START & nssm set $Config.TelegramBotServiceName AppDirectory $Config.TelegramBotPath & nssm set $Config.TelegramBotServiceName AppEnvironmentExtra "PYTHONPATH=$($Config.TelegramBotPath)" # Set logging $stdoutLog = Join-Path $Config.TelegramBotLogsPath "stdout.log" $stderrLog = Join-Path $Config.TelegramBotLogsPath "stderr.log" & nssm set $Config.TelegramBotServiceName AppStdout $stdoutLog & nssm set $Config.TelegramBotServiceName AppStderr $stderrLog & nssm set $Config.TelegramBotServiceName AppStdoutCreationDisposition 4 & nssm set $Config.TelegramBotServiceName AppStderrCreationDisposition 4 & nssm set $Config.TelegramBotServiceName AppExit Default Restart & nssm set $Config.TelegramBotServiceName AppRestartDelay 5000 Write-Success "Windows Service created: $($Config.TelegramBotServiceName)" Write-Host "`n" + ("=" * 70) -ForegroundColor Green Write-Host " TELEGRAM BOT INSTALLATION COMPLETED" -ForegroundColor Green Write-Host ("=" * 70) -ForegroundColor Green Write-Host "`nIMPORTANT: Configure .env file before starting service" -ForegroundColor Yellow Write-Host "Location: $($Config.TelegramBotPath)\.env" -ForegroundColor Yellow return $true } catch { Write-Host "`n[INSTALLATION FAILED] $_" -ForegroundColor Red Write-Host $_.ScriptStackTrace -ForegroundColor Red return $false } } # ============================================================================= # MENU FUNCTIONS # ============================================================================= function Show-MainMenu { Clear-Host Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan Write-Host " ROA2WEB - Unified Deployment & Management Console" -ForegroundColor Cyan Write-Host ("=" * 70) -ForegroundColor Cyan Write-Host "" Write-Host " Main Menu:" -ForegroundColor Yellow Write-Host "" Write-Host " [1] Deploy Components" -ForegroundColor White Write-Host " (Update application files and configurations)" -ForegroundColor Gray Write-Host "" Write-Host " [2] Manage Services" -ForegroundColor White Write-Host " (Start, stop, restart Backend and Telegram Bot)" -ForegroundColor Gray Write-Host "" Write-Host " [3] Check Status" -ForegroundColor White Write-Host " (View service status and health checks)" -ForegroundColor Gray Write-Host "" Write-Host " [Q] Quit" -ForegroundColor Red Write-Host "" Write-Host ("=" * 70) -ForegroundColor Cyan do { Write-Host "`nYour choice: " -ForegroundColor Yellow -NoNewline $choice = Read-Host switch ($choice.ToUpper()) { "1" { return "Deploy" } "2" { return "Manage" } "3" { return "Status" } "Q" { return "Quit" } default { Write-Host "Invalid choice. Please select 1-3 or Q." -ForegroundColor Red } } } while ($true) } function Show-DeployMenu { Clear-Host Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan Write-Host " Deploy Components" -ForegroundColor Cyan Write-Host ("=" * 70) -ForegroundColor Cyan Write-Host "" Write-Host " Select what to deploy:" -ForegroundColor Yellow Write-Host "" Write-Host " [1] Backend + Frontend" -ForegroundColor White Write-Host " (FastAPI backend + Vue.js frontend files)" -ForegroundColor Gray Write-Host "" Write-Host " [2] Telegram Bot" -ForegroundColor White Write-Host " (Telegram bot application only)" -ForegroundColor Gray Write-Host "" Write-Host " [3] All Components" -ForegroundColor White Write-Host " (Backend + Frontend + Telegram Bot)" -ForegroundColor Gray Write-Host "" Write-Host " [B] Back to Main Menu" -ForegroundColor Yellow Write-Host "" Write-Host ("=" * 70) -ForegroundColor Cyan do { Write-Host "`nYour choice: " -ForegroundColor Yellow -NoNewline $choice = Read-Host switch ($choice.ToUpper()) { "1" { return "Backend" } "2" { return "TelegramBot" } "3" { return "All" } "B" { return "Back" } default { Write-Host "Invalid choice. Please select 1-3 or B." -ForegroundColor Red } } } while ($true) } function Show-ManageMenu { Clear-Host Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan Write-Host " Manage Services" -ForegroundColor Cyan Write-Host ("=" * 70) -ForegroundColor Cyan Write-Host "" Write-Host " Quick Actions:" -ForegroundColor Yellow Write-Host " [1] Start All Services" -ForegroundColor Green Write-Host " [2] Stop All Services" -ForegroundColor Red Write-Host " [3] Restart All Services" -ForegroundColor Yellow Write-Host "" Write-Host " Backend Service:" -ForegroundColor Yellow Write-Host " [4] Start Backend" -ForegroundColor Green Write-Host " [5] Stop Backend" -ForegroundColor Red Write-Host " [6] Restart Backend" -ForegroundColor Yellow Write-Host "" Write-Host " Telegram Bot Service:" -ForegroundColor Yellow Write-Host " [7] Start Telegram Bot" -ForegroundColor Green Write-Host " [8] Stop Telegram Bot" -ForegroundColor Red Write-Host " [9] Restart Telegram Bot" -ForegroundColor Yellow Write-Host "" Write-Host " [B] Back to Main Menu" -ForegroundColor Gray Write-Host "" Write-Host ("=" * 70) -ForegroundColor Cyan do { Write-Host "`nYour choice: " -ForegroundColor Yellow -NoNewline $choice = Read-Host switch ($choice.ToUpper()) { "1" { return @{ Action = "Start"; Component = "All" } } "2" { return @{ Action = "Stop"; Component = "All" } } "3" { return @{ Action = "Restart"; Component = "All" } } "4" { return @{ Action = "Start"; Component = "Backend" } } "5" { return @{ Action = "Stop"; Component = "Backend" } } "6" { return @{ Action = "Restart"; Component = "Backend" } } "7" { return @{ Action = "Start"; Component = "TelegramBot" } } "8" { return @{ Action = "Stop"; Component = "TelegramBot" } } "9" { return @{ Action = "Restart"; Component = "TelegramBot" } } "B" { return @{ Action = "Back"; Component = "" } } default { Write-Host "Invalid choice. Please select 1-9 or B." -ForegroundColor Red } } } while ($true) } # ============================================================================= # SERVICE MANAGEMENT FUNCTIONS # ============================================================================= function Start-ServiceComponent { param( [string]$ComponentName, [string]$ServiceName, [string]$HealthUrl, [int]$HealthTimeout ) Write-Step "Starting $ComponentName..." $service = Get-ServiceSafe -ServiceName $ServiceName if (-not $service) { Write-Error "Service '$ServiceName' not found" Write-Warning "Run Install script first to create the service" return $false } if ($service.Status -eq "Running") { Write-Success "Service is already running" return $true } try { Start-Service -Name $ServiceName Write-Info "Service start command issued" $elapsed = 0 while ($service.Status -ne "Running" -and $elapsed -lt $Config.ServiceTimeout) { Start-Sleep -Seconds 1 $service.Refresh() $elapsed++ if ($elapsed % 5 -eq 0) { Write-Info "Waiting for service to start... ($elapsed/$($Config.ServiceTimeout))" } } if ($service.Status -eq "Running") { Write-Success "Service started successfully" # Wait for initialization $waitTime = if ($ComponentName -eq "Telegram Bot") { 5 } else { 3 } Start-Sleep -Seconds $waitTime # Test health endpoint $health = Test-HealthEndpoint -Url $HealthUrl -TimeoutSec $HealthTimeout if ($health.Success) { Write-Success "Health check passed: $($health.Status)" } else { Write-Warning "Health check failed: $($health.Status) (service may still be starting)" } return $true } else { Write-Error "Service failed to start (Status: $($service.Status))" return $false } } catch { Write-Error "Failed to start service: $_" return $false } } function Stop-ServiceComponent { param( [string]$ComponentName, [string]$ServiceName ) Write-Step "Stopping $ComponentName..." $service = Get-ServiceSafe -ServiceName $ServiceName if (-not $service) { Write-Error "Service '$ServiceName' not found" return $false } if ($service.Status -eq "Stopped") { Write-Success "Service is already stopped" return $true } try { Stop-Service -Name $ServiceName -Force Write-Info "Service stop command issued" $elapsed = 0 while ($service.Status -ne "Stopped" -and $elapsed -lt $Config.ServiceTimeout) { Start-Sleep -Seconds 1 $service.Refresh() $elapsed++ if ($elapsed % 5 -eq 0) { Write-Info "Waiting for service to stop... ($elapsed/$($Config.ServiceTimeout))" } } if ($service.Status -eq "Stopped") { Write-Success "Service stopped successfully" return $true } else { Write-Error "Service did not stop within timeout (Status: $($service.Status))" return $false } } catch { Write-Error "Failed to stop service: $_" return $false } } function Restart-ServiceComponent { param( [string]$ComponentName, [string]$ServiceName, [string]$HealthUrl, [int]$HealthTimeout ) Write-Step "Restarting $ComponentName..." $stopResult = Stop-ServiceComponent -ComponentName $ComponentName -ServiceName $ServiceName if (-not $stopResult) { Write-Error "Failed to stop service, aborting restart" return $false } Start-Sleep -Seconds 2 $startResult = Start-ServiceComponent -ComponentName $ComponentName -ServiceName $ServiceName -HealthUrl $HealthUrl -HealthTimeout $HealthTimeout if ($startResult) { Write-Success "Service restarted successfully" return $true } else { Write-Error "Failed to start service after stop" return $false } } function Show-ServiceStatus { param( [string]$ComponentName, [string]$ServiceName, [string]$HealthUrl, [int]$HealthTimeout ) $service = Get-ServiceSafe -ServiceName $ServiceName Write-Host "`n${ComponentName}:" -ForegroundColor Cyan Write-Host " Service Name: $ServiceName" -ForegroundColor Gray if ($service) { $statusColor = switch ($service.Status) { "Running" { "Green" } "Stopped" { "Yellow" } default { "Gray" } } Write-Host " Status: $($service.Status)" -ForegroundColor $statusColor Write-Host " Start Type: $($service.StartType)" -ForegroundColor Gray if ($service.Status -eq "Running") { $health = Test-HealthEndpoint -Url $HealthUrl -TimeoutSec $HealthTimeout $healthColor = switch ($health.Status) { "healthy" { "Green" } "ok" { "Green" } "unhealthy" { "Red" } "unreachable" { "Yellow" } default { "Gray" } } Write-Host " Health: $($health.Status)" -ForegroundColor $healthColor } } else { Write-Host " Status: Not Installed" -ForegroundColor Red } } # ============================================================================= # BACKUP FUNCTIONS # ============================================================================= function New-BackupDirectory { if (-not (Test-Path $Config.BackupPath)) { New-Item -ItemType Directory -Path $Config.BackupPath -Force | Out-Null } } function Backup-CurrentDeployment { param( [string]$Component # "Backend", "TelegramBot", or "All" ) Write-Step "Creating backup of current deployment..." New-BackupDirectory $timestamp = Get-Date -Format "yyyyMMdd-HHmmss" $backupName = "backup-$Component-$timestamp" $backupFullPath = Join-Path $Config.BackupPath $backupName try { New-Item -ItemType Directory -Path $backupFullPath -Force | Out-Null switch ($Component) { "Backend" { # Backup backend if (Test-Path $Config.BackendPath) { $backupBackendPath = Join-Path $backupFullPath "backend" Copy-Item -Path $Config.BackendPath -Destination $backupBackendPath -Recurse -Force -ErrorAction SilentlyContinue Write-Success "Backend backed up" } # Backup frontend if (Test-Path $Config.FrontendPath) { $backupFrontendPath = Join-Path $backupFullPath "frontend" Copy-Item -Path $Config.FrontendPath -Destination $backupFrontendPath -Recurse -Force -ErrorAction SilentlyContinue Write-Success "Frontend backed up" } } "TelegramBot" { # Backup app directory $appPath = Join-Path $Config.TelegramBotPath "app" if (Test-Path $appPath) { $backupAppPath = Join-Path $backupFullPath "app" Copy-Item -Path $appPath -Destination $backupAppPath -Recurse -Force Write-Success "App files backed up" } # Backup requirements.txt $reqFile = Join-Path $Config.TelegramBotPath "requirements.txt" if (Test-Path $reqFile) { Copy-Item -Path $reqFile -Destination (Join-Path $backupFullPath "requirements.txt") -Force Write-Success "Requirements file backed up" } # Backup .env $envFile = Join-Path $Config.TelegramBotPath ".env" if (Test-Path $envFile) { Copy-Item -Path $envFile -Destination (Join-Path $backupFullPath ".env") -Force Write-Success ".env file backed up" } # Backup database $dbFile = Join-Path $Config.TelegramBotPath "data\telegram_bot.db" if (Test-Path $dbFile) { $backupDataPath = Join-Path $backupFullPath "data" New-Item -ItemType Directory -Path $backupDataPath -Force | Out-Null Copy-Item -Path $dbFile -Destination (Join-Path $backupDataPath "telegram_bot.db") -Force Write-Success "Database backed up" } } "All" { # Backup both Backup-CurrentDeployment -Component "Backend" Backup-CurrentDeployment -Component "TelegramBot" return $backupFullPath } } Write-Success "Backup created at: $backupFullPath" # Clean old backups (keep last 10) $allBackups = Get-ChildItem -Path $Config.BackupPath -Directory | Where-Object { $_.Name -like "backup-*" } | Sort-Object Name -Descending if ($allBackups.Count -gt 10) { $oldBackups = $allBackups | Select-Object -Skip 10 foreach ($oldBackup in $oldBackups) { Remove-Item -Path $oldBackup.FullName -Recurse -Force Write-Success "Cleaned old backup: $($oldBackup.Name)" } } return $backupFullPath } catch { Write-Error "Backup failed: $_" throw } } # ============================================================================= # DEPLOYMENT FUNCTIONS # ============================================================================= function Update-BackendFiles { Write-Step "Updating backend files..." $sourceBackend = Join-Path $Config.SourcePath "backend" if (-not (Test-Path $sourceBackend)) { throw "Source backend path not found: $sourceBackend" } try { # Copy files except .env and logs $excludeFiles = @("*.env", "*.log", "*.pyc", "__pycache__") Get-ChildItem -Path $sourceBackend -Recurse -File | ForEach-Object { $relativePath = $_.FullName.Substring($sourceBackend.Length) $destPath = Join-Path $Config.BackendPath $relativePath $skip = $false foreach ($pattern in $excludeFiles) { if ($_.Name -like $pattern -or $_.Directory.Name -eq "__pycache__") { $skip = $true break } } if (-not $skip) { $destDir = Split-Path $destPath -Parent if (-not (Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir -Force | Out-Null } Copy-Item -Path $_.FullName -Destination $destPath -Force } } Write-Success "Backend files updated" # Copy .env.example explicitly (excluded from recursive copy) $sourceEnvExample = Join-Path $sourceBackend ".env.example" $destEnvExample = Join-Path $Config.BackendPath ".env.example" if (Test-Path $sourceEnvExample) { Copy-Item -Path $sourceEnvExample -Destination $destEnvExample -Force Write-Success ".env.example template updated" } # Check if requirements.txt changed $sourceReq = Join-Path $sourceBackend "requirements.txt" $destReq = Join-Path $Config.BackendPath "requirements.txt" if (Test-Path $sourceReq) { $sourceHash = (Get-FileHash $sourceReq).Hash $destHash = if (Test-Path $destReq) { (Get-FileHash $destReq).Hash } else { "" } if ($sourceHash -ne $destHash) { Write-Step "Requirements changed, updating Python dependencies..." Copy-Item -Path $sourceReq -Destination $destReq -Force # Use virtual environment pip (same pattern as Telegram Bot) $venvPath = Join-Path $Config.BackendPath "venv" $pipPath = Join-Path $venvPath "Scripts\pip.exe" if (Test-Path $pipPath) { & $pipPath install -r $destReq --upgrade if ($LASTEXITCODE -eq 0) { Write-Success "Python dependencies updated" } else { throw "pip install failed with exit code: $LASTEXITCODE" } } else { Write-Warning "Virtual environment not found at: $venvPath" Write-Warning "Run Install-ROA2WEB.ps1 first to create virtual environment" throw "Backend virtual environment not found" } } else { Write-Success "Python dependencies unchanged" } } } catch { Write-Error "Failed to update backend files: $_" throw } } function Update-FrontendFiles { Write-Step "Updating frontend files..." $sourceFrontend = Join-Path $Config.SourcePath "frontend" if (-not (Test-Path $sourceFrontend)) { throw "Source frontend path not found: $sourceFrontend" } try { # Remove old frontend files (except web.config) if (Test-Path $Config.FrontendPath) { Get-ChildItem -Path $Config.FrontendPath -Exclude "web.config" | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue } # Copy new frontend files Copy-Item -Path "$sourceFrontend\*" -Destination $Config.FrontendPath -Recurse -Force Write-Success "Frontend files updated" } catch { Write-Error "Failed to update frontend files: $_" throw } } function Update-TelegramBotFiles { Write-Step "Updating Telegram bot files..." $sourceApp = Join-Path $Config.SourcePath "app" if (-not (Test-Path $sourceApp)) { throw "Source app directory not found: $sourceApp" } try { # Remove old app directory $destApp = Join-Path $Config.TelegramBotPath "app" if (Test-Path $destApp) { Remove-Item -Path $destApp -Recurse -Force Write-Success "Removed old app directory" } # Copy new app files Copy-Item -Path $sourceApp -Destination $destApp -Recurse -Force Write-Success "Application files updated" # Update requirements.txt if changed $sourceReq = Join-Path $Config.SourcePath "requirements.txt" $destReq = Join-Path $Config.TelegramBotPath "requirements.txt" if (Test-Path $sourceReq) { $sourceHash = (Get-FileHash $sourceReq -Algorithm SHA256).Hash $destHash = if (Test-Path $destReq) { (Get-FileHash $destReq -Algorithm SHA256).Hash } else { "" } if ($sourceHash -ne $destHash) { Write-Step "Requirements changed, updating Python dependencies..." Copy-Item -Path $sourceReq -Destination $destReq -Force # Use virtual environment pip $venvPath = Join-Path $Config.TelegramBotPath "venv" $pipPath = Join-Path $venvPath "Scripts\pip.exe" if (Test-Path $pipPath) { & $pipPath install -r $destReq --upgrade Write-Success "Python dependencies updated" } else { Write-Warning "Virtual environment not found, skipping dependency update" } } else { Write-Success "Python dependencies unchanged" } } # Copy .env.example template (always update to keep in sync) $sourceEnvExample = Join-Path $Config.SourcePath ".env.example" $destEnvExample = Join-Path $Config.TelegramBotPath ".env.example" if (Test-Path $sourceEnvExample) { Copy-Item -Path $sourceEnvExample -Destination $destEnvExample -Force Write-Success ".env.example template updated" } # Preserve .env file (or create from .env.example if missing) $envFile = Join-Path $Config.TelegramBotPath ".env" if (-not (Test-Path $envFile)) { if (Test-Path $sourceEnvExample) { Copy-Item -Path $sourceEnvExample -Destination $envFile -Force Write-Warning "Created .env from .env.example - PLEASE CONFIGURE" } } else { Write-Success ".env file preserved" } } catch { Write-Error "Failed to update Telegram bot files: $_" throw } } function Deploy-Backend { Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan Write-Host " DEPLOYING BACKEND + FRONTEND" -ForegroundColor Cyan Write-Host ("=" * 70) -ForegroundColor Cyan try { # Auto-detect: First-time install or update? $isInstalled = Test-BackendInstalled if (-not $isInstalled) { Write-Host "" Write-Warning "Backend NOT detected - performing FIRST-TIME INSTALLATION" Write-Host "" Start-Sleep -Seconds 2 # Route to first-time installation return Install-BackendFirstTime } # Backend is already installed - proceed with UPDATE Write-Host "" Write-Info "Backend detected - performing UPDATE" Write-Host "" # Create backup $backupPath = Backup-CurrentDeployment -Component "Backend" # Stop backend service $stopped = Stop-ServiceComponent -ComponentName "Backend" -ServiceName $Config.BackendServiceName if (-not $stopped) { throw "Failed to stop backend service" } # Update files Update-BackendFiles Update-FrontendFiles # Start backend service $started = Start-ServiceComponent -ComponentName "Backend" ` -ServiceName $Config.BackendServiceName ` -HealthUrl $Config.BackendHealthUrl ` -HealthTimeout $Config.BackendHealthTimeout if ($started) { Write-Host "`n" + ("=" * 70) -ForegroundColor Green Write-Host " BACKEND + FRONTEND DEPLOYED SUCCESSFULLY" -ForegroundColor Green Write-Host ("=" * 70) -ForegroundColor Green Write-Host " Backup: $backupPath" -ForegroundColor Gray return $true } else { throw "Failed to start backend service after deployment" } } catch { Write-Host "`n[DEPLOYMENT FAILED] $_" -ForegroundColor Red return $false } } function Deploy-TelegramBot { Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan Write-Host " DEPLOYING TELEGRAM BOT" -ForegroundColor Cyan Write-Host ("=" * 70) -ForegroundColor Cyan try { # Auto-detect: First-time install or update? $isInstalled = Test-TelegramBotInstalled if (-not $isInstalled) { Write-Host "" Write-Warning "Telegram Bot NOT detected - performing FIRST-TIME INSTALLATION" Write-Host "" Start-Sleep -Seconds 2 # Route to first-time installation return Install-TelegramBotFirstTime } # Telegram Bot is already installed - proceed with UPDATE Write-Host "" Write-Info "Telegram Bot detected - performing UPDATE" Write-Host "" # Create backup $backupPath = Backup-CurrentDeployment -Component "TelegramBot" # Stop telegram bot service $stopped = Stop-ServiceComponent -ComponentName "Telegram Bot" -ServiceName $Config.TelegramBotServiceName if (-not $stopped) { throw "Failed to stop Telegram bot service" } # Update files # Temporarily change source path for telegram bot $originalSourcePath = $Config.SourcePath $Config.SourcePath = Join-Path $originalSourcePath "telegram-bot" Update-TelegramBotFiles # Restore original source path $Config.SourcePath = $originalSourcePath # Start telegram bot service $started = Start-ServiceComponent -ComponentName "Telegram Bot" ` -ServiceName $Config.TelegramBotServiceName ` -HealthUrl $Config.TelegramBotHealthUrl ` -HealthTimeout $Config.TelegramBotHealthTimeout if ($started) { Write-Host "`n" + ("=" * 70) -ForegroundColor Green Write-Host " TELEGRAM BOT DEPLOYED SUCCESSFULLY" -ForegroundColor Green Write-Host ("=" * 70) -ForegroundColor Green Write-Host " Backup: $backupPath" -ForegroundColor Gray return $true } else { throw "Failed to start Telegram bot service after deployment" } } catch { Write-Host "`n[DEPLOYMENT FAILED] $_" -ForegroundColor Red return $false } } # ============================================================================= # MAIN EXECUTION FLOW # ============================================================================= function Execute-ManageAction { param( [string]$Action, [string]$Component ) $allSuccess = $true $components = @() switch ($Component) { "All" { $components = @( @{ Name = "Backend"; ServiceName = $Config.BackendServiceName; HealthUrl = $Config.BackendHealthUrl; HealthTimeout = $Config.BackendHealthTimeout }, @{ Name = "Telegram Bot"; ServiceName = $Config.TelegramBotServiceName; HealthUrl = $Config.TelegramBotHealthUrl; HealthTimeout = $Config.TelegramBotHealthTimeout } ) } "Backend" { $components = @( @{ Name = "Backend"; ServiceName = $Config.BackendServiceName; HealthUrl = $Config.BackendHealthUrl; HealthTimeout = $Config.BackendHealthTimeout } ) } "TelegramBot" { $components = @( @{ Name = "Telegram Bot"; ServiceName = $Config.TelegramBotServiceName; HealthUrl = $Config.TelegramBotHealthUrl; HealthTimeout = $Config.TelegramBotHealthTimeout } ) } } foreach ($comp in $components) { $result = switch ($Action) { "Start" { Start-ServiceComponent -ComponentName $comp.Name -ServiceName $comp.ServiceName ` -HealthUrl $comp.HealthUrl -HealthTimeout $comp.HealthTimeout } "Stop" { Stop-ServiceComponent -ComponentName $comp.Name -ServiceName $comp.ServiceName } "Restart" { Restart-ServiceComponent -ComponentName $comp.Name -ServiceName $comp.ServiceName ` -HealthUrl $comp.HealthUrl -HealthTimeout $comp.HealthTimeout } } if (-not $result) { $allSuccess = $false } } if ($allSuccess) { Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan Write-Host " OPERATION COMPLETED SUCCESSFULLY" -ForegroundColor Green Write-Host ("=" * 70) -ForegroundColor Cyan } else { Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan Write-Host " OPERATION COMPLETED WITH ERRORS" -ForegroundColor Red Write-Host ("=" * 70) -ForegroundColor Cyan } } function Execute-DeployAction { param([string]$Component) $success = switch ($Component) { "Backend" { Deploy-Backend } "TelegramBot" { Deploy-TelegramBot } "All" { $backendOk = Deploy-Backend $telegramOk = Deploy-TelegramBot $backendOk -and $telegramOk } } if ($success) { Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan Write-Host " DEPLOYMENT COMPLETED" -ForegroundColor Green Write-Host ("=" * 70) -ForegroundColor Cyan } else { Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan Write-Host " DEPLOYMENT FAILED" -ForegroundColor Red Write-Host ("=" * 70) -ForegroundColor Cyan } } function Show-AllStatus { Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan Write-Host " ROA2WEB - System Status" -ForegroundColor Cyan Write-Host ("=" * 70) -ForegroundColor Cyan Show-ServiceStatus -ComponentName "Backend" ` -ServiceName $Config.BackendServiceName ` -HealthUrl $Config.BackendHealthUrl ` -HealthTimeout $Config.BackendHealthTimeout Show-ServiceStatus -ComponentName "Telegram Bot" ` -ServiceName $Config.TelegramBotServiceName ` -HealthUrl $Config.TelegramBotHealthUrl ` -HealthTimeout $Config.TelegramBotHealthTimeout Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan } function Main { # Check administrator privileges if (-not (Test-Administrator)) { Write-Host "`n[ERROR] This script requires Administrator privileges" -ForegroundColor Red Write-Host "Please run PowerShell as Administrator and try again.`n" -ForegroundColor Yellow exit 1 } # Non-interactive mode if ($NonInteractive -and $Action) { switch ($Action) { "DeployBackend" { Execute-DeployAction -Component "Backend" } "DeployTelegramBot" { Execute-DeployAction -Component "TelegramBot" } "DeployAll" { Execute-DeployAction -Component "All" } "StartAll" { Execute-ManageAction -Action "Start" -Component "All" } "StopAll" { Execute-ManageAction -Action "Stop" -Component "All" } "RestartAll" { Execute-ManageAction -Action "Restart" -Component "All" } "Status" { Show-AllStatus } } return } # Interactive mode - main loop do { $mainChoice = Show-MainMenu switch ($mainChoice) { "Deploy" { do { $deployChoice = Show-DeployMenu if ($deployChoice -ne "Back") { Execute-DeployAction -Component $deployChoice Wait-ForKeyPress } } while ($deployChoice -ne "Back") } "Manage" { do { $manageChoice = Show-ManageMenu if ($manageChoice.Action -ne "Back") { Execute-ManageAction -Action $manageChoice.Action -Component $manageChoice.Component Wait-ForKeyPress } } while ($manageChoice.Action -ne "Back") } "Status" { Show-AllStatus Wait-ForKeyPress } "Quit" { Write-Host "`nGoodbye!`n" -ForegroundColor Cyan return } } } while ($true) } # Run main Main