Refactor Windows deployment scripts: unify build and management tools

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 <noreply@anthropic.com>
This commit is contained in:
2025-11-12 01:35:14 +02:00
parent a227a10ca8
commit 1832684aca
8 changed files with 1447 additions and 3305 deletions

View File

@@ -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 '<transferred-path>'"
}
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

View File

@@ -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 = @"
====================================================================

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff