From 1832684aca6c191211cca400b653c7d2a99c15d5 Mon Sep 17 00:00:00 2001 From: Marius Mutu Date: Wed, 12 Nov 2025 01:35:14 +0200 Subject: [PATCH] Refactor Windows deployment scripts: unify build and management tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major improvements to deployment workflow with unified scripts and interactive menus. New unified scripts: - Build-ROA2WEB.ps1: Interactive menu for building all components * Isolated temp directory for frontend builds (prevents WSL node_modules corruption) * Automatic devDependencies installation (fixes Vite not found issue) * Auto-cleanup after build * Supports both interactive menu and non-interactive CLI - ROA2WEB-Console.ps1: All-in-one deployment and management console * Interactive menus for deploy, manage services, and status checks * Automatic backups before deployment * Smart dependency updates (only if requirements.txt changed) * Health checks after service operations * Color-coded status output * Both interactive and non-interactive modes Removed deprecated scripts (replaced by unified tools): - Build-Frontend.ps1 → Use Build-ROA2WEB.ps1 -Component Frontend - Build-TelegramBot.ps1 → Use Build-ROA2WEB.ps1 -Component TelegramBot - Deploy-ROA2WEB.ps1 → Use ROA2WEB-Console.ps1 [Deploy menu] - Deploy-TelegramBot.ps1 → Use ROA2WEB-Console.ps1 [Deploy menu] - Manage-ROA2WEB.ps1 → Use ROA2WEB-Console.ps1 [Manage menu] Updated documentation: - Complete rewrite of scripts/README.md - Clear workflow guides for first-time deployment and updates - Comparison table v1.0 vs v2.0 - Updated best practices and troubleshooting Benefits: ✅ Reduced from 13 to 8 scripts (better maintainability) ✅ Interactive menus for better UX ✅ Fixed WSL node_modules corruption issue ✅ Smart dependency management (faster deployments) ✅ Unified interface reduces learning curve ✅ Better error handling and health checks 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude --- deployment/windows/scripts/Build-Frontend.ps1 | 615 ---------- deployment/windows/scripts/Build-ROA2WEB.ps1 | 181 ++- .../windows/scripts/Build-TelegramBot.ps1 | 809 ------------- deployment/windows/scripts/Deploy-ROA2WEB.ps1 | 535 --------- .../windows/scripts/Deploy-TelegramBot.ps1 | 598 ---------- deployment/windows/scripts/Manage-ROA2WEB.ps1 | 498 -------- deployment/windows/scripts/README.md | 492 ++++---- .../windows/scripts/ROA2WEB-Console.ps1 | 1024 +++++++++++++++++ 8 files changed, 1447 insertions(+), 3305 deletions(-) delete mode 100644 deployment/windows/scripts/Build-Frontend.ps1 delete mode 100644 deployment/windows/scripts/Build-TelegramBot.ps1 delete mode 100644 deployment/windows/scripts/Deploy-ROA2WEB.ps1 delete mode 100644 deployment/windows/scripts/Deploy-TelegramBot.ps1 delete mode 100644 deployment/windows/scripts/Manage-ROA2WEB.ps1 create mode 100644 deployment/windows/scripts/ROA2WEB-Console.ps1 diff --git a/deployment/windows/scripts/Build-Frontend.ps1 b/deployment/windows/scripts/Build-Frontend.ps1 deleted file mode 100644 index a4de503..0000000 --- a/deployment/windows/scripts/Build-Frontend.ps1 +++ /dev/null @@ -1,615 +0,0 @@ -<# -================================================================================ -⚠️ DEPRECATED - This script is deprecated in favor of Build-ROA2WEB.ps1 -================================================================================ - -This script is maintained for backward compatibility but will be removed in -a future version. Please use the new unified build script instead: - - .\Build-ROA2WEB.ps1 -Component Frontend - -The new script provides: - - Unified build process for all components - - Better parameter validation - - Consistent output structure - - Support for building All, Frontend, Backend, or TelegramBot - -For complete package: .\Build-ROA2WEB.ps1 -Component All - -================================================================================ -#> - -<# -.SYNOPSIS - Build ROA2WEB Frontend for Production Deployment - -.DESCRIPTION - This script builds the Vue.js frontend for Windows Server deployment: - - Checks for Node.js installation - - Installs npm dependencies - - Builds production-optimized static files - - Creates deployment package with backend files - - Optionally transfers to remote server - -.PARAMETER BackendSource - Path to backend source (default: ../../reports-app/backend) - -.PARAMETER FrontendSource - Path to frontend source (default: ../../reports-app/frontend) - -.PARAMETER OutputPath - Output path for deployment package (default: ./deploy-package) - -.PARAMETER ServerPath - Remote server path for automatic deployment (optional) - -.PARAMETER ServerHost - Remote server hostname/IP for automatic deployment (optional) - -.EXAMPLE - .\Build-Frontend.ps1 - Build with defaults, output to ./deploy-package - -.EXAMPLE - .\Build-Frontend.ps1 -OutputPath "D:\deployments\roa2web-$(Get-Date -Format 'yyyyMMdd')" - Build to custom output path - -.EXAMPLE - .\Build-Frontend.ps1 -ServerHost "10.0.20.170" -ServerPath "C:\Temp\roa2web-deploy" - Build and transfer to remote server - -.NOTES - Author: ROA2WEB Team - Requires: Node.js 16+, npm - Can run on: WSL, Windows, Linux -#> - -[CmdletBinding()] -param( - [string]$BackendSource = "../../reports-app/backend", - [string]$FrontendSource = "../../reports-app/frontend", - [string]$OutputPath = "./deploy-package", - [string]$ServerHost = "", - [string]$ServerPath = "" -) - -$ErrorActionPreference = "Stop" - -# ============================================================================= -# 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 Test-NodeJS { - Write-Step "Checking Node.js installation..." - - try { - $nodeVersion = node --version 2>&1 - $npmVersion = npm --version 2>&1 - - Write-Success "Node.js: $nodeVersion" - Write-Success "npm: $npmVersion" - - # Check minimum version (16.x) - if ($nodeVersion -match "v(\d+)\.") { - $major = [int]$matches[1] - if ($major -lt 16) { - throw "Node.js version 16+ required (found: $nodeVersion)" - } - } - - return $true - } catch { - Write-Error "Node.js not found or version too old" - Write-Host "`n Install Node.js from: https://nodejs.org/" -ForegroundColor Yellow - Write-Host " Minimum version: 16.x" -ForegroundColor Yellow - throw - } -} - -function Resolve-FullPath { - param([string]$Path) - - $scriptDir = Split-Path -Parent $PSScriptRoot - $fullPath = Join-Path $scriptDir $Path - - # Convert to absolute path and resolve .. and . properly - $fullPath = [System.IO.Path]::GetFullPath($fullPath) - - return $fullPath -} - -function Build-Frontend { - param([string]$SourcePath) - - Write-Step "Building Vue.js frontend..." - - if (-not (Test-Path $SourcePath)) { - throw "Frontend source path not found: $SourcePath" - } - - Push-Location $SourcePath - try { - # Clean node_modules if it exists (to avoid EPERM errors) - $nodeModulesPath = Join-Path $SourcePath "node_modules" - if (Test-Path $nodeModulesPath) { - Write-Step "Cleaning existing node_modules..." - try { - Remove-Item -Path $nodeModulesPath -Recurse -Force -ErrorAction Stop - Write-Success "Cleaned node_modules" - } catch { - Write-Warning "Could not remove node_modules: $_" - Write-Warning "Please close VS Code/IDE and try again, or run as Administrator" - throw "Cannot proceed with locked node_modules. Close all IDEs and retry." - } - } - - # Install dependencies - Write-Step "Installing npm dependencies (this may take a minute)..." - - # Show npm output for transparency, redirect to host to prevent capture - npm install | Out-Default - - # Verify node_modules was created - if (-not (Test-Path $nodeModulesPath)) { - throw "npm install failed: node_modules not created. Check errors above." - } - Write-Success "Dependencies installed" - - # Build for production - Write-Step "Building for production (this may take a minute)..." - $env:NODE_ENV = "production" - - # Show build output for transparency, redirect to host to prevent capture - npm run build | Out-Default - - Write-Success "Build completed" - - # Verify dist folder - $distPath = Join-Path $SourcePath "dist" - if (-not (Test-Path $distPath)) { - throw "Build failed: dist folder not found" - } - - $distFiles = Get-ChildItem -Path $distPath -Recurse -File - $totalSize = ($distFiles | Measure-Object -Property Length -Sum).Sum / 1MB - Write-Success "Generated $(($distFiles).Count) files (Total: $([math]::Round($totalSize, 2)) MB)" - - return $distPath - } finally { - Pop-Location - } -} - -function Copy-BackendFiles { - param( - [string]$SourcePath, - [string]$DestPath - ) - - Write-Step "Copying backend files..." - - if (-not (Test-Path $SourcePath)) { - throw "Backend source path not found: $SourcePath" - } - - # Ensure destination exists - if (-not (Test-Path $DestPath)) { - New-Item -ItemType Directory -Path $DestPath -Force | Out-Null - } - - # Exclude directory names (will skip entire directory trees) - $excludeDirs = @( - "venv", - "__pycache__", - ".pytest_cache", - "logs", - "temp", - "node_modules" - ) - - # Exclude file patterns - $excludeFiles = @( - "*.pyc", - "*.pyo", - "*.log", - ".env", - ".env.local" - ) - - # Normalize source path (ensure trailing backslash for proper substring calculation) - $normalizedSourcePath = $SourcePath.TrimEnd('\', '/') + '\' - - # Helper function to check if path should be excluded (using script: scope to access parent variables) - $testExclude = { - param([string]$RelativePath, [bool]$IsDirectory, [array]$ExcludeDirs, [array]$ExcludeFiles) - - # Check directory exclusions (match directory name exactly) - if ($IsDirectory) { - $dirName = Split-Path $RelativePath -Leaf - if ($ExcludeDirs -contains $dirName) { - return $true - } - } - - # Check if any parent directory should be excluded - $pathParts = $RelativePath -split '[\\/]' - foreach ($part in $pathParts) { - if ($ExcludeDirs -contains $part) { - return $true - } - } - - # Check file patterns - if (-not $IsDirectory) { - foreach ($pattern in $ExcludeFiles) { - if ($RelativePath -like $pattern) { - return $true - } - } - } - - return $false - } - - # Copy files - Get-ChildItem -Path $SourcePath -Recurse | ForEach-Object { - # Calculate relative path safely - if ($_.FullName.Length -le $normalizedSourcePath.Length) { - return # Skip if path is too short (shouldn't happen, but safety check) - } - $relativePath = $_.FullName.Substring($normalizedSourcePath.Length) - - # Check if should be excluded - $shouldExclude = & $testExclude -RelativePath $relativePath -IsDirectory $_.PSIsContainer -ExcludeDirs $excludeDirs -ExcludeFiles $excludeFiles - if ($shouldExclude) { - return - } - - $destFile = Join-Path $DestPath $relativePath - - if ($_.PSIsContainer) { - if (-not (Test-Path $destFile)) { - New-Item -ItemType Directory -Path $destFile -Force | Out-Null - } - } else { - $destDir = Split-Path $destFile -Parent - if (-not (Test-Path $destDir)) { - New-Item -ItemType Directory -Path $destDir -Force | Out-Null - } - Copy-Item -Path $_.FullName -Destination $destFile -Force - } - } - - $backendFiles = Get-ChildItem -Path $DestPath -Recurse -File - Write-Success "Copied $(($backendFiles).Count) backend files" - - # Verify critical files are present - $requirementsTxt = Join-Path $DestPath "requirements.txt" - if (-not (Test-Path $requirementsTxt)) { - Write-Error "CRITICAL: requirements.txt not found in backend package!" - Write-Host " Expected: $requirementsTxt" -ForegroundColor Red - throw "Backend package incomplete - missing requirements.txt" - } - Write-Success "Verified: requirements.txt present" -} - -function New-DeploymentPackage { - param( - [string]$FrontendDistPath, - [string]$BackendSourcePath, - [string]$OutputPath - ) - - Write-Step "Creating deployment package..." - - # Create output directory - if (Test-Path $OutputPath) { - Write-Warning "Output path exists, cleaning..." - Remove-Item -Path $OutputPath -Recurse -Force - } - New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null - - # Create structure - $frontendDest = Join-Path $OutputPath "frontend" - $backendDest = Join-Path $OutputPath "backend" - - New-Item -ItemType Directory -Path $frontendDest -Force | Out-Null - New-Item -ItemType Directory -Path $backendDest -Force | Out-Null - - # Copy frontend dist - Write-Step "Copying frontend files..." - Copy-Item -Path "$FrontendDistPath\*" -Destination $frontendDest -Recurse -Force - Write-Success "Frontend files copied" - - # Copy backend files - Copy-BackendFiles -SourcePath $BackendSourcePath -DestPath $backendDest - - # Copy shared modules (database, auth, utils) - Write-Step "Copying shared modules..." - $sharedSource = Join-Path (Split-Path (Split-Path $BackendSourcePath -Parent) -Parent) "shared" - $sharedDest = Join-Path $OutputPath "shared" - - if (Test-Path $sharedSource) { - Copy-Item -Path $sharedSource -Destination $sharedDest -Recurse -Force -Exclude @("__pycache__", "*.pyc", "tests") - Write-Success "Shared modules copied" - } else { - Write-Warning "Shared modules not found at: $sharedSource" - } - - # Copy deployment config - $configSource = Join-Path (Split-Path -Parent $PSScriptRoot) "config" - $configDest = Join-Path $OutputPath "config" - - if (Test-Path $configSource) { - Copy-Item -Path $configSource -Destination $configDest -Recurse -Force - Write-Success "Config files copied" - } - - # Copy deployment scripts - Write-Step "Copying deployment scripts..." - $scriptsSource = $PSScriptRoot - $scriptsDest = Join-Path $OutputPath "scripts" - New-Item -ItemType Directory -Path $scriptsDest -Force | Out-Null - - # List of scripts to include in deployment package - $deploymentScripts = @( - "Install-ROA2WEB.ps1", - "Deploy-ROA2WEB.ps1", - "Start-ROA2WEB.ps1", - "Stop-ROA2WEB.ps1", - "Restart-ROA2WEB.ps1" - ) - - $copiedScripts = 0 - foreach ($script in $deploymentScripts) { - $scriptPath = Join-Path $scriptsSource $script - if (Test-Path $scriptPath) { - Copy-Item -Path $scriptPath -Destination $scriptsDest -Force - $copiedScripts++ - } - } - Write-Success "Copied $copiedScripts deployment scripts" - - # Create README - $readmePath = Join-Path $OutputPath "README.txt" - $readme = @" -================================================================================ - ROA2WEB DEPLOYMENT PACKAGE - Generated: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss") -================================================================================ - -CONTENTS: ---------- - backend/ FastAPI backend application files (Python) - frontend/ Vue.js static files (built for production) - config/ IIS configuration files (.env template, web.config) - scripts/ PowerShell management scripts - -DEPLOYMENT SCRIPTS: -------------------- - Install-ROA2WEB.ps1 First-time setup (Python venv, IIS site) - Deploy-ROA2WEB.ps1 Update application files (auto-detects source) - Start-ROA2WEB.ps1 Start backend service + IIS - Stop-ROA2WEB.ps1 Stop backend service + IIS - Restart-ROA2WEB.ps1 Quick restart - -================================================================================ -DEPLOYMENT WORKFLOW -================================================================================ - ->> FIRST TIME INSTALLATION: ---------------------------- -1. Copy this entire folder to server (e.g., C:\Deploy\ROA2WEB-v1) - -2. Open PowerShell as Administrator: - cd C:\Deploy\ROA2WEB-v1\scripts - .\Install-ROA2WEB.ps1 - -3. Configure environment: - notepad C:\inetpub\wwwroot\roa2web\backend\.env - -4. Start services: - .\Start-ROA2WEB.ps1 - -5. Access: http://localhost:8080 - - ->> UPDATES (New code version): -------------------------------- -1. Copy new deployment package to server (e.g., C:\Deploy\ROA2WEB-v2) - -2. Open PowerShell as Administrator: - cd C:\Deploy\ROA2WEB-v2\scripts - -3. Deploy (automatically stops, updates, and starts): - .\Stop-ROA2WEB.ps1 - .\Deploy-ROA2WEB.ps1 - .\Start-ROA2WEB.ps1 - - Note: Deploy-ROA2WEB.ps1 auto-detects source path (no parameters needed!) - -4. Done! Application updated with new code. - - ->> QUICK OPERATIONS: --------------------- - Restart app: .\Restart-ROA2WEB.ps1 - Stop app: .\Stop-ROA2WEB.ps1 - Start app: .\Start-ROA2WEB.ps1 - -================================================================================ -REQUIREMENTS -================================================================================ - - Windows Server 2016+ or Windows 10/11 - - IIS already installed (with ASP.NET Core Hosting Bundle) - - Python 3.8+ installed - - PowerShell 5.1+ (run as Administrator) - -NOTES: ------- - • Backend files do NOT include venv (virtual environment) - • Install-ROA2WEB.ps1 creates venv and installs dependencies automatically - • Deploy-ROA2WEB.ps1 creates backup before updating - • .env files are preserved during updates - • Application installs to: C:\inetpub\wwwroot\roa2web\ - -TROUBLESHOOTING: ----------------- - Check logs: C:\inetpub\wwwroot\roa2web\logs\ - Backend logs: C:\inetpub\wwwroot\roa2web\backend\backend.log - IIS logs: C:\inetpub\logs\LogFiles\ - -For detailed documentation, see: WINDOWS_DEPLOYMENT.md - -================================================================================ -"@ - Set-Content -Path $readmePath -Value $readme -Force - - # Calculate package size - $packageFiles = Get-ChildItem -Path $OutputPath -Recurse -File - $packageSize = ($packageFiles | Measure-Object -Property Length -Sum).Sum / 1MB - - Write-Success "Deployment package created: $OutputPath" - Write-Success "Total files: $(($packageFiles).Count)" - Write-Success "Total size: $([math]::Round($packageSize, 2)) MB" - - return $OutputPath -} - -function Copy-ToRemoteServer { - param( - [string]$LocalPath, - [string]$ServerHost, - [string]$ServerPath - ) - - Write-Step "Transferring to remote server..." - - try { - # Check if remote server is accessible - $pingResult = Test-Connection -ComputerName $ServerHost -Count 1 -Quiet - - if (-not $pingResult) { - Write-Warning "Server $ServerHost not reachable" - return $false - } - - # Use robocopy for efficient transfer (Windows) - if ($IsWindows -or $env:OS -match "Windows") { - $remotePath = "\\$ServerHost\$($ServerPath -replace ':', '$')" - - Write-Host " [*] Copying to: $remotePath" -ForegroundColor Yellow - - robocopy $LocalPath $remotePath /E /Z /R:3 /W:5 /MT:8 /NFL /NDL /NP - - if ($LASTEXITCODE -le 7) { - Write-Success "Files transferred successfully" - return $true - } else { - Write-Error "Transfer failed with code: $LASTEXITCODE" - return $false - } - } else { - # Use scp for Unix/WSL - Write-Warning "Remote copy via SCP not yet implemented" - Write-Host " Manual transfer required to: $ServerHost`:$ServerPath" -ForegroundColor Yellow - return $false - } - } catch { - Write-Error "Failed to transfer to server: $_" - return $false - } -} - -# ============================================================================= -# MAIN BUILD FLOW -# ============================================================================= - -function Main { - Write-Host @" - - ==================================================================== - ROA2WEB - Frontend Build Script - Build Vue.js frontend and create deployment package - ==================================================================== - -"@ -ForegroundColor Cyan - - try { - # Resolve paths - $backendSourcePath = Resolve-FullPath -Path $BackendSource - $frontendSourcePath = Resolve-FullPath -Path $FrontendSource - $outputPath = if ([System.IO.Path]::IsPathRooted($OutputPath)) { $OutputPath } else { Resolve-FullPath -Path $OutputPath } - - Write-Host "`nPaths:" -ForegroundColor Yellow - Write-Host " Backend Source: $backendSourcePath" - Write-Host " Frontend Source: $frontendSourcePath" - Write-Host " Output Path: $outputPath" - - # Check Node.js - Test-NodeJS - - # Build frontend - $distPath = Build-Frontend -SourcePath $frontendSourcePath - - # Create deployment package - $packagePath = New-DeploymentPackage ` - -FrontendDistPath $distPath ` - -BackendSourcePath $backendSourcePath ` - -OutputPath $outputPath - - # Transfer to server if specified - if ($ServerHost -and $ServerPath) { - $transferred = Copy-ToRemoteServer ` - -LocalPath $packagePath ` - -ServerHost $ServerHost ` - -ServerPath $ServerPath - } - - Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan - Write-Host " BUILD COMPLETED SUCCESSFULLY" -ForegroundColor Green - Write-Host ("=" * 70) -ForegroundColor Cyan - - Write-Host "`nDeployment Package: $packagePath" -ForegroundColor Yellow - - if ($ServerHost) { - Write-Host "`nNext Steps (on server $ServerHost):" -ForegroundColor Yellow - Write-Host " cd $ServerPath" - Write-Host " .\Deploy-ROA2WEB.ps1 -SourcePath ." - } else { - Write-Host "`nNext Steps:" -ForegroundColor Yellow - Write-Host " 1. Transfer '$packagePath' to your Windows Server" - Write-Host " 2. On the server, run: Deploy-ROA2WEB.ps1 -SourcePath ''" - } - - Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan - - } catch { - Write-Host "`n[FATAL ERROR] Build failed: $_" -ForegroundColor Red - Write-Host $_.ScriptStackTrace -ForegroundColor Red - exit 1 - } -} - -# Run main build -Main diff --git a/deployment/windows/scripts/Build-ROA2WEB.ps1 b/deployment/windows/scripts/Build-ROA2WEB.ps1 index 8f7ca49..c59bfcf 100644 --- a/deployment/windows/scripts/Build-ROA2WEB.ps1 +++ b/deployment/windows/scripts/Build-ROA2WEB.ps1 @@ -14,11 +14,12 @@ - Supports automatic server transfer .PARAMETER Component - Component(s) to build: - - All (default): Build complete package (frontend + backend + telegram bot) + Component(s) to build (optional - shows interactive menu if not specified): + - All: Build complete package (frontend + backend + telegram bot) - Frontend: Build frontend + backend files only - Backend: Copy backend files only (no frontend build) - TelegramBot: Build telegram bot package only + - If omitted: Interactive menu will be displayed .PARAMETER OutputPath Output path for deployment package (default: ./deploy-package) @@ -34,7 +35,11 @@ .EXAMPLE .\Build-ROA2WEB.ps1 - Build complete deployment package (all components) + Shows interactive menu to select components to build + +.EXAMPLE + .\Build-ROA2WEB.ps1 -Component All + Build complete deployment package (all components) without menu .EXAMPLE .\Build-ROA2WEB.ps1 -Component Frontend @@ -57,7 +62,7 @@ [CmdletBinding()] param( [ValidateSet("All", "Frontend", "Backend", "TelegramBot")] - [string]$Component = "All", + [string]$Component = "", [string]$OutputPath = "./deploy-package", [string]$ServerHost = "", @@ -74,7 +79,7 @@ $ErrorActionPreference = "Stop" $config = @{ BackendSource = "../../reports-app/backend" FrontendSource = "../../reports-app/frontend" - TelegramBotSource = "../../../reports-app/telegram-bot" + TelegramBotSource = "../../reports-app/telegram-bot" SharedSource = "../../shared" ConfigSource = "../config" RequiredNodeVersion = 16 @@ -114,6 +119,49 @@ function Resolve-FullPath { return $fullPath } +function Show-BuildMenu { + Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan + Write-Host " ROA2WEB - Build Component Selection Menu" -ForegroundColor Cyan + Write-Host ("=" * 70) -ForegroundColor Cyan + Write-Host "" + Write-Host " Select components to build:" -ForegroundColor Yellow + Write-Host "" + Write-Host " [1] All Components" -ForegroundColor White + Write-Host " (Frontend + Backend + Telegram Bot)" -ForegroundColor Gray + Write-Host "" + Write-Host " [2] Frontend + Backend" -ForegroundColor White + Write-Host " (Vue.js build + FastAPI backend files)" -ForegroundColor Gray + Write-Host "" + Write-Host " [3] Backend Only" -ForegroundColor White + Write-Host " (FastAPI backend files + shared modules)" -ForegroundColor Gray + Write-Host "" + Write-Host " [4] Telegram Bot Only" -ForegroundColor White + Write-Host " (Telegram bot standalone package)" -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 "All" } + "2" { return "Frontend" } + "3" { return "Backend" } + "4" { return "TelegramBot" } + "Q" { + Write-Host "`nBuild cancelled by user." -ForegroundColor Yellow + exit 0 + } + default { + Write-Host "Invalid choice. Please select 1-4 or Q." -ForegroundColor Red + } + } + } while ($true) +} + function Test-NodeJS { Write-Step "Checking Node.js installation..." @@ -142,37 +190,90 @@ function Test-NodeJS { } function Build-Frontend { - param([string]$SourcePath) + param( + [string]$SourcePath, + [string]$OutputPath + ) - Write-Step "Building Vue.js frontend..." + Write-Step "Building Vue.js frontend in isolated environment..." if (-not (Test-Path $SourcePath)) { throw "Frontend source path not found: $SourcePath" } - Push-Location $SourcePath - try { - # Clean node_modules if it exists - $nodeModulesPath = Join-Path $SourcePath "node_modules" - if (Test-Path $nodeModulesPath) { - Write-Step "Cleaning existing node_modules..." - try { - Remove-Item -Path $nodeModulesPath -Recurse -Force -ErrorAction Stop - Write-Success "Cleaned node_modules" - } catch { - Write-Warning "Could not remove node_modules: $_" - throw "Cannot proceed with locked node_modules. Close all IDEs and retry." + # Create temporary build directory + $tempBuildDir = Join-Path $OutputPath ".temp-frontend-build" + if (Test-Path $tempBuildDir) { + Write-Step "Cleaning existing temp build directory..." + Remove-Item -Path $tempBuildDir -Recurse -Force + } + New-Item -ItemType Directory -Path $tempBuildDir -Force | Out-Null + Write-Success "Created temp build directory (isolated from WSL)" + + # Copy frontend sources to temp (exclude node_modules, dist, .git) + Write-Step "Copying frontend sources to temp directory..." + $excludeDirs = @("node_modules", "dist", ".git", "__pycache__", ".vscode", ".idea") + $excludeFiles = @(".env", ".env.local", "*.log") + + Get-ChildItem -Path $SourcePath -Recurse | ForEach-Object { + $relativePath = $_.FullName.Substring($SourcePath.Length).TrimStart('\', '/') + + # Check if in excluded directory + $inExcludedDir = $false + foreach ($excludeDir in $excludeDirs) { + if ($relativePath -match "^$excludeDir" -or $relativePath -match "[\\/]$excludeDir[\\/]") { + $inExcludedDir = $true + break } } + if ($inExcludedDir) { return } - # Install dependencies - Write-Step "Installing npm dependencies..." + # Check if excluded file + $isExcludedFile = $false + foreach ($pattern in $excludeFiles) { + if ($_.Name -like $pattern) { + $isExcludedFile = $true + break + } + } + if ($isExcludedFile) { return } + + $destPath = Join-Path $tempBuildDir $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 "Frontend sources copied to temp" + + # Build in temp directory + Push-Location $tempBuildDir + try { + # Install dependencies (including devDependencies for Vite) + Write-Step "Installing npm dependencies (Windows binaries)..." + # Clear NODE_ENV to ensure devDependencies (vite, etc.) are installed + Remove-Item Env:\NODE_ENV -ErrorAction SilentlyContinue npm install | Out-Default + $nodeModulesPath = Join-Path $tempBuildDir "node_modules" if (-not (Test-Path $nodeModulesPath)) { throw "npm install failed: node_modules not created" } - Write-Success "Dependencies installed" + Write-Success "Dependencies installed in temp" + + # Verify Vite is installed + $vitePath = Join-Path $nodeModulesPath ".bin\vite.cmd" + if (-not (Test-Path $vitePath)) { + throw "Vite not found in node_modules - devDependencies not installed" + } # Build for production Write-Step "Building for production..." @@ -181,7 +282,7 @@ function Build-Frontend { Write-Success "Build completed" # Verify dist folder - $distPath = Join-Path $SourcePath "dist" + $distPath = Join-Path $tempBuildDir "dist" if (-not (Test-Path $distPath)) { throw "Build failed: dist folder not found" } @@ -417,6 +518,26 @@ function Copy-ConfigTemplates { } } +function Remove-TempDirectories { + param([string]$OutputPath) + + Write-Step "Cleaning up temporary build directories..." + + $tempBuildDir = Join-Path $OutputPath ".temp-frontend-build" + + if (Test-Path $tempBuildDir) { + try { + Remove-Item -Path $tempBuildDir -Recurse -Force -ErrorAction Stop + Write-Success "Temporary build directory cleaned" + } catch { + Write-Warning "Could not remove temp directory: $_" + Write-Warning "You may need to manually delete: $tempBuildDir" + } + } else { + Write-Success "No temporary directories to clean" + } +} + function Copy-DeploymentScripts { param( [string]$ScriptsSourcePath, @@ -652,7 +773,7 @@ function New-DeploymentPackage { switch ($ComponentType) { "All" { # Frontend - $frontendDistPath = Build-Frontend -SourcePath $Paths.FrontendSource + $frontendDistPath = Build-Frontend -SourcePath $Paths.FrontendSource -OutputPath $OutputPath $frontendDest = Join-Path $OutputPath "frontend" New-Item -ItemType Directory -Path $frontendDest -Force | Out-Null Write-Step "Copying frontend files..." @@ -678,7 +799,7 @@ function New-DeploymentPackage { "Frontend" { # Frontend build - $frontendDistPath = Build-Frontend -SourcePath $Paths.FrontendSource + $frontendDistPath = Build-Frontend -SourcePath $Paths.FrontendSource -OutputPath $OutputPath $frontendDest = Join-Path $OutputPath "frontend" New-Item -ItemType Directory -Path $frontendDest -Force | Out-Null Write-Step "Copying frontend files..." @@ -719,6 +840,11 @@ function New-DeploymentPackage { } } + # Cleanup temporary directories + if ($ComponentType -eq "All" -or $ComponentType -eq "Frontend") { + Remove-TempDirectories -OutputPath $OutputPath + } + # Copy deployment scripts Copy-DeploymentScripts -ScriptsSourcePath $PSScriptRoot -DestPath $OutputPath -ComponentType $ComponentType @@ -741,6 +867,11 @@ function New-DeploymentPackage { # ============================================================================= function Main { + # Show interactive menu if no component specified + if ([string]::IsNullOrWhiteSpace($Component)) { + $script:Component = Show-BuildMenu + } + $banner = @" ==================================================================== diff --git a/deployment/windows/scripts/Build-TelegramBot.ps1 b/deployment/windows/scripts/Build-TelegramBot.ps1 deleted file mode 100644 index 8d360cb..0000000 --- a/deployment/windows/scripts/Build-TelegramBot.ps1 +++ /dev/null @@ -1,809 +0,0 @@ -<# -================================================================================ -⚠️ DEPRECATED - This script is deprecated in favor of Build-ROA2WEB.ps1 -================================================================================ - -This script is maintained for backward compatibility but will be removed in -a future version. Please use the new unified build script instead: - - .\Build-ROA2WEB.ps1 -Component TelegramBot - -The new script provides: - - Unified build process for all components - - Better parameter validation - - Consistent output structure - - Support for building All, Frontend, Backend, or TelegramBot - -For complete package: .\Build-ROA2WEB.ps1 -Component All - -================================================================================ -#> - -<# -.SYNOPSIS - Build ROA2WEB Telegram Bot for Windows Server Deployment - -.DESCRIPTION - This script creates a deployment package for the Telegram bot: - - Copies application source files (app/) - - Copies requirements.txt - - Copies PowerShell deployment scripts - - Creates .env.example template - - Creates deployment README - - Excludes development files (venv, data, logs, etc.) - - Optionally transfers to remote server - -.PARAMETER SourcePath - Path to telegram-bot source (default: ../../reports-app/telegram-bot) - -.PARAMETER OutputPath - Output path for deployment package (default: ../deploy-package/telegram-bot) - -.PARAMETER ServerHost - Remote server hostname/IP for automatic deployment (optional) - -.PARAMETER ServerPath - Remote server path for automatic deployment (optional) - -.PARAMETER Clean - Clean output directory before building (default: true) - -.EXAMPLE - .\Build-TelegramBot.ps1 - Build with defaults - -.EXAMPLE - .\Build-TelegramBot.ps1 -OutputPath "D:\deployments\telegram-bot-$(Get-Date -Format 'yyyyMMdd')" - Build to custom output path - -.EXAMPLE - .\Build-TelegramBot.ps1 -ServerHost "10.0.20.36" -ServerPath "C:\Temp\telegram-bot-deploy" - Build and transfer to remote server - -.NOTES - Author: ROA2WEB Team - Requires: PowerShell 5.1+ - Can run on: WSL, Windows, Linux -#> - -[CmdletBinding()] -param( - [string]$SourcePath = "../../../reports-app/telegram-bot", - [string]$OutputPath = "../deploy-package/telegram-bot", - [string]$ServerHost = "", - [string]$ServerPath = "", - [bool]$Clean = $true -) - -$ErrorActionPreference = "Stop" - -# ============================================================================= -# 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 Resolve-FullPath { - param([string]$Path) - - $scriptDir = $PSScriptRoot - $fullPath = Join-Path $scriptDir $Path - - # Convert to absolute path and resolve .. and . properly - $fullPath = [System.IO.Path]::GetFullPath($fullPath) - - return $fullPath -} - -function Test-SourceDirectory { - param([string]$Path) - - Write-Step "Validating source directory..." - - if (-not (Test-Path $Path)) { - throw "Source path not found: $Path" - } - - # Check for required files/directories - $requiredPaths = @( - (Join-Path $Path "app"), - (Join-Path $Path "requirements.txt") - ) - - foreach ($reqPath in $requiredPaths) { - if (-not (Test-Path $reqPath)) { - throw "Required path not found: $reqPath" - } - } - - Write-Success "Source directory validated: $Path" -} - -function New-CleanOutputDirectory { - param([string]$Path) - - if ($Clean -and (Test-Path $Path)) { - Write-Step "Cleaning output directory..." - Remove-Item -Path $Path -Recurse -Force - Write-Success "Output directory cleaned" - } - - if (-not (Test-Path $Path)) { - New-Item -ItemType Directory -Path $Path -Force | Out-Null - Write-Success "Created output directory: $Path" - } -} - -function Copy-ApplicationFiles { - param( - [string]$SourcePath, - [string]$DestPath - ) - - Write-Step "Copying application files..." - - # Exclude patterns - $excludeDirs = @( - "venv", - "data", - "logs", - "temp", - "backups", - "__pycache__", - ".pytest_cache", - "tests", - ".git" - ) - - $excludeFiles = @( - ".env", - "*.pyc", - "*.pyo", - "*.log", - "*.db", - ".DS_Store", - "Thumbs.db" - ) - - # Create app directory in destination - $destApp = Join-Path $DestPath "app" - New-Item -ItemType Directory -Path $destApp -Force | Out-Null - - # Copy app/ directory - $sourceApp = Join-Path $SourcePath "app" - $fileCount = 0 - - Get-ChildItem -Path $sourceApp -Recurse | ForEach-Object { - # Check if in excluded directory - $inExcludedDir = $false - foreach ($excludeDir in $excludeDirs) { - if ($_.FullName -like "*\$excludeDir\*" -or $_.FullName -like "*/$excludeDir/*") { - $inExcludedDir = $true - break - } - } - - if ($inExcludedDir) { - return - } - - # Check if excluded file - $isExcludedFile = $false - foreach ($pattern in $excludeFiles) { - if ($_.Name -like $pattern) { - $isExcludedFile = $true - break - } - } - - if ($isExcludedFile) { - return - } - - # Calculate relative path and destination - $relativePath = $_.FullName.Substring($sourceApp.Length) - $destFile = Join-Path $destApp $relativePath - - if ($_.PSIsContainer) { - # Create directory - if (-not (Test-Path $destFile)) { - New-Item -ItemType Directory -Path $destFile -Force | Out-Null - } - } else { - # Copy file - $destFileDir = Split-Path $destFile -Parent - if (-not (Test-Path $destFileDir)) { - New-Item -ItemType Directory -Path $destFileDir -Force | Out-Null - } - Copy-Item -Path $_.FullName -Destination $destFile -Force - $fileCount++ - } - } - - Write-Success "Copied $fileCount application files" -} - -function Copy-RequirementsFile { - param( - [string]$SourcePath, - [string]$DestPath - ) - - Write-Step "Copying requirements.txt..." - - $sourceReq = Join-Path $SourcePath "requirements.txt" - $destReq = Join-Path $DestPath "requirements.txt" - - if (Test-Path $sourceReq) { - Copy-Item -Path $sourceReq -Destination $destReq -Force - Write-Success "requirements.txt copied" - } else { - Write-Warning "requirements.txt not found in source" - } -} - -function New-EnvironmentTemplate { - param([string]$DestPath) - - Write-Step "Creating .env.example template..." - - $envTemplate = @" -# ROA2WEB Telegram Bot - Production Configuration Template - -# Telegram Bot Configuration -TELEGRAM_BOT_TOKEN=your_production_bot_token_from_@BotFather - -# Claude API Configuration -CLAUDE_API_KEY=your_production_claude_api_key_from_anthropic_console - -# Backend API Configuration -BACKEND_URL=http://localhost:8000 -BACKEND_TIMEOUT=30 - -# SQLite Database Configuration -SQLITE_DB_PATH=C:\inetpub\wwwroot\roa2web\telegram-bot\data\telegram_bot.db - -# Internal API Configuration (for backend callbacks) -INTERNAL_API_HOST=127.0.0.1 -INTERNAL_API_PORT=8002 - -# Logging Configuration -LOG_LEVEL=INFO -LOG_FILE=C:\inetpub\wwwroot\roa2web\telegram-bot\logs\bot.log - -# Environment -ENVIRONMENT=production - -# Authentication Configuration -AUTH_CODE_EXPIRY_MINUTES=15 -JWT_REFRESH_THRESHOLD_MINUTES=5 - -# Session Configuration -SESSION_TIMEOUT_MINUTES=60 -MAX_CONVERSATION_HISTORY=20 -"@ - - $envPath = Join-Path $DestPath ".env.example" - Set-Content -Path $envPath -Value $envTemplate -Encoding UTF8 - Write-Success ".env.example template created" -} - -function Copy-DeploymentScripts { - param( - [string]$SourceScriptsPath, - [string]$DestPath - ) - - Write-Step "Copying deployment scripts..." - - $scriptsDir = Join-Path $DestPath "scripts" - New-Item -ItemType Directory -Path $scriptsDir -Force | Out-Null - - # List of scripts to copy - $scripts = @( - "Install-TelegramBot.ps1", - "Deploy-TelegramBot.ps1", - "Start-TelegramBot.ps1", - "Stop-TelegramBot.ps1", - "Restart-TelegramBot.ps1", - "Backup-TelegramDB.ps1", - "Setup-DailyBackup.ps1", - "Setup-ClaudeAuth.ps1" - ) - - $copiedCount = 0 - foreach ($script in $scripts) { - $sourcePath = Join-Path $SourceScriptsPath $script - if (Test-Path $sourcePath) { - $destScript = Join-Path $scriptsDir $script - Copy-Item -Path $sourcePath -Destination $destScript -Force - $copiedCount++ - } else { - Write-Warning "Script not found: $script" - } - } - - Write-Success "Copied $copiedCount deployment scripts" -} - -function Copy-ConfigTemplates { - param( - [string]$SourceConfigPath, - [string]$DestPath - ) - - Write-Step "Copying configuration templates..." - - $configDir = Join-Path $DestPath "config" - New-Item -ItemType Directory -Path $configDir -Force | Out-Null - - # Copy .env.production.windows.telegram if it exists - $prodEnv = Join-Path $SourceConfigPath ".env.production.windows.telegram" - if (Test-Path $prodEnv) { - Copy-Item -Path $prodEnv -Destination (Join-Path $configDir ".env.production.windows.telegram") -Force - Write-Success "Production config template copied" - } -} - -function New-DeploymentReadme { - param([string]$DestPath) - - Write-Step "Creating deployment README..." - - $readme = @" -# ROA2WEB Telegram Bot - Deployment Package - -**Created**: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss") -**Version**: Production Deployment Package - -## Contents - -- ``app/`` - Telegram bot application source code -- ``scripts/`` - PowerShell deployment and management scripts -- ``config/`` - Configuration templates -- ``requirements.txt`` - Python dependencies -- ``.env.example`` - Environment configuration template -- ``README.txt`` - This file - -## Deployment Instructions - -### 1. Initial Installation - -Run as Administrator on Windows Server: - -```powershell -cd scripts -.\Install-TelegramBot.ps1 -``` - -This will: -- Check Python 3.11+ installation -- Install NSSM (service manager) -- Create directory structure -- Create virtual environment -- Install Python dependencies -- Create Windows Service (ROA2WEB-TelegramBot) -- Create configuration template - -### 2. Configuration - -Edit the ``.env`` file: - -```powershell -notepad C:\inetpub\wwwroot\roa2web\telegram-bot\.env -``` - -Required settings: -- ``TELEGRAM_BOT_TOKEN`` - Get from @BotFather on Telegram -- ``CLAUDE_API_KEY`` - Get from Anthropic console -- ``BACKEND_URL`` - Usually http://localhost:8000 - -### 3. Start Service - -```powershell -cd C:\inetpub\wwwroot\roa2web\telegram-bot\scripts -.\Start-TelegramBot.ps1 -``` - -### 4. Verify Deployment - -Check health endpoint: - -```powershell -Invoke-WebRequest http://localhost:8002/internal/health -``` - -View logs: - -```powershell -Get-Content C:\inetpub\wwwroot\roa2web\telegram-bot\logs\stdout.log -Tail 50 -Wait -``` - -## Management Commands - -- **Start**: ``.\Start-TelegramBot.ps1`` -- **Stop**: ``.\Stop-TelegramBot.ps1`` -- **Restart**: ``.\Restart-TelegramBot.ps1`` -- **Deploy Update**: ``.\Deploy-TelegramBot.ps1 -SourcePath "path\to\new\package"`` -- **Backup Database**: ``.\Backup-TelegramDB.ps1`` -- **Setup Daily Backup**: ``.\Setup-DailyBackup.ps1`` - -## Directory Structure - -``` -C:\inetpub\wwwroot\roa2web\telegram-bot\ -├── app\ # Application source code -├── venv\ # Python virtual environment -├── data\ # SQLite database (telegram_bot.db) -├── logs\ # Application logs -├── backups\ # Database backups -├── temp\ # Temporary files -├── scripts\ # Management scripts -├── config\ # Configuration templates -├── requirements.txt # Python dependencies -└── .env # Environment configuration -``` - -## Troubleshooting - -### Service won't start - -Check logs: - -```powershell -Get-Content C:\inetpub\wwwroot\roa2web\telegram-bot\logs\stderr.log -Tail 100 -``` - -### Bot not responding on Telegram - -1. Verify service is running: ``Get-Service ROA2WEB-TelegramBot`` -2. Check health endpoint: ``Invoke-WebRequest http://localhost:8002/internal/health`` -3. Verify ``.env`` configuration (TELEGRAM_BOT_TOKEN) -4. Check logs for errors - -### Database errors - -Run database backup and check integrity: - -```powershell -.\Backup-TelegramDB.ps1 -``` - -## Support - -- Documentation: ``C:\inetpub\wwwroot\roa2web\deployment\windows\docs\TELEGRAM_BOT_DEPLOYMENT.md`` -- Project repository: ROA2WEB on GitHub -- Contact: ROA2WEB Team - -"@ - - $readmePath = Join-Path $DestPath "README.txt" - Set-Content -Path $readmePath -Value $readme -Encoding UTF8 - Write-Success "Deployment README created" -} - -function Copy-ClaudeCredentials { - param([string]$PackagePath) - - Write-Host "`n" + ("=" * 60) -ForegroundColor Yellow - Write-Host " OPTIONAL: Claude Credentials" -ForegroundColor Yellow - Write-Host ("=" * 60) -ForegroundColor Yellow - Write-Host "" - Write-Host "If you have Claude Pro/Max credentials from 'claude-code login'," - Write-Host "you can include them in the deployment package for easy setup." - Write-Host "" - - $response = Read-Host "Copy Claude credentials to deployment package? (Y/N)" - - if ($response -eq "Y" -or $response -eq "y") { - # Try to find credentials automatically in both possible locations - $possiblePaths = @( - (Join-Path $env:USERPROFILE ".claude\.credentials.json"), # Correct location - (Join-Path $env:APPDATA "claude\credentials.json") # Alternative location - ) - - $defaultCredPath = $null - foreach ($path in $possiblePaths) { - if (Test-Path $path) { - $defaultCredPath = $path - break - } - } - - if ($defaultCredPath) { - Write-Host "`nFound credentials at: $defaultCredPath" -ForegroundColor Green - $usePath = Read-Host "Use this path? (Y/N)" - - if ($usePath -eq "Y" -or $usePath -eq "y") { - $credPath = $defaultCredPath - } else { - $credPath = Read-Host "Enter path to credentials.json" - } - } else { - Write-Host "`nCredentials not found at default locations" -ForegroundColor Yellow - Write-Host " Checked: $($possiblePaths -join ', ')" -ForegroundColor Gray - $credPath = Read-Host "Enter full path to credentials.json" - } - - if (Test-Path $credPath) { - try { - $destCredPath = Join-Path $PackagePath "claude-credentials.json" - Copy-Item -Path $credPath -Destination $destCredPath -Force - Write-Success "Claude credentials copied to deployment package" - Write-Host " Location: $destCredPath" -ForegroundColor Gray - Write-Host " The Setup-ClaudeAuth.ps1 script will detect and use this file automatically" -ForegroundColor Gray - return $true - } catch { - Write-Warning "Failed to copy credentials: $_" - return $false - } - } else { - Write-Warning "Credentials file not found at: $credPath" - return $false - } - } else { - Write-Host "Skipping credentials copy. You can set up Claude auth manually on the server." -ForegroundColor Gray - return $false - } -} - -function Transfer-ToServerSSH { - param([string]$PackagePath) - - Write-Host "`n" + ("=" * 60) -ForegroundColor Yellow - Write-Host " OPTIONAL: SSH Transfer to Server" -ForegroundColor Yellow - Write-Host ("=" * 60) -ForegroundColor Yellow - Write-Host "" - Write-Host "You can automatically transfer the deployment package to" - Write-Host "the Windows Server via SSH/SCP (requires SSH server on Windows)." - Write-Host "" - - $response = Read-Host "Transfer package to server via SSH? (Y/N)" - - if ($response -eq "Y" -or $response -eq "y") { - Write-Host "" - $sshUser = Read-Host "Enter SSH username (e.g., Administrator)" - $sshHost = Read-Host "Enter server hostname/IP (e.g., 10.0.20.36)" - $sshPort = Read-Host "Enter SSH port (default: 22, press Enter for default)" - if ([string]::IsNullOrWhiteSpace($sshPort)) { - $sshPort = "22" - } - - $remotePath = Read-Host "Enter remote path (e.g., C:/Temp/telegram-bot-deploy)" - - # Convert Windows path to SCP format if needed - $remotePath = $remotePath -replace '\\', '/' - if ($remotePath -match '^[A-Za-z]:') { - # Convert C:/path to /c/path format for SCP - $remotePath = $remotePath -replace '^([A-Za-z]):', '/$1' - } - - Write-Host "`nTransfer Configuration:" -ForegroundColor Cyan - Write-Host " Source: $PackagePath" - Write-Host " Target: ${sshUser}@${sshHost}:${remotePath}" - Write-Host " Port: $sshPort" - Write-Host "" - - $confirm = Read-Host "Proceed with transfer? (Y/N)" - - if ($confirm -eq "Y" -or $confirm -eq "y") { - Write-Step "Transferring package via SCP..." - - try { - # Use SCP to transfer - $scpTarget = "${sshUser}@${sshHost}:${remotePath}" - - # Build SCP command - $scpArgs = @( - "-P", $sshPort, - "-r", - $PackagePath, - $scpTarget - ) - - Write-Host " Running: scp $scpArgs" -ForegroundColor Gray - - & scp @scpArgs - - if ($LASTEXITCODE -eq 0) { - Write-Success "Package transferred successfully!" - Write-Host " Remote location: $scpTarget" -ForegroundColor Gray - return $true - } else { - Write-Warning "SCP transfer failed with exit code: $LASTEXITCODE" - Write-Host " You can transfer manually via RDP or network share" -ForegroundColor Yellow - return $false - } - } catch { - Write-Warning "Transfer failed: $_" - Write-Host " Make sure 'scp' is available in PATH" -ForegroundColor Yellow - Write-Host " Alternative: Use WinSCP, FileZilla, or manual RDP copy" -ForegroundColor Yellow - return $false - } - } else { - Write-Host "Transfer cancelled" -ForegroundColor Gray - return $false - } - } else { - Write-Host "Skipping SSH transfer. Transfer package manually via RDP or network share." -ForegroundColor Gray - return $false - } -} - -function Show-PackageSummary { - param( - [string]$PackagePath, - [bool]$CredentialsCopied, - [bool]$TransferredToServer - ) - - Write-Host "`n" + ("=" * 80) -ForegroundColor Cyan - Write-Host " DEPLOYMENT PACKAGE CREATED SUCCESSFULLY" -ForegroundColor Green - Write-Host ("=" * 80) -ForegroundColor Cyan - - Write-Host "`nPackage Location:" -ForegroundColor Yellow - Write-Host " $PackagePath" - - # Calculate package size - $files = Get-ChildItem -Path $PackagePath -Recurse -File - $totalSize = ($files | Measure-Object -Property Length -Sum).Sum / 1MB - - Write-Host "`nPackage Contents:" -ForegroundColor Yellow - Write-Host " Files: $($files.Count)" - Write-Host " Total Size: $([math]::Round($totalSize, 2)) MB" - if ($CredentialsCopied) { - Write-Host " ✓ Claude credentials included" -ForegroundColor Green - } - - if ($TransferredToServer) { - Write-Host "`nDeployment Status:" -ForegroundColor Yellow - Write-Host " ✓ Package transferred to server via SSH" -ForegroundColor Green - Write-Host "" - Write-Host "Next Steps on Server:" -ForegroundColor Yellow - Write-Host " 1. SSH to server or RDP" - Write-Host " 2. Navigate to deployment location" - Write-Host " 3. Run: scripts\Install-TelegramBot.ps1 (as Administrator)" - Write-Host " 4. Run: scripts\Setup-ClaudeAuth.ps1 (will auto-detect credentials)" - Write-Host " 5. Configure: .env file with Telegram bot token" - Write-Host " 6. Run: scripts\Start-TelegramBot.ps1" - } else { - Write-Host "`nNext Steps:" -ForegroundColor Yellow - Write-Host " 1. Transfer package to Windows Server (10.0.20.36)" - Write-Host " - Via network share: Copy-Item -Path $PackagePath -Destination \\10.0.20.36\C$\Temp\telegram-bot-deploy -Recurse" - Write-Host " - Via RDP: Manual copy" - Write-Host " 2. On server, run: scripts\Install-TelegramBot.ps1 (as Administrator)" - Write-Host " 3. Configure: .env file with production credentials" - Write-Host " 4. Run: scripts\Start-TelegramBot.ps1" - } - - Write-Host "`n" + ("=" * 80) -ForegroundColor Cyan -} - -function Transfer-ToServer { - param( - [string]$PackagePath, - [string]$ServerHost, - [string]$ServerPath - ) - - if (-not $ServerHost -or -not $ServerPath) { - return - } - - Write-Step "Transferring to server $ServerHost..." - - try { - # Check if server is reachable - if (Test-Connection -ComputerName $ServerHost -Count 1 -Quiet) { - Write-Success "Server is reachable" - } else { - Write-Warning "Server is not reachable, skipping transfer" - return - } - - # Transfer files (using Copy-Item for network path or RoboCopy) - $networkPath = "\\$ServerHost\$(($ServerPath -replace ':', '$'))" - - Write-Step "Copying to: $networkPath" - - # Ensure destination exists - if (-not (Test-Path $networkPath)) { - New-Item -ItemType Directory -Path $networkPath -Force | Out-Null - } - - # Copy package - Copy-Item -Path "$PackagePath\*" -Destination $networkPath -Recurse -Force - - Write-Success "Package transferred to server" - } catch { - Write-Warning "Failed to transfer to server: $_" - Write-Host " You can manually copy the package from: $PackagePath" -ForegroundColor Yellow - } -} - -# ============================================================================= -# MAIN BUILD FLOW -# ============================================================================= - -function Main { - Write-Host @" - - ==================================================================== - ROA2WEB Telegram Bot - Build Deployment Package - Creating production-ready deployment package - ==================================================================== - -"@ -ForegroundColor Cyan - - # Resolve paths - $sourcePath = Resolve-FullPath -Path $SourcePath - $outputPath = Resolve-FullPath -Path $OutputPath - $scriptsPath = $PSScriptRoot - - Write-Step "Build Configuration" - Write-Host " Source: $sourcePath" -ForegroundColor Gray - Write-Host " Output: $outputPath" -ForegroundColor Gray - - try { - # Build steps - Test-SourceDirectory -Path $sourcePath - New-CleanOutputDirectory -Path $outputPath - Copy-ApplicationFiles -SourcePath $sourcePath -DestPath $outputPath - Copy-RequirementsFile -SourcePath $sourcePath -DestPath $outputPath - New-EnvironmentTemplate -DestPath $outputPath - Copy-DeploymentScripts -SourceScriptsPath $scriptsPath -DestPath $outputPath - - # Copy config templates if they exist - $configPath = Join-Path (Split-Path $scriptsPath -Parent) "config" - if (Test-Path $configPath) { - Copy-ConfigTemplates -SourceConfigPath $configPath -DestPath $outputPath - } - - New-DeploymentReadme -DestPath $outputPath - - # Interactive: Copy Claude credentials to package - $credentialsCopied = Copy-ClaudeCredentials -PackagePath $outputPath - - # Interactive: Transfer to server via SSH - $transferredToServer = $false - if (-not ($ServerHost -and $ServerPath)) { - # Interactive SSH transfer - $transferredToServer = Transfer-ToServerSSH -PackagePath $outputPath - } else { - # Legacy non-interactive transfer (if parameters provided) - Transfer-ToServer -PackagePath $outputPath -ServerHost $ServerHost -ServerPath $ServerPath - $transferredToServer = $true - } - - # Show summary with deployment status - Show-PackageSummary -PackagePath $outputPath -CredentialsCopied $credentialsCopied -TransferredToServer $transferredToServer - - Write-Host "`nBuild completed successfully!" -ForegroundColor Green - - } catch { - Write-Host "`n[BUILD FAILED] $_" -ForegroundColor Red - Write-Host $_.ScriptStackTrace -ForegroundColor Red - exit 1 - } -} - -# Run main build -Main diff --git a/deployment/windows/scripts/Deploy-ROA2WEB.ps1 b/deployment/windows/scripts/Deploy-ROA2WEB.ps1 deleted file mode 100644 index 91333f3..0000000 --- a/deployment/windows/scripts/Deploy-ROA2WEB.ps1 +++ /dev/null @@ -1,535 +0,0 @@ -<# -.SYNOPSIS - ROA2WEB - Quick Deployment/Update Script for Windows Server - -.DESCRIPTION - This script performs rapid deployment or updates of ROA2WEB application: - - Auto-detects source path (use from scripts/ directory) - - Creates backup of current deployment - - Stops backend service - - Updates backend and/or frontend files - - Installs new Python dependencies if changed - - Restarts backend service - - Validates deployment health - -.PARAMETER InstallPath - Target installation path (default: C:\inetpub\wwwroot\roa2web) - -.PARAMETER BackupEnabled - Create backup before deployment (default: true) - -.PARAMETER RestartService - Restart backend service after deployment (default: true) - -.PARAMETER UpdateBackend - Update backend files (default: true) - -.PARAMETER UpdateFrontend - Update frontend files (default: true) - -.PARAMETER ForceInstallDependencies - Force reinstall all Python dependencies even if requirements.txt hasn't changed (default: false) - -.EXAMPLE - cd C:\Deploy\ROA2WEB\scripts - .\Deploy-ROA2WEB.ps1 - Deploy from current deployment package (auto-detected) - -.EXAMPLE - .\Deploy-ROA2WEB.ps1 -UpdateBackend -UpdateFrontend:$false - Update only backend files - -.EXAMPLE - .\Deploy-ROA2WEB.ps1 -ForceInstallDependencies $true - Update files and force reinstall all Python dependencies - -.NOTES - Author: ROA2WEB Team - Requires: PowerShell 5.1+, Administrator privileges - Must be run from deployment package's scripts/ directory -#> - -[CmdletBinding()] -param( - [string]$InstallPath = "C:\inetpub\wwwroot\roa2web", - [bool]$BackupEnabled = $true, - [bool]$RestartService = $true, - [bool]$UpdateBackend = $true, - [bool]$UpdateFrontend = $true, - [bool]$ForceInstallDependencies = $false -) - -$ErrorActionPreference = "Stop" - -# ============================================================================= -# CONFIGURATION -# ============================================================================= - -# Auto-detect source path: if running from scripts/ subdirectory, use parent -$detectedSourcePath = $PSScriptRoot -if ((Split-Path $PSScriptRoot -Leaf) -eq "scripts") { - $detectedSourcePath = Split-Path $PSScriptRoot -Parent -} - -$script:Config = @{ - AppName = "ROA2WEB" - ServiceName = "ROA2WEB-Backend" - InstallPath = $InstallPath - BackendPath = Join-Path $InstallPath "backend" - FrontendPath = Join-Path $InstallPath "frontend" - BackupPath = Join-Path $InstallPath "backups" - LogsPath = Join-Path $InstallPath "logs" - SourcePath = $detectedSourcePath -} - -# ============================================================================= -# 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 Test-Administrator { - $identity = [Security.Principal.WindowsIdentity]::GetCurrent() - $principal = [Security.Principal.WindowsPrincipal]$identity - return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) -} - -function New-BackupDirectory { - if (-not (Test-Path $Config.BackupPath)) { - New-Item -ItemType Directory -Path $Config.BackupPath -Force | Out-Null - } -} - -function Backup-CurrentDeployment { - if (-not $BackupEnabled) { - Write-Warning "Backup disabled, skipping..." - return $null - } - - Write-Step "Creating backup of current deployment..." - - New-BackupDirectory - - $timestamp = Get-Date -Format "yyyyMMdd-HHmmss" - $backupName = "backup-$timestamp" - $backupFullPath = Join-Path $Config.BackupPath $backupName - - try { - # Create backup directory - New-Item -ItemType Directory -Path $backupFullPath -Force | Out-Null - - # Backup backend - if ((Test-Path $Config.BackendPath) -and $UpdateBackend) { - $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) -and $UpdateFrontend) { - $backupFrontendPath = Join-Path $backupFullPath "frontend" - Copy-Item -Path $Config.FrontendPath -Destination $backupFrontendPath -Recurse -Force -ErrorAction SilentlyContinue - Write-Success "Frontend backed up" - } - - Write-Success "Backup created at: $backupFullPath" - - # Clean old backups (keep last 10) - $allBackups = Get-ChildItem -Path $Config.BackupPath -Directory | 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 - } -} - -function Stop-BackendService { - Write-Step "Stopping backend service..." - - try { - $service = Get-Service -Name $Config.ServiceName -ErrorAction SilentlyContinue - - if (-not $service) { - Write-Warning "Service $($Config.ServiceName) not found" - return - } - - if ($service.Status -eq "Running") { - Stop-Service -Name $Config.ServiceName -Force - Start-Sleep -Seconds 2 - - # Wait for service to stop - $timeout = 30 - $elapsed = 0 - while ($service.Status -ne "Stopped" -and $elapsed -lt $timeout) { - Start-Sleep -Seconds 1 - $service.Refresh() - $elapsed++ - } - - if ($service.Status -eq "Stopped") { - Write-Success "Service stopped successfully" - } else { - Write-Warning "Service did not stop within timeout" - } - } else { - Write-Success "Service already stopped" - } - } catch { - Write-Error "Failed to stop service: $_" - throw - } -} - -function Update-BackendFiles { - if (-not $UpdateBackend) { - Write-Warning "Backend update disabled, skipping..." - return - } - - Write-Step "Updating backend files..." - - $sourceBackend = Join-Path $Config.SourcePath "backend" - - if (-not (Test-Path $sourceBackend)) { - Write-Warning "Source backend path not found: $sourceBackend" - return - } - - try { - # Copy all files except .env (preserve existing config) - $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 excluded files - $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" - - # Check if requirements.txt changed or if force install is requested - $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 { "" } - $requirementsChanged = $sourceHash -ne $destHash - - if ($requirementsChanged -or $ForceInstallDependencies) { - if ($ForceInstallDependencies) { - Write-Step "Force installing Python dependencies..." - } else { - Write-Step "Requirements changed, updating Python dependencies..." - } - - Copy-Item -Path $sourceReq -Destination $destReq -Force - - try { - # Verify Python is available - $pythonCmd = Get-Command python -ErrorAction SilentlyContinue - if (-not $pythonCmd) { - Write-Error "Python not found in PATH. Please ensure Python 3.11+ is installed." - throw "Python not found" - } - - Write-Host " Installing/upgrading dependencies from: $destReq" -ForegroundColor Gray - & python -m pip install -r $destReq --upgrade - - if ($LASTEXITCODE -eq 0) { - Write-Success "Python dependencies installed successfully" - } else { - Write-Error "pip install failed with exit code: $LASTEXITCODE" - throw "Dependency installation failed" - } - } catch { - Write-Error "Failed to update Python dependencies: $_" - Write-Warning "Try running manually: python -m pip install -r $destReq" - throw - } - } else { - Write-Success "Python dependencies unchanged" - } - } else { - Write-Warning "requirements.txt not found in source package" - } - } catch { - Write-Error "Failed to update backend files: $_" - throw - } -} - -function Update-FrontendFiles { - if (-not $UpdateFrontend) { - Write-Warning "Frontend update disabled, skipping..." - return - } - - Write-Step "Updating frontend files..." - - $sourceFrontend = Join-Path $Config.SourcePath "frontend" - - if (-not (Test-Path $sourceFrontend)) { - Write-Warning "Source frontend path not found: $sourceFrontend" - Write-Warning "Expected path: $sourceFrontend" - return - } - - 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 - - # Ensure web.config exists - $webConfigPath = Join-Path $Config.FrontendPath "web.config" - $webConfigTemplate = Join-Path (Split-Path $PSScriptRoot -Parent) "config\web.config" - - if (-not (Test-Path $webConfigPath) -and (Test-Path $webConfigTemplate)) { - Copy-Item -Path $webConfigTemplate -Destination $webConfigPath -Force - Write-Success "web.config restored from template" - } - - Write-Success "Frontend files updated" - } catch { - Write-Error "Failed to update frontend files: $_" - throw - } -} - -function Start-BackendService { - if (-not $RestartService) { - Write-Warning "Service restart disabled, skipping..." - return - } - - Write-Step "Starting backend service..." - - try { - $service = Get-Service -Name $Config.ServiceName -ErrorAction SilentlyContinue - - if (-not $service) { - Write-Error "Service $($Config.ServiceName) not found" - return - } - - Start-Service -Name $Config.ServiceName - Start-Sleep -Seconds 3 - - # Wait for service to start - $timeout = 30 - $elapsed = 0 - while ($service.Status -ne "Running" -and $elapsed -lt $timeout) { - Start-Sleep -Seconds 1 - $service.Refresh() - $elapsed++ - } - - if ($service.Status -eq "Running") { - Write-Success "Service started successfully" - } else { - Write-Error "Service failed to start (Status: $($service.Status))" - Write-Warning "Check logs at: $($Config.LogsPath)\backend-stderr.log" - } - } catch { - Write-Error "Failed to start service: $_" - throw - } -} - -function Test-DeploymentHealth { - Write-Step "Testing deployment health..." - - Start-Sleep -Seconds 5 - - try { - # Get service port from .env or use default - $envFile = Join-Path $Config.BackendPath ".env" - $port = 8000 - - if (Test-Path $envFile) { - $portLine = Get-Content $envFile | Where-Object { $_ -match "^PORT=(\d+)" } - if ($portLine) { - $port = [int]$matches[1] - } - } - - # Test backend health endpoint - $healthUrl = "http://localhost:$port/health" - $response = Invoke-WebRequest -Uri $healthUrl -UseBasicParsing -TimeoutSec 10 - - if ($response.StatusCode -eq 200) { - Write-Success "Backend health check passed" - Write-Success "Response: $($response.Content)" - } else { - Write-Warning "Health check returned status: $($response.StatusCode)" - } - - # Test frontend (IIS) - try { - $frontendResponse = Invoke-WebRequest -Uri "http://localhost/" -UseBasicParsing -TimeoutSec 5 - if ($frontendResponse.StatusCode -eq 200) { - Write-Success "Frontend health check passed" - } - } catch { - Write-Warning "Frontend health check failed: $_" - } - } catch { - Write-Warning "Health check failed: $_" - Write-Warning "The service may need more time to start" - Write-Warning "Check logs: $($Config.LogsPath)\backend-stderr.log" - } -} - -function Show-DeploymentSummary { - param([string]$BackupPath, [datetime]$StartTime) - - $duration = (Get-Date) - $StartTime - - Write-Host "`n" + ("=" * 80) -ForegroundColor Cyan - Write-Host " DEPLOYMENT COMPLETED" -ForegroundColor Green - Write-Host ("=" * 80) -ForegroundColor Cyan - - Write-Host "`nDeployment Summary:" -ForegroundColor Yellow - Write-Host " Duration: $($duration.TotalSeconds) seconds" - Write-Host " Backend Updated: $UpdateBackend" - Write-Host " Frontend Updated: $UpdateFrontend" - if ($BackupPath) { - Write-Host " Backup Location: $BackupPath" - } - - Write-Host "`nApplication Status:" -ForegroundColor Yellow - try { - $service = Get-Service -Name $Config.ServiceName -ErrorAction SilentlyContinue - if ($service) { - Write-Host " Backend Service: $($service.Status)" -ForegroundColor $(if ($service.Status -eq "Running") { "Green" } else { "Red" }) - } - } catch { - Write-Host " Backend Service: Unknown" -ForegroundColor Yellow - } - - Write-Host "`nAccess Points:" -ForegroundColor Yellow - Write-Host " Web Application: http://localhost" - Write-Host " API Documentation: http://localhost/api/docs" - - Write-Host "`nManagement:" -ForegroundColor Yellow - Write-Host " View Logs: Get-Content $($Config.LogsPath)\backend-stdout.log -Tail 50 -Wait" - Write-Host " Restart Service: .\Restart-ROA2WEB.ps1" - Write-Host " Rollback: Use backup at $BackupPath" - - Write-Host "`n" + ("=" * 80) -ForegroundColor Cyan -} - -# ============================================================================= -# MAIN DEPLOYMENT FLOW -# ============================================================================= - -function Main { - Write-Host @" - - ==================================================================== - ROA2WEB - Deployment Script - Fast deployment and updates for Windows Server + IIS - ==================================================================== - -"@ -ForegroundColor Cyan - - $startTime = Get-Date - - # Check prerequisites - Write-Step "Checking prerequisites..." - - if (-not (Test-Administrator)) { - Write-Error "This script must be run as Administrator" - exit 1 - } - Write-Success "Running as Administrator" - - if (-not (Test-Path $Config.InstallPath)) { - Write-Error "Installation path not found: $($Config.InstallPath)" - Write-Host " Run Install-ROA2WEB.ps1 first" -ForegroundColor Yellow - exit 1 - } - Write-Success "Installation path exists" - - # Check if this is first deployment (no backend files yet) - $isFirstDeployment = -not (Test-Path (Join-Path $Config.BackendPath "app")) - if ($isFirstDeployment) { - Write-Warning "First deployment detected - will install all dependencies" - $script:ForceInstallDependencies = $true - } - - try { - # Deployment steps - $backupPath = Backup-CurrentDeployment - Stop-BackendService - Update-BackendFiles - Update-FrontendFiles - Start-BackendService - Test-DeploymentHealth - Show-DeploymentSummary -BackupPath $backupPath -StartTime $startTime - - Write-Host "`nDeployment completed successfully!" -ForegroundColor Green - - } catch { - Write-Host "`n[FATAL ERROR] Deployment failed: $_" -ForegroundColor Red - Write-Host $_.ScriptStackTrace -ForegroundColor Red - - # Attempt rollback if backup exists - if ($backupPath -and (Test-Path $backupPath)) { - Write-Host "`nAttempting automatic rollback..." -ForegroundColor Yellow - # TODO: Implement rollback logic - } - - exit 1 - } -} - -# Run main deployment -Main diff --git a/deployment/windows/scripts/Deploy-TelegramBot.ps1 b/deployment/windows/scripts/Deploy-TelegramBot.ps1 deleted file mode 100644 index 48b9efa..0000000 --- a/deployment/windows/scripts/Deploy-TelegramBot.ps1 +++ /dev/null @@ -1,598 +0,0 @@ -<# -.SYNOPSIS - ROA2WEB Telegram Bot - Quick Deployment/Update Script for Windows Server - -.DESCRIPTION - This script performs rapid deployment or updates of ROA2WEB Telegram Bot: - - Auto-detects source path (use from scripts/ directory) - - Creates backup of current deployment (app files + database) - - Stops bot service - - Updates application files - - Installs new Python dependencies if changed - - Preserves .env configuration - - Restarts bot service - - Validates deployment health - - Rollback support on failure - -.PARAMETER InstallPath - Target installation path (default: C:\inetpub\wwwroot\roa2web\telegram-bot) - -.PARAMETER SourcePath - Source path for deployment package (auto-detected if run from scripts/) - -.PARAMETER BackupEnabled - Create backup before deployment (default: true) - -.PARAMETER RestartService - Restart bot service after deployment (default: true) - -.PARAMETER RollbackOnFailure - Automatically rollback if deployment fails (default: true) - -.EXAMPLE - cd C:\Deploy\TelegramBot\scripts - .\Deploy-TelegramBot.ps1 - Deploy from current deployment package (auto-detected) - -.EXAMPLE - .\Deploy-TelegramBot.ps1 -SourcePath "C:\Deploy\new-version" - Deploy from specific source path - -.EXAMPLE - .\Deploy-TelegramBot.ps1 -BackupEnabled $false -RestartService $false - Update files without backup or restart (manual testing) - -.NOTES - Author: ROA2WEB Team - Requires: PowerShell 5.1+, Administrator privileges - Recommended to run from deployment package's scripts/ directory -#> - -[CmdletBinding()] -param( - [string]$InstallPath = "C:\inetpub\wwwroot\roa2web\telegram-bot", - [string]$SourcePath = "", - [bool]$BackupEnabled = $true, - [bool]$RestartService = $true, - [bool]$RollbackOnFailure = $true -) - -$ErrorActionPreference = "Stop" - -# ============================================================================= -# CONFIGURATION -# ============================================================================= - -# Auto-detect source path: if running from scripts/ subdirectory, use parent -$detectedSourcePath = if ($SourcePath) { - $SourcePath -} elseif ((Split-Path $PSScriptRoot -Leaf) -eq "scripts") { - Split-Path $PSScriptRoot -Parent -} else { - $PSScriptRoot -} - -$script:Config = @{ - AppName = "ROA2WEB-TelegramBot" - ServiceName = "ROA2WEB-TelegramBot" - InstallPath = $InstallPath - DataPath = Join-Path $InstallPath "data" - BackupPath = Join-Path $InstallPath "backups" - LogsPath = Join-Path $InstallPath "logs" - SourcePath = $detectedSourcePath -} - -$script:DeploymentState = @{ - BackupPath = $null - ServiceWasRunning = $false - DeploymentSuccess = $false -} - -# ============================================================================= -# 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 Test-Administrator { - $identity = [Security.Principal.WindowsIdentity]::GetCurrent() - $principal = [Security.Principal.WindowsPrincipal]$identity - return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) -} - -function New-BackupDirectory { - if (-not (Test-Path $Config.BackupPath)) { - New-Item -ItemType Directory -Path $Config.BackupPath -Force | Out-Null - } -} - -function Backup-CurrentDeployment { - if (-not $BackupEnabled) { - Write-Warning "Backup disabled, skipping..." - return $null - } - - Write-Step "Creating backup of current deployment..." - - New-BackupDirectory - - $timestamp = Get-Date -Format "yyyyMMdd-HHmmss" - $backupName = "backup-$timestamp" - $backupFullPath = Join-Path $Config.BackupPath $backupName - - try { - # Create backup directory - New-Item -ItemType Directory -Path $backupFullPath -Force | Out-Null - - # Backup app directory - if (Test-Path (Join-Path $Config.InstallPath "app")) { - $backupAppPath = Join-Path $backupFullPath "app" - Copy-Item -Path (Join-Path $Config.InstallPath "app") -Destination $backupAppPath -Recurse -Force - Write-Success "App files backed up" - } - - # Backup requirements.txt - $reqFile = Join-Path $Config.InstallPath "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 file - $envFile = Join-Path $Config.InstallPath ".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.DataPath "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" - } - - 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 - } -} - -function Stop-BotService { - Write-Step "Stopping Telegram bot service..." - - try { - $service = Get-Service -Name $Config.ServiceName -ErrorAction SilentlyContinue - - if (-not $service) { - Write-Warning "Service $($Config.ServiceName) not found" - $DeploymentState.ServiceWasRunning = $false - return - } - - if ($service.Status -eq "Running") { - $DeploymentState.ServiceWasRunning = $true - - Stop-Service -Name $Config.ServiceName -Force - Start-Sleep -Seconds 2 - - # Wait for service to stop - $timeout = 30 - $elapsed = 0 - while ($service.Status -ne "Stopped" -and $elapsed -lt $timeout) { - Start-Sleep -Seconds 1 - $service.Refresh() - $elapsed++ - } - - if ($service.Status -eq "Stopped") { - Write-Success "Service stopped successfully" - } else { - Write-Warning "Service did not stop within timeout" - } - } else { - Write-Success "Service already stopped" - $DeploymentState.ServiceWasRunning = $false - } - } catch { - Write-Error "Failed to stop service: $_" - throw - } -} - -function Update-ApplicationFiles { - Write-Step "Updating application 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.InstallPath "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 present - $sourceReq = Join-Path $Config.SourcePath "requirements.txt" - $destReq = Join-Path $Config.InstallPath "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.InstallPath "venv" - $pipPath = Join-Path $venvPath "Scripts\pip.exe" - - if (Test-Path $pipPath) { - try { - & $pipPath install -r $destReq --upgrade - Write-Success "Python dependencies updated" - } catch { - Write-Error "Failed to update Python dependencies: $_" - throw - } - } else { - Write-Warning "Virtual environment not found, skipping dependency update" - } - } else { - Write-Success "Python dependencies unchanged" - } - } - - # Preserve .env file (never overwrite) - $envFile = Join-Path $Config.InstallPath ".env" - if (-not (Test-Path $envFile)) { - $sourceEnv = Join-Path $Config.SourcePath ".env.example" - if (Test-Path $sourceEnv) { - Copy-Item -Path $sourceEnv -Destination $envFile -Force - Write-Warning "Created .env from .env.example - PLEASE CONFIGURE" - } - } else { - Write-Success ".env file preserved (not overwritten)" - } - - # Update management scripts - $sourceScripts = Join-Path $Config.SourcePath "scripts" - if (Test-Path $sourceScripts) { - $destScripts = Join-Path $Config.InstallPath "scripts" - if (-not (Test-Path $destScripts)) { - New-Item -ItemType Directory -Path $destScripts -Force | Out-Null - } - - # List of management scripts to update - $managementScripts = @( - "Start-TelegramBot.ps1", - "Stop-TelegramBot.ps1", - "Restart-TelegramBot.ps1", - "Backup-TelegramDB.ps1", - "Setup-DailyBackup.ps1", - "Setup-ClaudeAuth.ps1" - ) - - $updatedScriptsCount = 0 - foreach ($script in $managementScripts) { - $sourcePath = Join-Path $sourceScripts $script - if (Test-Path $sourcePath) { - $destPath = Join-Path $destScripts $script - Copy-Item -Path $sourcePath -Destination $destPath -Force - $updatedScriptsCount++ - } - } - - if ($updatedScriptsCount -gt 0) { - Write-Success "Updated $updatedScriptsCount management scripts" - } - } - - } catch { - Write-Error "Failed to update application files: $_" - throw - } -} - -function Start-BotService { - if (-not $RestartService) { - Write-Warning "Service restart disabled, skipping..." - return - } - - Write-Step "Starting Telegram bot service..." - - try { - $service = Get-Service -Name $Config.ServiceName -ErrorAction SilentlyContinue - - if (-not $service) { - Write-Warning "Service $($Config.ServiceName) not found" - return - } - - Start-Service -Name $Config.ServiceName - Start-Sleep -Seconds 3 - - # Wait for service to start - $timeout = 30 - $elapsed = 0 - while ($service.Status -ne "Running" -and $elapsed -lt $timeout) { - Start-Sleep -Seconds 1 - $service.Refresh() - $elapsed++ - } - - if ($service.Status -eq "Running") { - Write-Success "Service started successfully" - } else { - throw "Service failed to start (Status: $($service.Status))" - } - } catch { - Write-Error "Failed to start service: $_" - throw - } -} - -function Test-DeploymentHealth { - Write-Step "Testing deployment health..." - - Start-Sleep -Seconds 5 - - try { - # Get service port from .env or use default - $envFile = Join-Path $Config.InstallPath ".env" - $port = 8002 - - if (Test-Path $envFile) { - $envContent = Get-Content $envFile - $portLine = $envContent | Where-Object { $_ -match "^INTERNAL_API_PORT=(\d+)" } - if ($portLine -and $matches[1]) { - $port = [int]$matches[1] - } - } - - $healthUrl = "http://localhost:$port/internal/health" - $response = Invoke-WebRequest -Uri $healthUrl -UseBasicParsing -TimeoutSec 10 - - if ($response.StatusCode -eq 200) { - $content = $response.Content | ConvertFrom-Json - Write-Success "Health check passed: $($content.status)" - Write-Success "Database: $($content.database.status)" - return $true - } else { - throw "Health check returned status code: $($response.StatusCode)" - } - } catch { - Write-Error "Health check failed: $_" - return $false - } -} - -function Restore-FromBackup { - param([string]$BackupPath) - - if (-not $BackupPath -or -not (Test-Path $BackupPath)) { - Write-Error "Cannot rollback: backup path not found ($BackupPath)" - return $false - } - - Write-Step "Rolling back to backup: $BackupPath" - - try { - # Stop service - Stop-BotService - - # Restore app directory - $backupApp = Join-Path $BackupPath "app" - $destApp = Join-Path $Config.InstallPath "app" - - if (Test-Path $backupApp) { - if (Test-Path $destApp) { - Remove-Item -Path $destApp -Recurse -Force - } - Copy-Item -Path $backupApp -Destination $destApp -Recurse -Force - Write-Success "App files restored" - } - - # Restore requirements.txt - $backupReq = Join-Path $BackupPath "requirements.txt" - if (Test-Path $backupReq) { - Copy-Item -Path $backupReq -Destination (Join-Path $Config.InstallPath "requirements.txt") -Force - Write-Success "Requirements file restored" - } - - # Restore database - $backupDb = Join-Path $BackupPath "data\telegram_bot.db" - if (Test-Path $backupDb) { - Copy-Item -Path $backupDb -Destination (Join-Path $Config.DataPath "telegram_bot.db") -Force - Write-Success "Database restored" - } - - # Restart service - if ($DeploymentState.ServiceWasRunning) { - Start-BotService - } - - Write-Success "Rollback completed successfully" - return $true - } catch { - Write-Error "Rollback failed: $_" - return $false - } -} - -function Show-DeploymentSummary { - Write-Host "`n" + ("=" * 80) -ForegroundColor Cyan - - if ($DeploymentState.DeploymentSuccess) { - Write-Host " DEPLOYMENT COMPLETED SUCCESSFULLY" -ForegroundColor Green - } else { - Write-Host " DEPLOYMENT FAILED" -ForegroundColor Red - } - - Write-Host ("=" * 80) -ForegroundColor Cyan - - Write-Host "`nDeployment Details:" -ForegroundColor Yellow - Write-Host " Install Path: $($Config.InstallPath)" - Write-Host " Source Path: $($Config.SourcePath)" - Write-Host " Backup Created: $(if ($DeploymentState.BackupPath) { 'Yes' } else { 'No' })" - - if ($DeploymentState.BackupPath) { - Write-Host " Backup Location: $($DeploymentState.BackupPath)" - } - - $service = Get-Service -Name $Config.ServiceName -ErrorAction SilentlyContinue - if ($service) { - Write-Host " Service Status: $($service.Status)" -ForegroundColor $(if ($service.Status -eq "Running") { "Green" } else { "Red" }) - } - - if ($DeploymentState.DeploymentSuccess) { - Write-Host "`nNext Steps:" -ForegroundColor Yellow - Write-Host " - Monitor logs: Get-Content $($Config.LogsPath)\stdout.log -Tail 50 -Wait" - Write-Host " - Check health: Invoke-WebRequest http://localhost:8002/internal/health" - Write-Host " - Test bot on Telegram" - } else { - Write-Host "`nTroubleshooting:" -ForegroundColor Yellow - Write-Host " - Check logs: Get-Content $($Config.LogsPath)\stderr.log -Tail 100" - Write-Host " - Verify .env configuration" - if ($DeploymentState.BackupPath -and $RollbackOnFailure) { - Write-Host " - Rollback completed automatically to: $($DeploymentState.BackupPath)" - } elseif ($DeploymentState.BackupPath) { - Write-Host " - Manual rollback available at: $($DeploymentState.BackupPath)" - } - } - - Write-Host "`n" + ("=" * 80) -ForegroundColor Cyan -} - -# ============================================================================= -# MAIN DEPLOYMENT FLOW -# ============================================================================= - -function Main { - Write-Host @" - - ==================================================================== - ROA2WEB Telegram Bot - Deployment Script - Quick deployment and update automation - ==================================================================== - -"@ -ForegroundColor Cyan - - # Check prerequisites - Write-Step "Checking prerequisites..." - - if (-not (Test-Administrator)) { - Write-Error "This script must be run as Administrator" - Write-Host " Right-click PowerShell and select 'Run as Administrator'" -ForegroundColor Yellow - exit 1 - } - Write-Success "Running as Administrator" - - if (-not (Test-Path $Config.InstallPath)) { - Write-Error "Installation path not found: $($Config.InstallPath)" - Write-Host " Run Install-TelegramBot.ps1 first" -ForegroundColor Yellow - exit 1 - } - Write-Success "Installation path verified" - - if (-not (Test-Path $Config.SourcePath)) { - Write-Error "Source path not found: $($Config.SourcePath)" - exit 1 - } - Write-Success "Source path verified: $($Config.SourcePath)" - - try { - # Deployment steps - $DeploymentState.BackupPath = Backup-CurrentDeployment - Stop-BotService - Update-ApplicationFiles - Start-BotService - - $healthOk = Test-DeploymentHealth - - if ($healthOk) { - $DeploymentState.DeploymentSuccess = $true - Write-Host "`nDeployment completed successfully!" -ForegroundColor Green - } else { - throw "Health check failed after deployment" - } - - } catch { - Write-Host "`n[DEPLOYMENT FAILED] $_" -ForegroundColor Red - - if ($RollbackOnFailure -and $DeploymentState.BackupPath) { - Write-Host "`nAttempting automatic rollback..." -ForegroundColor Yellow - $rollbackOk = Restore-FromBackup -BackupPath $DeploymentState.BackupPath - - if ($rollbackOk) { - Write-Host "Rollback completed successfully" -ForegroundColor Yellow - } else { - Write-Host "Rollback failed - manual intervention required" -ForegroundColor Red - } - } else { - Write-Host "Automatic rollback disabled or no backup available" -ForegroundColor Yellow - } - - $DeploymentState.DeploymentSuccess = $false - } finally { - Show-DeploymentSummary - } - - if (-not $DeploymentState.DeploymentSuccess) { - exit 1 - } -} - -# Run main deployment -Main diff --git a/deployment/windows/scripts/Manage-ROA2WEB.ps1 b/deployment/windows/scripts/Manage-ROA2WEB.ps1 deleted file mode 100644 index 5b8f293..0000000 --- a/deployment/windows/scripts/Manage-ROA2WEB.ps1 +++ /dev/null @@ -1,498 +0,0 @@ -<# -.SYNOPSIS - Unified Service Management for ROA2WEB Application - -.DESCRIPTION - Manages Windows services for ROA2WEB Backend and Telegram Bot. - Provides unified interface for start, stop, restart, and status operations. - -.PARAMETER Action - Action to perform: Start, Stop, Restart, Status - -.PARAMETER Component - Component(s) to manage: - - All (default): Manage both Backend and TelegramBot - - Backend: Manage only Backend service - - TelegramBot: Manage only Telegram Bot service - -.PARAMETER Timeout - Timeout in seconds for service operations (default: 30) - -.EXAMPLE - .\Manage-ROA2WEB.ps1 -Action Start - Start all services (Backend + TelegramBot) - -.EXAMPLE - .\Manage-ROA2WEB.ps1 -Action Stop -Component Backend - Stop only Backend service - -.EXAMPLE - .\Manage-ROA2WEB.ps1 -Action Restart -Component TelegramBot - Restart only Telegram Bot service - -.EXAMPLE - .\Manage-ROA2WEB.ps1 -Action Status - Show status of all services - -.NOTES - Author: ROA2WEB Team - Version: 2.0 (Unified Management Script) - Requires: Administrator privileges -#> - -[CmdletBinding()] -param( - [Parameter(Mandatory=$true)] - [ValidateSet("Start", "Stop", "Restart", "Status")] - [string]$Action, - - [ValidateSet("All", "Backend", "TelegramBot")] - [string]$Component = "All", - - [int]$Timeout = 30 -) - -$ErrorActionPreference = "Stop" - -# ============================================================================= -# CONFIGURATION -# ============================================================================= - -$config = @{ - Backend = @{ - ServiceName = "ROA2WEB-Backend" - DisplayName = "ROA2WEB Backend" - HealthUrl = "http://localhost:8000/health" - HealthTimeout = 5 - } - TelegramBot = @{ - ServiceName = "ROA2WEB-TelegramBot" - DisplayName = "ROA2WEB Telegram Bot" - HealthUrl = "http://localhost:8002/internal/health" - HealthTimeout = 10 - } -} - -# ============================================================================= -# 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 to parse JSON if available - 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 Start-ServiceComponent { - param( - [string]$ComponentName, - [hashtable]$ComponentConfig - ) - - Write-Step "Starting $($ComponentConfig.DisplayName)..." - - $service = Get-ServiceSafe -ServiceName $ComponentConfig.ServiceName - - if (-not $service) { - Write-Error "Service '$($ComponentConfig.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 - Start-Service -Name $ComponentConfig.ServiceName - Write-Info "Service start command issued" - - # Wait for service to start - $elapsed = 0 - while ($service.Status -ne "Running" -and $elapsed -lt $Timeout) { - Start-Sleep -Seconds 1 - $service.Refresh() - $elapsed++ - if ($elapsed % 5 -eq 0) { - Write-Info "Waiting for service to start... ($elapsed/$Timeout)" - } - } - - if ($service.Status -eq "Running") { - Write-Success "Service started successfully" - - # Wait a bit for service to fully initialize - $waitTime = if ($ComponentName -eq "TelegramBot") { 5 } else { 3 } - Start-Sleep -Seconds $waitTime - - # Test health endpoint - $health = Test-HealthEndpoint -Url $ComponentConfig.HealthUrl -TimeoutSec $ComponentConfig.HealthTimeout - - if ($health.Success) { - Write-Success "Health check passed: $($health.Status)" - - # Show additional details for Telegram Bot - if ($ComponentName -eq "TelegramBot" -and $health.Details) { - if ($health.Details.database) { - Write-Success "Database: $($health.Details.database.status)" - } - } - } else { - Write-Warning "Health check failed: $($health.Status) (service may still be starting)" - if ($health.Error) { - Write-Warning "Error: $($health.Error)" - } - } - - 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, - [hashtable]$ComponentConfig - ) - - Write-Step "Stopping $($ComponentConfig.DisplayName)..." - - $service = Get-ServiceSafe -ServiceName $ComponentConfig.ServiceName - - if (-not $service) { - Write-Error "Service '$($ComponentConfig.ServiceName)' not found" - return $false - } - - if ($service.Status -eq "Stopped") { - Write-Success "Service is already stopped" - return $true - } - - try { - # Stop service - Stop-Service -Name $ComponentConfig.ServiceName -Force - Write-Info "Service stop command issued" - - # Wait for service to stop - $elapsed = 0 - while ($service.Status -ne "Stopped" -and $elapsed -lt $Timeout) { - Start-Sleep -Seconds 1 - $service.Refresh() - $elapsed++ - if ($elapsed % 5 -eq 0) { - Write-Info "Waiting for service to stop... ($elapsed/$Timeout)" - } - } - - if ($service.Status -eq "Stopped") { - Write-Success "Service stopped successfully" - return $true - } else { - Write-Error "Service did not stop within timeout (Status: $($service.Status))" - Write-Warning "You may need to force kill the process" - return $false - } - } catch { - Write-Error "Failed to stop service: $_" - return $false - } -} - -function Restart-ServiceComponent { - param( - [string]$ComponentName, - [hashtable]$ComponentConfig - ) - - Write-Step "Restarting $($ComponentConfig.DisplayName)..." - - # Stop service - $stopResult = Stop-ServiceComponent -ComponentName $ComponentName -ComponentConfig $ComponentConfig - - if (-not $stopResult) { - Write-Error "Failed to stop service, aborting restart" - return $false - } - - # Wait a moment - Start-Sleep -Seconds 2 - - # Start service - $startResult = Start-ServiceComponent -ComponentName $ComponentName -ComponentConfig $ComponentConfig - - if ($startResult) { - Write-Success "Service restarted successfully" - return $true - } else { - Write-Error "Failed to start service after stop" - return $false - } -} - -function Get-ServiceStatus { - param( - [string]$ComponentName, - [hashtable]$ComponentConfig - ) - - $service = Get-ServiceSafe -ServiceName $ComponentConfig.ServiceName - - $statusInfo = @{ - Component = $ComponentConfig.DisplayName - ServiceName = $ComponentConfig.ServiceName - Status = "Not Installed" - StartType = "N/A" - Health = "Unknown" - HealthDetails = $null - } - - if ($service) { - $statusInfo.Status = $service.Status - $statusInfo.StartType = $service.StartType - - if ($service.Status -eq "Running") { - # Test health endpoint - $health = Test-HealthEndpoint -Url $ComponentConfig.HealthUrl -TimeoutSec $ComponentConfig.HealthTimeout - $statusInfo.Health = $health.Status - $statusInfo.HealthDetails = $health.Details - } - } - - return $statusInfo -} - -function Show-ServiceStatus { - param( - [string]$ComponentName, - [hashtable]$ComponentConfig - ) - - $status = Get-ServiceStatus -ComponentName $ComponentName -ComponentConfig $ComponentConfig - - Write-Host "`n$($status.Component):" -ForegroundColor Cyan - Write-Host " Service Name: $($status.ServiceName)" -ForegroundColor Gray - - # Color-code status - $statusColor = switch ($status.Status) { - "Running" { "Green" } - "Stopped" { "Yellow" } - "Not Installed" { "Red" } - default { "Gray" } - } - Write-Host " Status: $($status.Status)" -ForegroundColor $statusColor - Write-Host " Start Type: $($status.StartType)" -ForegroundColor Gray - - if ($status.Status -eq "Running") { - $healthColor = switch ($status.Health) { - "healthy" { "Green" } - "ok" { "Green" } - "unhealthy" { "Red" } - "unreachable" { "Yellow" } - default { "Gray" } - } - Write-Host " Health: $($status.Health)" -ForegroundColor $healthColor - - # Show additional details for Telegram Bot - if ($ComponentName -eq "TelegramBot" -and $status.HealthDetails) { - if ($status.HealthDetails.database) { - Write-Host " Database: $($status.HealthDetails.database.status)" -ForegroundColor Gray - } - if ($status.HealthDetails.telegram) { - Write-Host " Telegram: $($status.HealthDetails.telegram.status)" -ForegroundColor Gray - } - } - } -} - -# ============================================================================= -# MAIN EXECUTION -# ============================================================================= - -function Main { - $banner = @" - - ==================================================================== - ROA2WEB - Service Management (v2.0) - Action: $Action | Component: $Component - ==================================================================== - -"@ - Write-Host $banner -ForegroundColor Cyan - - # Check administrator privileges - if (-not (Test-Administrator)) { - Write-Error "This script requires Administrator privileges" - Write-Host "`nPlease run PowerShell as Administrator and try again." -ForegroundColor Yellow - exit 1 - } - - # Determine which components to manage - $components = @() - switch ($Component) { - "All" { - $components = @( - @{ Name = "Backend"; Config = $config.Backend }, - @{ Name = "TelegramBot"; Config = $config.TelegramBot } - ) - } - "Backend" { - $components = @( - @{ Name = "Backend"; Config = $config.Backend } - ) - } - "TelegramBot" { - $components = @( - @{ Name = "TelegramBot"; Config = $config.TelegramBot } - ) - } - } - - # Execute action - $allSuccess = $true - - switch ($Action) { - "Start" { - foreach ($comp in $components) { - $result = Start-ServiceComponent -ComponentName $comp.Name -ComponentConfig $comp.Config - if (-not $result) { - $allSuccess = $false - } - } - } - - "Stop" { - foreach ($comp in $components) { - $result = Stop-ServiceComponent -ComponentName $comp.Name -ComponentConfig $comp.Config - if (-not $result) { - $allSuccess = $false - } - } - } - - "Restart" { - foreach ($comp in $components) { - $result = Restart-ServiceComponent -ComponentName $comp.Name -ComponentConfig $comp.Config - if (-not $result) { - $allSuccess = $false - } - } - } - - "Status" { - foreach ($comp in $components) { - Show-ServiceStatus -ComponentName $comp.Name -ComponentConfig $comp.Config - } - - Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan - exit 0 - } - } - - # Show final status - Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan - - if ($allSuccess) { - Write-Host " OPERATION COMPLETED SUCCESSFULLY" -ForegroundColor Green - Write-Host ("=" * 70) -ForegroundColor Cyan - exit 0 - } else { - Write-Host " OPERATION COMPLETED WITH ERRORS" -ForegroundColor Red - Write-Host ("=" * 70) -ForegroundColor Cyan - Write-Host "`nSome services may require manual intervention." -ForegroundColor Yellow - Write-Host "Check service logs for details:" -ForegroundColor Yellow - Write-Host " Backend: C:\inetpub\wwwroot\roa2web\logs\" -ForegroundColor Gray - Write-Host " Telegram Bot: C:\inetpub\wwwroot\roa2web\telegram-bot\logs\" -ForegroundColor Gray - exit 1 - } -} - -# Run main -Main diff --git a/deployment/windows/scripts/README.md b/deployment/windows/scripts/README.md index ad31399..6016f82 100644 --- a/deployment/windows/scripts/README.md +++ b/deployment/windows/scripts/README.md @@ -4,49 +4,110 @@ Complete reference for ROA2WEB Windows deployment PowerShell scripts (v2.0 - Uni ## 📋 Script Overview -### 🔨 Build Scripts +### ⭐ Unified Scripts (Recommended) -#### **Build-ROA2WEB.ps1** ⭐ (UNIFIED - Recommended) -**Purpose**: Unified build script for all components +#### **Build-ROA2WEB.ps1** - Unified Build System +**Purpose**: Build deployment packages for all components with interactive menu **Usage**: ```powershell -# Build complete package (default) +# Interactive mode - shows menu .\Build-ROA2WEB.ps1 -# Build specific components +# Non-interactive mode - specific component +.\Build-ROA2WEB.ps1 -Component All # Complete package .\Build-ROA2WEB.ps1 -Component Frontend # Frontend + Backend .\Build-ROA2WEB.ps1 -Component Backend # Backend only .\Build-ROA2WEB.ps1 -Component TelegramBot # Telegram Bot only # Custom output path -.\Build-ROA2WEB.ps1 -OutputPath "D:\builds\roa2web-$(Get-Date -Format 'yyyyMMdd')" +.\Build-ROA2WEB.ps1 -Component All -OutputPath "D:\builds\roa2web-$(Get-Date -Format 'yyyyMMdd')" +``` + +**Interactive Menu**: +``` +[1] All Components (Frontend + Backend + Telegram Bot) +[2] Frontend + Backend (Vue.js build + FastAPI backend) +[3] Backend Only (FastAPI backend + shared modules) +[4] Telegram Bot Only (Standalone package) +[Q] Quit ``` **Parameters**: -- `-Component` All|Frontend|Backend|TelegramBot (default: All) +- `-Component` All|Frontend|Backend|TelegramBot (optional - shows menu if not specified) - `-OutputPath` (default: `./deploy-package`) - `-Clean` $true|$false (default: $true) +**Features**: +- ✅ Interactive menu for component selection +- ✅ Isolated temp directory for frontend builds (doesn't corrupt WSL node_modules) +- ✅ Automatic dependency installation (including devDependencies) +- ✅ Auto-cleanup after build +- ✅ Generates deployment README + **Output**: Creates `deploy-package/` with complete deployment files --- -#### Build-Frontend.ps1 ⚠️ DEPRECATED -**Status**: Deprecated in favor of `Build-ROA2WEB.ps1 -Component Frontend` +#### **ROA2WEB-Console.ps1** - Unified Deployment & Management Console +**Purpose**: All-in-one console for deploying and managing ROA2WEB services -Use: `.\Build-ROA2WEB.ps1 -Component Frontend` instead +**Usage**: +```powershell +# Interactive mode - shows main menu +.\ROA2WEB-Console.ps1 + +# Non-interactive mode - specific actions +.\ROA2WEB-Console.ps1 -NonInteractive -Action DeployAll +.\ROA2WEB-Console.ps1 -NonInteractive -Action DeployBackend +.\ROA2WEB-Console.ps1 -NonInteractive -Action DeployTelegramBot +.\ROA2WEB-Console.ps1 -NonInteractive -Action StartAll +.\ROA2WEB-Console.ps1 -NonInteractive -Action StopAll +.\ROA2WEB-Console.ps1 -NonInteractive -Action RestartAll +.\ROA2WEB-Console.ps1 -NonInteractive -Action Status +``` + +**Interactive Main Menu**: +``` +[1] Deploy Components + - Deploy Backend + Frontend + - Deploy Telegram Bot + - Deploy All Components + +[2] Manage Services + - Start/Stop/Restart All + - Start/Stop/Restart Backend + - Start/Stop/Restart Telegram Bot + +[3] Check Status + - Service status + - Health checks + +[Q] Quit +``` + +**Parameters**: +- `-NonInteractive` Switch to enable non-interactive mode +- `-Action` DeployBackend|DeployTelegramBot|DeployAll|StartAll|StopAll|RestartAll|Status + +**Features**: +- ✅ Unified interface for deploy + management +- ✅ Interactive menus with easy navigation +- ✅ Automatic backups before deployment +- ✅ Smart dependency updates (only if requirements.txt changed) +- ✅ Health checks after operations +- ✅ Color-coded status output +- ✅ Both interactive and non-interactive modes +- ✅ Preserves .env configuration files + +**Replaces**: +- ❌ `Deploy-ROA2WEB.ps1` (removed) +- ❌ `Deploy-TelegramBot.ps1` (removed) +- ❌ `Manage-ROA2WEB.ps1` (removed) --- -#### Build-TelegramBot.ps1 ⚠️ DEPRECATED -**Status**: Deprecated in favor of `Build-ROA2WEB.ps1 -Component TelegramBot` - -Use: `.\Build-ROA2WEB.ps1 -Component TelegramBot` instead - ---- - -### 🚀 Installation Scripts +### 🚀 Installation Scripts (First-Time Setup) #### **Install-ROA2WEB.ps1** **Purpose**: First-time installation of Backend + Frontend @@ -103,140 +164,6 @@ Use: `.\Build-ROA2WEB.ps1 -Component TelegramBot` instead --- -### 📦 Deployment Scripts - -#### **Deploy-ROA2WEB.ps1** -**Purpose**: Deploy updates to existing Backend + Frontend - -**Usage**: -```powershell -# Auto-detects source path (if run from deploy-package/scripts/) -.\Deploy-ROA2WEB.ps1 - -# Explicit source path -.\Deploy-ROA2WEB.ps1 -SourcePath "C:\Deploy\roa2web-v2" - -# Disable automatic restart -.\Deploy-ROA2WEB.ps1 -RestartService $false -``` - -**What it does**: -1. Creates backup of current deployment -2. Stops backend service -3. Updates backend & frontend files (preserves .env) -4. Checks requirements.txt - updates dependencies if changed -5. Restarts backend service -6. Tests health endpoint -7. Cleans old backups (keeps last 10) - -**Parameters**: -- `-InstallPath` (default: `C:\inetpub\wwwroot\roa2web`) -- `-SourcePath` (auto-detected from script location) -- `-BackupEnabled`, `-RestartService`, `-UpdateBackend`, `-UpdateFrontend` -- `-ForceInstallDependencies` - ---- - -#### **Deploy-TelegramBot.ps1** -**Purpose**: Deploy updates to existing Telegram Bot - -**Usage**: -```powershell -# Auto-detects source path -.\Deploy-TelegramBot.ps1 - -# With automatic rollback on failure -.\Deploy-TelegramBot.ps1 -RollbackOnFailure $true -``` - -**What it does**: -1. Creates backup (app/, requirements.txt, .env, database) -2. Stops bot service -3. Removes old app/ directory -4. Copies new app/ files -5. Checks requirements.txt - updates dependencies if changed -6. Preserves .env (never overwrites) -7. Updates management scripts -8. Restarts service -9. Tests health endpoint -10. **Automatic rollback** on failure (if enabled) -11. Cleans old backups (keeps last 10) - -**Parameters**: -- `-InstallPath` (default: `C:\inetpub\wwwroot\roa2web\telegram-bot`) -- `-SourcePath` (auto-detected) -- `-BackupEnabled`, `-RestartService`, `-RollbackOnFailure` - ---- - -### ⚙️ Service Management - -#### **Manage-ROA2WEB.ps1** ⭐ (UNIFIED - Recommended) -**Purpose**: Unified service management for all components - -**Usage**: -```powershell -# Start all services -.\Manage-ROA2WEB.ps1 -Action Start - -# Stop all services -.\Manage-ROA2WEB.ps1 -Action Stop - -# Restart all services -.\Manage-ROA2WEB.ps1 -Action Restart - -# Check status of all services -.\Manage-ROA2WEB.ps1 -Action Status - -# Manage specific component -.\Manage-ROA2WEB.ps1 -Action Start -Component Backend -.\Manage-ROA2WEB.ps1 -Action Restart -Component TelegramBot -.\Manage-ROA2WEB.ps1 -Action Status -Component Backend -``` - -**Parameters**: -- `-Action` Start|Stop|Restart|Status (required) -- `-Component` All|Backend|TelegramBot (default: All) -- `-Timeout` (default: 30 seconds) - -**Features**: -- ✅ Health checks after start operations -- ✅ Colored status output -- ✅ Detailed error messages -- ✅ Administrator privilege verification - ---- - -#### Start-ROA2WEB.ps1 ⚠️ REMOVED -**Status**: Removed - Use `Manage-ROA2WEB.ps1 -Action Start -Component Backend` - ---- - -#### Stop-ROA2WEB.ps1 ⚠️ REMOVED -**Status**: Removed - Use `Manage-ROA2WEB.ps1 -Action Stop -Component Backend` - ---- - -#### Restart-ROA2WEB.ps1 ⚠️ REMOVED -**Status**: Removed - Use `Manage-ROA2WEB.ps1 -Action Restart -Component Backend` - ---- - -#### Start-TelegramBot.ps1 ⚠️ REMOVED -**Status**: Removed - Use `Manage-ROA2WEB.ps1 -Action Start -Component TelegramBot` - ---- - -#### Stop-TelegramBot.ps1 ⚠️ REMOVED -**Status**: Removed - Use `Manage-ROA2WEB.ps1 -Action Stop -Component TelegramBot` - ---- - -#### Restart-TelegramBot.ps1 ⚠️ REMOVED -**Status**: Removed - Use `Manage-ROA2WEB.ps1 -Action Restart -Component TelegramBot` - ---- - ### 🛠️ Utility Scripts #### **Backup-TelegramDB.ps1** @@ -326,124 +253,239 @@ Use: `.\Build-ROA2WEB.ps1 -Component TelegramBot` instead --- -## 📊 Script Comparison Matrix +## 🚀 Quick Reference Guide -| Feature | Build-ROA2WEB | Build-Frontend | Build-TelegramBot | Manage-ROA2WEB | Old Start/Stop | -|---------|---------------|----------------|-------------------|----------------|----------------| -| **Status** | ✅ Active | ⚠️ Deprecated | ⚠️ Deprecated | ✅ Active | 🗑️ Removed | -| **Components** | All | Frontend+Backend | TelegramBot | All | Single | -| **Unified** | ✅ Yes | ❌ No | ❌ No | ✅ Yes | ❌ No | -| **Flexible** | ✅ High | ⚠️ Medium | ⚠️ Medium | ✅ High | ❌ Low | -| **Health Checks** | ✅ Yes | ❌ No | ❌ No | ✅ Yes | ⚠️ Basic | - ---- - -## 🚀 Quick Reference - -### First-Time Deployment +### 📦 First-Time Deployment Workflow ```powershell -# 1. Build on development machine +# ======================================== +# STEP 1: Build on Development Machine (WSL/Linux) +# ======================================== cd deployment/windows/scripts .\Build-ROA2WEB.ps1 +# Select [1] All Components from menu -# 2. Transfer deploy-package/ to server +# ======================================== +# STEP 2: Transfer Package to Windows Server +# ======================================== +# Transfer deploy-package/ to server (e.g., C:\Deploy\roa2web-package) -# 3. Install on server (as Administrator) -cd C:\Deploy\deploy-package\scripts +# ======================================== +# STEP 3: Install on Windows Server (as Administrator) +# ======================================== +cd C:\Deploy\roa2web-package\scripts + +# Install Backend + Frontend .\Install-ROA2WEB.ps1 + +# Install Telegram Bot .\Install-TelegramBot.ps1 -# 4. Configure +# ======================================== +# STEP 4: Configure +# ======================================== notepad C:\inetpub\wwwroot\roa2web\backend\.env notepad C:\inetpub\wwwroot\roa2web\telegram-bot\.env -# 5. Start services -.\Manage-ROA2WEB.ps1 -Action Start -``` - -### Deploy Updates - -```powershell -# 1. Build new package on development machine -.\Build-ROA2WEB.ps1 - -# 2. Transfer to server - -# 3. Deploy (as Administrator) -cd C:\Deploy\new-package\scripts -.\Manage-ROA2WEB.ps1 -Action Stop -.\Deploy-ROA2WEB.ps1 -.\Deploy-TelegramBot.ps1 -.\Manage-ROA2WEB.ps1 -Action Start -``` - -### Daily Operations - -```powershell -# Check status -.\Manage-ROA2WEB.ps1 -Action Status - -# Restart services -.\Manage-ROA2WEB.ps1 -Action Restart - -# Restart specific component -.\Manage-ROA2WEB.ps1 -Action Restart -Component Backend - -# Backup database -.\Backup-TelegramDB.ps1 +# ======================================== +# STEP 5: Start Services via Console +# ======================================== +.\ROA2WEB-Console.ps1 +# Select [2] Manage Services -> [1] Start All Services ``` --- -## 📝 Notes +### 🔄 Deploy Updates Workflow -- **Administrator Privileges**: All scripts require PowerShell as Administrator -- **Automatic Backups**: Deploy scripts create backups before updating -- **Health Checks**: Manage-ROA2WEB.ps1 validates services after starting -- **Rollback Support**: Deploy-TelegramBot.ps1 supports automatic rollback on failure -- **Configuration Preservation**: .env files are never overwritten during updates -- **Dependency Management**: Scripts only reinstall Python packages if requirements.txt changes +```powershell +# ======================================== +# STEP 1: Build New Package on Development Machine +# ======================================== +cd deployment/windows/scripts +.\Build-ROA2WEB.ps1 +# Select component to update from menu + +# ======================================== +# STEP 2: Transfer to Windows Server +# ======================================== +# Transfer new deploy-package/ to server + +# ======================================== +# STEP 3: Deploy via Console (as Administrator) +# ======================================== +cd C:\Deploy\new-package\scripts +.\ROA2WEB-Console.ps1 + +# Option A: Interactive Menu +# [1] Deploy Components -> Select what to deploy + +# Option B: Non-Interactive +.\ROA2WEB-Console.ps1 -NonInteractive -Action DeployAll +``` + +--- + +### 🎛️ Daily Operations via Console + +```powershell +# Launch console +.\ROA2WEB-Console.ps1 + +# Main Menu Navigation: +# [1] Deploy Components -> Deploy/update application files +# [2] Manage Services -> Start/Stop/Restart services +# [3] Check Status -> View service status and health +# [Q] Quit + +# Non-Interactive Quick Commands: +.\ROA2WEB-Console.ps1 -NonInteractive -Action Status +.\ROA2WEB-Console.ps1 -NonInteractive -Action RestartAll +``` + +--- + +### 📊 Utility Operations + +```powershell +# Backup database +.\Backup-TelegramDB.ps1 + +# Setup automated daily backups +.\Setup-DailyBackup.ps1 -Time "02:00" -RetentionDays 14 + +# Configure Claude API +.\Setup-ClaudeAuth.ps1 + +# Enable HTTPS +.\Enable-HTTPS.ps1 +``` + +--- + +## 📊 Script Comparison: v1.0 vs v2.0 + +| Capability | v1.0 (Old) | v2.0 (Unified) | +|-----------|-----------|----------------| +| **Build Scripts** | 3 separate | 1 unified (`Build-ROA2WEB.ps1`) | +| **Deploy Scripts** | 2 separate | 1 unified console (`ROA2WEB-Console.ps1`) | +| **Manage Scripts** | 1 basic | Integrated in console | +| **Total Scripts** | 13 scripts | 8 scripts | +| **Interactive Menus** | ❌ No | ✅ Yes | +| **Non-Interactive Mode** | ⚠️ Partial | ✅ Full support | +| **Health Checks** | ⚠️ Basic | ✅ Comprehensive | +| **Backup Management** | ⚠️ Manual | ✅ Automatic | +| **Dependency Updates** | ⚠️ Always reinstall | ✅ Smart (only if changed) | +| **WSL Compatibility** | ❌ Corrupts node_modules | ✅ Isolated builds | + +--- + +## 🗂️ Complete Script List (v2.0) + +### Core Scripts (2) +1. ⭐ **Build-ROA2WEB.ps1** - Unified build system with menu +2. ⭐ **ROA2WEB-Console.ps1** - Unified deployment & management console + +### Installation Scripts (2) +3. **Install-ROA2WEB.ps1** - First-time Backend + Frontend setup +4. **Install-TelegramBot.ps1** - First-time Telegram Bot setup + +### Utility Scripts (4) +5. **Backup-TelegramDB.ps1** - Database backup +6. **Setup-DailyBackup.ps1** - Automated backup scheduling +7. **Setup-ClaudeAuth.ps1** - Claude API configuration +8. **Enable-HTTPS.ps1** - HTTPS setup for IIS + +### Removed Scripts (5) +- ❌ `Build-Frontend.ps1` → Use `Build-ROA2WEB.ps1 -Component Frontend` +- ❌ `Build-TelegramBot.ps1` → Use `Build-ROA2WEB.ps1 -Component TelegramBot` +- ❌ `Deploy-ROA2WEB.ps1` → Use `ROA2WEB-Console.ps1` menu option [1] Deploy +- ❌ `Deploy-TelegramBot.ps1` → Use `ROA2WEB-Console.ps1` menu option [1] Deploy +- ❌ `Manage-ROA2WEB.ps1` → Use `ROA2WEB-Console.ps1` menu option [2] Manage + +--- + +## 💡 Best Practices + +### Development Machine (WSL/Linux) +```powershell +# Always use Build-ROA2WEB.ps1 for building +# It isolates npm builds to prevent WSL corruption +.\Build-ROA2WEB.ps1 +``` + +### Windows Server +```powershell +# Use ROA2WEB-Console.ps1 for all operations +# Interactive mode for manual operations +.\ROA2WEB-Console.ps1 + +# Non-interactive mode for automation/scripts +.\ROA2WEB-Console.ps1 -NonInteractive -Action DeployAll +``` + +### Configuration Management +- ✅ `.env` files are NEVER overwritten during deployment +- ✅ Backups are created automatically before any deployment +- ✅ Old backups are auto-cleaned (keeps last 10) +- ✅ Dependencies only reinstalled if `requirements.txt` changes + +### Service Management +- ✅ Always use ROA2WEB-Console for service operations +- ✅ Health checks are automatic after service starts +- ✅ Color-coded output makes status clear +- ✅ Services can be managed individually or together --- ## 🐛 Troubleshooting ### Script Execution Policy Error - ```powershell Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser ``` +### Build Fails on WSL +- Ensure Node.js 16+ is installed: `node -v` +- Close all IDEs (file locks prevent builds) +- Run as Administrator in PowerShell + ### Service Won't Start - ```powershell -# Check service status -.\Manage-ROA2WEB.ps1 -Action Status +# Check status via console +.\ROA2WEB-Console.ps1 -NonInteractive -Action Status -# View logs +# View backend logs Get-Content C:\inetpub\wwwroot\roa2web\logs\backend-stderr.log -Tail 50 + +# View Telegram bot logs Get-Content C:\inetpub\wwwroot\roa2web\telegram-bot\logs\stderr.log -Tail 50 ``` -### Build Fails +### Deployment Fails +```powershell +# ROA2WEB-Console.ps1 creates automatic backups +# Check backup location if rollback needed +Get-ChildItem C:\inetpub\wwwroot\roa2web\backups\ | Sort-Object Name -Descending +``` -- Ensure Node.js 16+ is installed (for Frontend builds) -- Close VS Code and IDEs (file locks) -- Run as Administrator +### Frontend Build Corrupts WSL node_modules +- ✅ **Fixed in v2.0!** Build-ROA2WEB.ps1 uses isolated temp directory +- The temp directory is automatically cleaned after build --- ## 📚 Documentation -- **Deployment Package Guide**: [`../DEPLOY_PACKAGE.md`](../DEPLOY_PACKAGE.md) -- **Complete Deployment Guide**: [`../docs/WINDOWS_DEPLOYMENT.md`](../docs/WINDOWS_DEPLOYMENT.md) -- **Troubleshooting**: [`../docs/TELEGRAM_BOT_TROUBLESHOOTING.md`](../docs/TELEGRAM_BOT_TROUBLESHOOTING.md) - **Quick Start**: [`../README.md`](../README.md) +- **Complete Deployment Guide**: [`../docs/WINDOWS_DEPLOYMENT.md`](../docs/WINDOWS_DEPLOYMENT.md) +- **Deployment Package Guide**: [`../DEPLOY_PACKAGE.md`](../DEPLOY_PACKAGE.md) +- **Telegram Bot Troubleshooting**: [`../docs/TELEGRAM_BOT_TROUBLESHOOTING.md`](../docs/TELEGRAM_BOT_TROUBLESHOOTING.md) +- **HTTPS Setup**: [`../docs/HTTPS_SETUP.md`](../docs/HTTPS_SETUP.md) - **Project Documentation**: [`../../../CLAUDE.md`](../../../CLAUDE.md) --- **Version**: 2.0 (Unified System) -**Last Updated**: 2025-11-11 +**Last Updated**: 2025-11-12 **Author**: ROA2WEB Team diff --git a/deployment/windows/scripts/ROA2WEB-Console.ps1 b/deployment/windows/scripts/ROA2WEB-Console.ps1 new file mode 100644 index 0000000..0e2c772 --- /dev/null +++ b/deployment/windows/scripts/ROA2WEB-Console.ps1 @@ -0,0 +1,1024 @@ +<# +.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" + + # 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" + } + } + + # Preserve .env file + $envFile = Join-Path $Config.TelegramBotPath ".env" + if (-not (Test-Path $envFile)) { + $sourceEnv = Join-Path $Config.SourcePath ".env.example" + if (Test-Path $sourceEnv) { + Copy-Item -Path $sourceEnv -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