Add deployment automation system with build/publish workflow and fix path resolution
This commit introduces a complete deployment automation system for Windows Server deployment: New Features: - Publish-And-Deploy.ps1: Interactive console for building locally and transferring to server * Supports auto-detection of transfer method (Windows Share or SSH) * Configurable via deploy-config.json * Integrated with Build-ROA2WEB.ps1 for consistent builds - Check-And-Deploy.ps1: Server-side auto-deployment script * Monitors transfer directory for new packages * Automatically deploys when new version detected * Integrates with ROA2WEB-Console.ps1 for deployment - Setup-AutoDeploy.ps1: Configures scheduled task for auto-deployment - DEPLOYMENT_AUTOMATION.md: Complete documentation for automation workflow Bug Fixes: - Fix path resolution in Publish-And-Deploy.ps1: Added Resolve-FullPath function to correctly resolve ../deploy-package path - Fix ROA2WEB-Console.ps1 exit codes: Properly return boolean values and exit codes in non-interactive mode - Fix Build-ROA2WEB.ps1 script copying: Added debug output and force deletion to avoid caching issues Enhancements: - ROA2WEB-Console.ps1: Support for ROA2WEB_SOURCE environment variable for flexible source paths - Build-ROA2WEB.ps1: Enhanced debug output for troubleshooting script copying issues 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
542
deployment/windows/scripts/Check-And-Deploy.ps1
Normal file
542
deployment/windows/scripts/Check-And-Deploy.ps1
Normal file
@@ -0,0 +1,542 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
ROA2WEB - Auto-Deploy Monitor (Server-Side)
|
||||
|
||||
.DESCRIPTION
|
||||
Server-side script that monitors C:\Temp\ for new deployment packages
|
||||
and automatically deploys them using ROA2WEB-Console.ps1.
|
||||
|
||||
Can run:
|
||||
- Via Scheduled Task (automated, silent)
|
||||
- Interactive mode (manual execution with menu)
|
||||
- Non-interactive mode (command-line automation)
|
||||
|
||||
.PARAMETER Interactive
|
||||
Run in interactive mode with menu
|
||||
|
||||
.PARAMETER WatchPath
|
||||
Path to monitor for deployment packages (default: C:\Temp)
|
||||
|
||||
.PARAMETER StateFile
|
||||
Path to state file tracking last deployment (default: C:\Temp\ROA2WEB-Scripts\last-deploy.json)
|
||||
|
||||
.PARAMETER ConsoleScriptPath
|
||||
Path to ROA2WEB-Console.ps1 script
|
||||
|
||||
.PARAMETER LogPath
|
||||
Path to log file directory (default: C:\Temp\ROA2WEB-Scripts\Logs)
|
||||
|
||||
.PARAMETER CheckOnly
|
||||
Check for updates without deploying
|
||||
|
||||
.EXAMPLE
|
||||
.\Check-And-Deploy.ps1
|
||||
Check for new packages and deploy automatically (silent)
|
||||
|
||||
.EXAMPLE
|
||||
.\Check-And-Deploy.ps1 -Interactive
|
||||
Show interactive menu with deployment options
|
||||
|
||||
.EXAMPLE
|
||||
.\Check-And-Deploy.ps1 -CheckOnly
|
||||
Check for new packages without deploying
|
||||
|
||||
.NOTES
|
||||
Author: ROA2WEB Team
|
||||
Version: 1.0 (Auto-Deploy Monitor)
|
||||
Requires: Administrator privileges, PowerShell 5.1+
|
||||
|
||||
Designed to run via Scheduled Task for automated deployments.
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$Interactive,
|
||||
[string]$WatchPath = "C:\Temp",
|
||||
[string]$StateFile = "C:\Temp\ROA2WEB-Scripts\last-deploy.json",
|
||||
[string]$ConsoleScriptPath = "",
|
||||
[string]$LogPath = "C:\Temp\ROA2WEB-Scripts\Logs",
|
||||
[switch]$CheckOnly
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# =============================================================================
|
||||
# CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
$script:Config = @{
|
||||
WatchPath = $WatchPath
|
||||
StateFile = $StateFile
|
||||
LogPath = $LogPath
|
||||
ConsoleScriptPath = if ($ConsoleScriptPath) {
|
||||
$ConsoleScriptPath
|
||||
} else {
|
||||
# Auto-detect ROA2WEB-Console.ps1
|
||||
$possiblePaths = @(
|
||||
"C:\inetpub\wwwroot\roa2web\scripts\ROA2WEB-Console.ps1",
|
||||
(Join-Path $PSScriptRoot "ROA2WEB-Console.ps1")
|
||||
)
|
||||
$found = $possiblePaths | Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||
if ($found) { $found } else { "" }
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
function Write-Log {
|
||||
param(
|
||||
[string]$Message,
|
||||
[ValidateSet("INFO", "SUCCESS", "WARNING", "ERROR")]
|
||||
[string]$Level = "INFO"
|
||||
)
|
||||
|
||||
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
$logMessage = "[$timestamp] [$Level] $Message"
|
||||
|
||||
# Ensure log directory exists
|
||||
if (-not (Test-Path $script:Config.LogPath)) {
|
||||
New-Item -ItemType Directory -Path $script:Config.LogPath -Force | Out-Null
|
||||
}
|
||||
|
||||
$logFile = Join-Path $script:Config.LogPath "check-and-deploy.log"
|
||||
Add-Content -Path $logFile -Value $logMessage
|
||||
|
||||
# Also write to console if interactive
|
||||
if ($Interactive) {
|
||||
$color = switch ($Level) {
|
||||
"SUCCESS" { "Green" }
|
||||
"WARNING" { "Yellow" }
|
||||
"ERROR" { "Red" }
|
||||
default { "White" }
|
||||
}
|
||||
Write-Host $logMessage -ForegroundColor $color
|
||||
}
|
||||
}
|
||||
|
||||
function Write-Step {
|
||||
param([string]$Message)
|
||||
Write-Host "`n[*] $Message" -ForegroundColor Cyan
|
||||
Write-Log -Message $Message -Level "INFO"
|
||||
}
|
||||
|
||||
function Write-Success {
|
||||
param([string]$Message)
|
||||
Write-Host " [OK] $Message" -ForegroundColor Green
|
||||
Write-Log -Message $Message -Level "SUCCESS"
|
||||
}
|
||||
|
||||
function Write-Error {
|
||||
param([string]$Message)
|
||||
Write-Host " [ERROR] $Message" -ForegroundColor Red
|
||||
Write-Log -Message $Message -Level "ERROR"
|
||||
}
|
||||
|
||||
function Write-Warning {
|
||||
param([string]$Message)
|
||||
Write-Host " [WARN] $Message" -ForegroundColor Yellow
|
||||
Write-Log -Message $Message -Level "WARNING"
|
||||
}
|
||||
|
||||
function Wait-ForKeyPress {
|
||||
Write-Host "`nPress any key to continue..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# STATE MANAGEMENT
|
||||
# =============================================================================
|
||||
|
||||
function Get-DeploymentState {
|
||||
if (Test-Path $script:Config.StateFile) {
|
||||
try {
|
||||
return Get-Content -Path $script:Config.StateFile -Raw | ConvertFrom-Json
|
||||
} catch {
|
||||
Write-Warning "Failed to load state file, starting fresh"
|
||||
return $null
|
||||
}
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Save-DeploymentState {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[hashtable]$DeploymentInfo
|
||||
)
|
||||
|
||||
try {
|
||||
# Load existing state
|
||||
$state = Get-DeploymentState
|
||||
|
||||
$newEntry = @{
|
||||
packageName = $DeploymentInfo.PackageName
|
||||
component = $DeploymentInfo.Component
|
||||
timestamp = (Get-Date).ToUniversalTime().ToString("o")
|
||||
status = $DeploymentInfo.Status
|
||||
services = $DeploymentInfo.Services
|
||||
}
|
||||
|
||||
if ($state) {
|
||||
# Add current deployment to history
|
||||
if (-not $state.history) {
|
||||
$state.history = @()
|
||||
}
|
||||
if ($state.lastDeployment) {
|
||||
$state.history = @($state.lastDeployment) + $state.history
|
||||
}
|
||||
|
||||
# Keep only last 10 entries in history
|
||||
if ($state.history.Count -gt 10) {
|
||||
$state.history = $state.history | Select-Object -First 10
|
||||
}
|
||||
|
||||
$state.lastDeployment = $newEntry
|
||||
} else {
|
||||
$state = @{
|
||||
lastDeployment = $newEntry
|
||||
history = @()
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure directory exists
|
||||
$stateDir = Split-Path $script:Config.StateFile -Parent
|
||||
if (-not (Test-Path $stateDir)) {
|
||||
New-Item -ItemType Directory -Path $stateDir -Force | Out-Null
|
||||
}
|
||||
|
||||
# Save state
|
||||
$state | ConvertTo-Json -Depth 10 | Set-Content -Path $script:Config.StateFile
|
||||
Write-Success "Deployment state saved"
|
||||
} catch {
|
||||
Write-Error "Failed to save deployment state: $_"
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# PACKAGE DETECTION
|
||||
# =============================================================================
|
||||
|
||||
function Get-LatestDeploymentPackage {
|
||||
Write-Step "Scanning for deployment packages..."
|
||||
|
||||
if (-not (Test-Path $script:Config.WatchPath)) {
|
||||
Write-Warning "Watch path does not exist: $($script:Config.WatchPath)"
|
||||
return $null
|
||||
}
|
||||
|
||||
# Find all deploy-* folders
|
||||
$packages = Get-ChildItem -Path $script:Config.WatchPath -Directory |
|
||||
Where-Object { $_.Name -match "^deploy-\d{8}-\d{6}$" } |
|
||||
Sort-Object LastWriteTime -Descending
|
||||
|
||||
if ($packages.Count -eq 0) {
|
||||
Write-Warning "No deployment packages found"
|
||||
return $null
|
||||
}
|
||||
|
||||
$latest = $packages | Select-Object -First 1
|
||||
Write-Success "Found $($packages.Count) package(s), latest: $($latest.Name)"
|
||||
|
||||
return $latest
|
||||
}
|
||||
|
||||
function Test-IsNewPackage {
|
||||
param([Parameter(Mandatory)]$Package)
|
||||
|
||||
$state = Get-DeploymentState
|
||||
|
||||
if (-not $state -or -not $state.lastDeployment) {
|
||||
Write-Success "No previous deployment found - this is a new package"
|
||||
return $true
|
||||
}
|
||||
|
||||
$lastDeployed = $state.lastDeployment.packageName
|
||||
|
||||
if ($Package.Name -ne $lastDeployed) {
|
||||
Write-Success "New package detected (last deployed: $lastDeployed)"
|
||||
return $true
|
||||
}
|
||||
|
||||
Write-Log -Message "Package already deployed: $($Package.Name)" -Level "INFO"
|
||||
return $false
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# DEPLOYMENT EXECUTION
|
||||
# =============================================================================
|
||||
|
||||
function Invoke-Deployment {
|
||||
param([Parameter(Mandatory)]$Package)
|
||||
|
||||
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
|
||||
Write-Host " STARTING DEPLOYMENT" -ForegroundColor Cyan
|
||||
Write-Host ("=" * 70) -ForegroundColor Cyan
|
||||
Write-Host "`nPackage: $($Package.Name)" -ForegroundColor Yellow
|
||||
Write-Host "Path: $($Package.FullName)" -ForegroundColor Gray
|
||||
|
||||
# Find ROA2WEB-Console.ps1 in package
|
||||
$consoleScript = Join-Path $Package.FullName "scripts\ROA2WEB-Console.ps1"
|
||||
|
||||
if (-not (Test-Path $consoleScript)) {
|
||||
Write-Error "ROA2WEB-Console.ps1 not found in package: $consoleScript"
|
||||
return $false
|
||||
}
|
||||
|
||||
try {
|
||||
Write-Step "Executing deployment via ROA2WEB-Console.ps1..."
|
||||
|
||||
# Set environment variable to tell ROA2WEB-Console where the package is
|
||||
$env:ROA2WEB_SOURCE = $Package.FullName
|
||||
|
||||
# Execute deployment (deploy all components)
|
||||
Push-Location (Split-Path $consoleScript -Parent)
|
||||
|
||||
& $consoleScript -NonInteractive -Action DeployAll
|
||||
|
||||
# Capture exit code IMMEDIATELY (before any other command that might reset it)
|
||||
$exitCode = $LASTEXITCODE
|
||||
|
||||
Pop-Location
|
||||
|
||||
# Check if exit code indicates success (0 = success)
|
||||
$deploySuccess = ($exitCode -eq 0)
|
||||
|
||||
if ($deploySuccess) {
|
||||
Write-Success "Deployment completed successfully (exit code: $exitCode)"
|
||||
|
||||
# Save deployment state
|
||||
Save-DeploymentState -DeploymentInfo @{
|
||||
PackageName = $Package.Name
|
||||
Component = "All"
|
||||
Status = "Success"
|
||||
Services = @{
|
||||
backend = "Running"
|
||||
telegramBot = "Running"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n" + ("=" * 70) -ForegroundColor Green
|
||||
Write-Host " DEPLOYMENT SUCCESSFUL" -ForegroundColor Green
|
||||
Write-Host ("=" * 70) -ForegroundColor Green
|
||||
|
||||
return $true
|
||||
} else {
|
||||
Write-Error "Deployment failed (exit code: $exitCode)"
|
||||
|
||||
# Save failure state
|
||||
Save-DeploymentState -DeploymentInfo @{
|
||||
PackageName = $Package.Name
|
||||
Component = "All"
|
||||
Status = "Failed"
|
||||
Services = @{}
|
||||
}
|
||||
|
||||
Write-Host "`n" + ("=" * 70) -ForegroundColor Red
|
||||
Write-Host " DEPLOYMENT FAILED" -ForegroundColor Red
|
||||
Write-Host ("=" * 70) -ForegroundColor Red
|
||||
|
||||
return $false
|
||||
}
|
||||
} catch {
|
||||
Write-Error "Deployment exception: $_"
|
||||
Write-Log -Message $_.ScriptStackTrace -Level "ERROR"
|
||||
|
||||
# Save failure state
|
||||
Save-DeploymentState -DeploymentInfo @{
|
||||
PackageName = $Package.Name
|
||||
Component = "All"
|
||||
Status = "Failed"
|
||||
Services = @{}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# INTERACTIVE MENU
|
||||
# =============================================================================
|
||||
|
||||
function Show-MainMenu {
|
||||
Clear-Host
|
||||
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
|
||||
Write-Host " ROA2WEB - Auto-Deploy Monitor (Interactive)" -ForegroundColor Cyan
|
||||
Write-Host ("=" * 70) -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host " [1] Check for Updates (No Deploy)" -ForegroundColor White
|
||||
Write-Host " (Scan for new packages without deploying)" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host " [2] Check and Deploy Now" -ForegroundColor White
|
||||
Write-Host " (Find latest package and deploy if new)" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host " [3] View Deployment History" -ForegroundColor White
|
||||
Write-Host " (Show previous deployments)" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host " [4] View Current Configuration" -ForegroundColor White
|
||||
Write-Host " (Display monitor settings)" -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 "CheckOnly" }
|
||||
"2" { return "CheckAndDeploy" }
|
||||
"3" { return "History" }
|
||||
"4" { return "ViewConfig" }
|
||||
"Q" { return "Quit" }
|
||||
default {
|
||||
Write-Host "Invalid choice. Please select 1-4 or Q." -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
} while ($true)
|
||||
}
|
||||
|
||||
function Show-DeploymentHistory {
|
||||
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
|
||||
Write-Host " Deployment History" -ForegroundColor Cyan
|
||||
Write-Host ("=" * 70) -ForegroundColor Cyan
|
||||
|
||||
$state = Get-DeploymentState
|
||||
|
||||
if (-not $state) {
|
||||
Write-Host "`nNo deployment history found" -ForegroundColor Yellow
|
||||
return
|
||||
}
|
||||
|
||||
if ($state.lastDeployment) {
|
||||
Write-Host "`nLast Deployment:" -ForegroundColor Yellow
|
||||
Write-Host " Package: $($state.lastDeployment.packageName)" -ForegroundColor Gray
|
||||
Write-Host " Component: $($state.lastDeployment.component)" -ForegroundColor Gray
|
||||
Write-Host " Timestamp: $($state.lastDeployment.timestamp)" -ForegroundColor Gray
|
||||
Write-Host " Status: $($state.lastDeployment.status)" -ForegroundColor $(if ($state.lastDeployment.status -eq "Success") { "Green" } else { "Red" })
|
||||
}
|
||||
|
||||
if ($state.history -and $state.history.Count -gt 0) {
|
||||
Write-Host "`nPrevious Deployments:" -ForegroundColor Yellow
|
||||
$i = 1
|
||||
foreach ($entry in $state.history) {
|
||||
Write-Host " [$i] $($entry.packageName) - $($entry.timestamp) - $($entry.status)" -ForegroundColor Gray
|
||||
$i++
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ("=" * 70) -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
function Show-Configuration {
|
||||
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
|
||||
Write-Host " Current Configuration" -ForegroundColor Cyan
|
||||
Write-Host ("=" * 70) -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host " Watch Path: $($script:Config.WatchPath)" -ForegroundColor Gray
|
||||
Write-Host " State File: $($script:Config.StateFile)" -ForegroundColor Gray
|
||||
Write-Host " Log Path: $($script:Config.LogPath)" -ForegroundColor Gray
|
||||
Write-Host " Console Script: $($script:Config.ConsoleScriptPath)" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host ("=" * 70) -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# MAIN CHECK & DEPLOY LOGIC
|
||||
# =============================================================================
|
||||
|
||||
function Invoke-CheckAndDeploy {
|
||||
param([switch]$CheckOnly)
|
||||
|
||||
Write-Log -Message "=== Check and Deploy Started ===" -Level "INFO"
|
||||
|
||||
# Get latest package
|
||||
$latestPackage = Get-LatestDeploymentPackage
|
||||
|
||||
if (-not $latestPackage) {
|
||||
Write-Log -Message "No deployment packages found" -Level "INFO"
|
||||
return $false
|
||||
}
|
||||
|
||||
# Check if it's a new package
|
||||
$isNew = Test-IsNewPackage -Package $latestPackage
|
||||
|
||||
if (-not $isNew) {
|
||||
Write-Log -Message "No new packages to deploy" -Level "INFO"
|
||||
return $false
|
||||
}
|
||||
|
||||
if ($CheckOnly) {
|
||||
Write-Success "New package available: $($latestPackage.Name)"
|
||||
Write-Host "`nRun without -CheckOnly to deploy" -ForegroundColor Yellow
|
||||
return $false
|
||||
}
|
||||
|
||||
# Deploy the package
|
||||
$deploySuccess = Invoke-Deployment -Package $latestPackage
|
||||
|
||||
Write-Log -Message "=== Check and Deploy Completed (Success: $deploySuccess) ===" -Level "INFO"
|
||||
|
||||
return $deploySuccess
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# MAIN EXECUTION FLOW
|
||||
# =============================================================================
|
||||
|
||||
function Main {
|
||||
# Check if running as Administrator
|
||||
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
|
||||
$isAdmin = $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
|
||||
if (-not $isAdmin) {
|
||||
Write-Host "`n[ERROR] This script requires Administrator privileges" -ForegroundColor Red
|
||||
Write-Host "Please run PowerShell as Administrator and try again.`n" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Interactive mode
|
||||
if ($Interactive) {
|
||||
do {
|
||||
$mainChoice = Show-MainMenu
|
||||
|
||||
switch ($mainChoice) {
|
||||
"CheckOnly" {
|
||||
Invoke-CheckAndDeploy -CheckOnly
|
||||
Wait-ForKeyPress
|
||||
}
|
||||
|
||||
"CheckAndDeploy" {
|
||||
Invoke-CheckAndDeploy
|
||||
Wait-ForKeyPress
|
||||
}
|
||||
|
||||
"History" {
|
||||
Show-DeploymentHistory
|
||||
Wait-ForKeyPress
|
||||
}
|
||||
|
||||
"ViewConfig" {
|
||||
Show-Configuration
|
||||
Wait-ForKeyPress
|
||||
}
|
||||
|
||||
"Quit" {
|
||||
Write-Host "`nGoodbye!`n" -ForegroundColor Cyan
|
||||
return
|
||||
}
|
||||
}
|
||||
} while ($true)
|
||||
}
|
||||
# Non-interactive mode (for Scheduled Task)
|
||||
else {
|
||||
Invoke-CheckAndDeploy -CheckOnly:$CheckOnly | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
# Run main
|
||||
Main
|
||||
Reference in New Issue
Block a user