Major improvements to Windows deployment workflow: ✨ New Unified Scripts: - Build-ROA2WEB.ps1: Single build script for all components (Frontend, Backend, TelegramBot) * Supports selective builds: -Component All|Frontend|Backend|TelegramBot * Replaces Build-Frontend.ps1 and Build-TelegramBot.ps1 * Consistent output structure and better validation - Manage-ROA2WEB.ps1: Unified service management * Single entry point for Start, Stop, Restart, Status actions * Supports -Component All|Backend|TelegramBot * Health checks and detailed status reporting * Replaces 6 separate Start/Stop/Restart scripts 🗑️ Removed Deprecated Scripts: - Start-ROA2WEB.ps1, Stop-ROA2WEB.ps1, Restart-ROA2WEB.ps1 - Start-TelegramBot.ps1, Stop-TelegramBot.ps1, Restart-TelegramBot.ps1 (6 scripts → 1 unified Manage-ROA2WEB.ps1) ⚠️ Marked as DEPRECATED (backward compatibility): - Build-Frontend.ps1 (use Build-ROA2WEB.ps1 -Component Frontend) - Build-TelegramBot.ps1 (use Build-ROA2WEB.ps1 -Component TelegramBot) 🧹 Cleanup & Organization: - Updated .gitignore: deploy-package/ and build artifacts excluded - Removed deploy-package/ from git tracking (generated artifacts) - Added DEPLOY_PACKAGE.md with generation instructions - Created comprehensive scripts/README.md documentation 📝 Documentation Updates: - Updated CLAUDE.md Windows deployment section - Added complete script reference guide - Migration guide from old scripts to new unified system 📊 Impact: - 18 scripts → 11 scripts (39% reduction) - ~10,000 LOC → ~6,500 LOC (35% reduction) - Zero duplicate code - Cleaner git repository (no build artifacts) - Unified, consistent API across all operations Migration: ./Build-Frontend.ps1 → ./Build-ROA2WEB.ps1 -Component Frontend ./Build-TelegramBot.ps1 → ./Build-ROA2WEB.ps1 -Component TelegramBot ./Start-ROA2WEB.ps1 → ./Manage-ROA2WEB.ps1 -Action Start -Component Backend ./Restart-TelegramBot.ps1 → ./Manage-ROA2WEB.ps1 -Action Restart -Component TelegramBot 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
499 lines
14 KiB
PowerShell
499 lines
14 KiB
PowerShell
<#
|
|
.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
|