Files
roa2web-service-auto/deployment/windows/scripts/ROA2WEB-Console.ps1
Marius Mutu 016c309d70 Optimize Windows deployment scripts: build cache, console integration, and .env.example handling
Build-ROA2WEB.ps1 improvements:
- Add intelligent npm node_modules caching (saves 2-5 minutes on repeated builds)
- Cache stored outside deploy-package in .build-cache/ directory
- Add Clean Cache menu option ([C]) and -CleanCache parameter
- Include ROA2WEB-Console.ps1 in deployment package
- Update README workflow to use unified console (interactive and CLI modes)
- Remove obsolete script references (Manage-ROA2WEB.ps1, Deploy-*.ps1)
- Fix .env.example copying for backend and telegram-bot (copy from source instead of hardcoded template)

ROA2WEB-Console.ps1 improvements:
- Fix PowerShell parsing error: ${ComponentName}: instead of $ComponentName:
- Add .env.example copying in Update-BackendFiles function
- Add .env.example copying in Update-TelegramBotFiles function
- Keep .env.example synchronized with development templates

.gitignore:
- Add .build-cache/ to prevent committing npm cache directory

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-12 02:04:31 +02:00

1040 lines
35 KiB
PowerShell

<#
.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"
BackendHealthUrl = "http://localhost:8000/health"
BackendHealthTimeout = 5
# Frontend
FrontendPath = "C:\inetpub\wwwroot\roa2web\frontend"
# Telegram Bot
TelegramBotPath = "C:\inetpub\wwwroot\roa2web\telegram-bot"
TelegramBotServiceName = "ROA2WEB-TelegramBot"
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 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")
}
# =============================================================================
# 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
$pythonCmd = Get-Command python -ErrorAction SilentlyContinue
if ($pythonCmd) {
& python -m pip install -r $destReq --upgrade
if ($LASTEXITCODE -eq 0) {
Write-Success "Python dependencies installed successfully"
} else {
throw "pip install failed with exit code: $LASTEXITCODE"
}
} else {
throw "Python not found in PATH"
}
} 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 {
# 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 {
# 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