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:
@@ -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
|
||||
@@ -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 = @"
|
||||
|
||||
====================================================================
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
1024
deployment/windows/scripts/ROA2WEB-Console.ps1
Normal file
1024
deployment/windows/scripts/ROA2WEB-Console.ps1
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user