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:
2025-11-14 14:59:47 +02:00
parent 914ab0107e
commit 87859e5510
7 changed files with 2714 additions and 14 deletions

View File

@@ -658,6 +658,11 @@ function Copy-DeploymentScripts {
$scriptsDir = Join-Path $DestPath "scripts"
New-Item -ItemType Directory -Path $scriptsDir -Force | Out-Null
# Debug: Show paths
Write-Host " [DEBUG] ScriptsSourcePath: $ScriptsSourcePath" -ForegroundColor Magenta
Write-Host " [DEBUG] DestPath: $DestPath" -ForegroundColor Magenta
Write-Host " [DEBUG] ScriptsDir: $scriptsDir" -ForegroundColor Magenta
# Essential scripts for all deployments
$scripts = @(
"ROA2WEB-Console.ps1", # Unified deployment & management console
@@ -684,7 +689,25 @@ function Copy-DeploymentScripts {
foreach ($script in $scripts) {
$scriptPath = Join-Path $ScriptsSourcePath $script
if (Test-Path $scriptPath) {
$destScript = Join-Path $scriptsDir $script
# Debug: Show file info
$sourceSize = (Get-Item $scriptPath).Length
Write-Host " [DEBUG] Copying $script (size: $sourceSize bytes)" -ForegroundColor Magenta
# Force delete destination first to avoid caching issues
if (Test-Path $destScript) {
$oldSize = (Get-Item $destScript).Length
Write-Host " [DEBUG] Deleting old $script (was: $oldSize bytes)" -ForegroundColor Magenta
Remove-Item -Path $destScript -Force
}
Copy-Item -Path $scriptPath -Destination $scriptsDir -Force
# Debug: Verify copy
$newSize = (Get-Item $destScript).Length
Write-Host " [DEBUG] Copied $script (new size: $newSize bytes)" -ForegroundColor Magenta
$copiedCount++
} else {
Write-Warning "Script not found: $script"

View 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

View File

@@ -0,0 +1,847 @@
<#
.SYNOPSIS
ROA2WEB - Interactive Build & Publish Console
.DESCRIPTION
Build deployment packages locally and transfer to Windows Server:
- Build locally (Frontend, Backend, Telegram Bot, or All)
- Transfer via Windows Share (LAN) or SSH/SCP (remote)
- Auto-detect best transfer method
- Interactive menu or command-line operation
- Server auto-deploys when new package detected
.PARAMETER NonInteractive
Run in non-interactive mode with specific action
.PARAMETER Action
Action to perform in non-interactive mode:
- Build: Build and publish package
- TestConnections: Test Windows Share and SSH connectivity
- Configure: Edit configuration (interactive only)
- ViewConfig: Display current configuration
.PARAMETER Component
Component to build (All, Frontend, Backend, TelegramBot)
.PARAMETER TransferMethod
Transfer method (Auto, WindowsShare, SSH)
.PARAMETER ConfigFile
Path to configuration file (default: deploy-config.json)
.EXAMPLE
.\Publish-And-Deploy.ps1
Launch interactive menu
.EXAMPLE
.\Publish-And-Deploy.ps1 -NonInteractive -Action Build -Component All
Build all components and publish (auto-detect transfer method)
.EXAMPLE
.\Publish-And-Deploy.ps1 -NonInteractive -Action Build -Component Frontend -TransferMethod SSH
Build frontend and publish via SSH
.NOTES
Author: ROA2WEB Team
Version: 1.0 (Interactive Build & Publish)
Requires: PowerShell 5.1+, SSH key configured for remote access
#>
[CmdletBinding()]
param(
[switch]$NonInteractive,
[ValidateSet("Build", "TestConnections", "ViewConfig")]
[string]$Action = "",
[ValidateSet("All", "Frontend", "Backend", "TelegramBot")]
[string]$Component = "",
[ValidateSet("Auto", "WindowsShare", "SSH")]
[string]$TransferMethod = "",
[string]$ConfigFile = ""
)
$ErrorActionPreference = "Stop"
# =============================================================================
# CONFIGURATION LOADING
# =============================================================================
$script:ConfigFilePath = if ($ConfigFile) {
$ConfigFile
} else {
Join-Path $PSScriptRoot "deploy-config.json"
}
function Load-Configuration {
Write-Host "`nLoading configuration from: $script:ConfigFilePath" -ForegroundColor Gray
if (-not (Test-Path $script:ConfigFilePath)) {
Write-Host "[ERROR] Configuration file not found: $script:ConfigFilePath" -ForegroundColor Red
Write-Host "Creating default configuration..." -ForegroundColor Yellow
$defaultConfig = @{
server = @{
host = "10.0.20.36"
sshPort = 22122
sshUser = "Administrator"
sshKeyPath = "~/.ssh/roa2web_deploy"
windowsShare = "\\10.0.20.36\ROA2WEB-Deploy"
deployPath = "C:\ROA2WEB-Deploy"
}
build = @{
defaultComponent = "All"
outputPath = "../deploy-package"
}
transfer = @{
defaultMethod = "Auto"
scpRemotePath = "/cygdrive/c/ROA2WEB-Deploy"
retryAttempts = 3
timeout = 300
}
deployment = @{
autoDeployEnabled = $true
checkIntervalMinutes = 5
}
}
$defaultConfig | ConvertTo-Json -Depth 10 | Set-Content -Path $script:ConfigFilePath
Write-Host "Default configuration created. Please review and edit as needed." -ForegroundColor Green
}
try {
$script:Config = Get-Content -Path $script:ConfigFilePath -Raw | ConvertFrom-Json
Write-Host "✓ Configuration loaded successfully" -ForegroundColor Green
return $true
} catch {
Write-Host "[ERROR] Failed to load configuration: $_" -ForegroundColor Red
return $false
}
}
function Save-Configuration {
try {
$script:Config | ConvertTo-Json -Depth 10 | Set-Content -Path $script:ConfigFilePath
Write-Host "✓ Configuration saved successfully" -ForegroundColor Green
return $true
} catch {
Write-Host "[ERROR] Failed to save configuration: $_" -ForegroundColor Red
return $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 Write-Info {
param([string]$Message)
Write-Host " [*] $Message" -ForegroundColor Yellow
}
function Resolve-FullPath {
param([string]$Path)
# Resolve relative paths from the parent of the scripts directory
# This matches the resolution logic in Build-ROA2WEB.ps1
$scriptDir = Split-Path -Parent $PSScriptRoot
$fullPath = Join-Path $scriptDir $Path
$fullPath = [System.IO.Path]::GetFullPath($fullPath)
return $fullPath
}
function Wait-ForKeyPress {
Write-Host "`nPress any key to continue..." -ForegroundColor Gray
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}
# =============================================================================
# CONNECTION TESTING
# =============================================================================
function Test-WindowsShare {
Write-Step "Testing Windows Share access..."
$sharePath = $script:Config.server.windowsShare
Write-Info "Share: $sharePath"
try {
if (Test-Path $sharePath) {
Write-Success "Windows Share is accessible"
# Try to write test file
$testFile = Join-Path $sharePath ".test-$(Get-Date -Format 'yyyyMMddHHmmss')"
"test" | Out-File -FilePath $testFile -Force
if (Test-Path $testFile) {
Remove-Item -Path $testFile -Force
Write-Success "Write permissions verified"
return $true
} else {
Write-Warning "Share is readable but write test failed"
return $false
}
} else {
Write-Error "Windows Share is not accessible"
Write-Info "Ensure network share is configured and accessible"
return $false
}
} catch {
Write-Error "Failed to access Windows Share: $_"
return $false
}
}
function Test-SSHConnection {
Write-Step "Testing SSH connection..."
$serverHost = $script:Config.server.host
$port = $script:Config.server.sshPort
$user = $script:Config.server.sshUser
$keyPath = $script:Config.server.sshKeyPath
Write-Info "Host: ${serverHost}:${port}"
Write-Info "User: $user"
Write-Info "Key: $keyPath"
# Expand tilde in key path (support both Unix / and Windows \ styles)
if ($keyPath -like "~/*" -or $keyPath -like "~\*") {
$keyPath = $keyPath -replace "^~", $env:USERPROFILE
}
if (-not (Test-Path $keyPath)) {
Write-Error "SSH key not found: $keyPath"
return $false
}
try {
# Test SSH connection
$sshTest = ssh -i "$keyPath" -p $port -o ConnectTimeout=10 -o StrictHostKeyChecking=no "${user}@${serverHost}" "echo 'Connection successful'" 2>&1
if ($LASTEXITCODE -eq 0 -and $sshTest -like "*Connection successful*") {
Write-Success "SSH connection established"
return $true
} else {
Write-Error "SSH connection failed"
Write-Info "Output: $sshTest"
return $false
}
} catch {
Write-Error "Failed to test SSH connection: $_"
return $false
}
}
function Test-AllConnections {
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
Write-Host " Connection Testing" -ForegroundColor Cyan
Write-Host ("=" * 70) -ForegroundColor Cyan
$shareOk = Test-WindowsShare
$sshOk = Test-SSHConnection
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
Write-Host " Test Results" -ForegroundColor Cyan
Write-Host ("=" * 70) -ForegroundColor Cyan
Write-Host "`n Windows Share: " -NoNewline
if ($shareOk) {
Write-Host "✓ Available" -ForegroundColor Green
} else {
Write-Host "✗ Not Available" -ForegroundColor Red
}
Write-Host " SSH Connection: " -NoNewline
if ($sshOk) {
Write-Host "✓ Available" -ForegroundColor Green
} else {
Write-Host "✗ Not Available" -ForegroundColor Red
}
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
return @{
WindowsShare = $shareOk
SSH = $sshOk
}
}
# =============================================================================
# BUILD & TRANSFER FUNCTIONS
# =============================================================================
function Invoke-Build {
param(
[Parameter(Mandatory)]
[ValidateSet("All", "Frontend", "Backend", "TelegramBot")]
[string]$Component
)
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
Write-Host " Building Component: $Component" -ForegroundColor Cyan
Write-Host ("=" * 70) -ForegroundColor Cyan
$buildScript = Join-Path $PSScriptRoot "Build-ROA2WEB.ps1"
if (-not (Test-Path $buildScript)) {
Write-Error "Build script not found: $buildScript"
return $false
}
try {
$outputPath = $script:Config.build.outputPath
# Resolve the output path using the same logic as Build-ROA2WEB.ps1
$resolvedOutputPath = if ([System.IO.Path]::IsPathRooted($outputPath)) {
$outputPath
} else {
Resolve-FullPath -Path $outputPath
}
Write-Step "Building deployment package..."
& $buildScript -Component $Component -OutputPath $outputPath
if ($LASTEXITCODE -ne 0) {
throw "Build script returned error code: $LASTEXITCODE"
}
if (-not (Test-Path $resolvedOutputPath)) {
throw "Build output not found: $resolvedOutputPath"
}
Write-Success "Build completed successfully"
return $true
} catch {
Write-Error "Build failed: $_"
return $false
}
}
function Copy-ViaWindowsShare {
param([string]$SourcePath)
Write-Step "Transferring via Windows Share..."
$sharePath = $script:Config.server.windowsShare
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$deployFolder = "deploy-$timestamp"
$destPath = Join-Path $sharePath $deployFolder
try {
Write-Info "Destination: $destPath"
Copy-Item -Path $SourcePath -Destination $destPath -Recurse -Force
if (Test-Path $destPath) {
Write-Success "Files transferred successfully"
Write-Success "Server will auto-deploy within $($script:Config.deployment.checkIntervalMinutes) minutes"
return $true
} else {
Write-Error "Transfer failed - destination folder not found"
return $false
}
} catch {
Write-Error "Windows Share transfer failed: $_"
return $false
}
}
function Copy-ViaSSH {
param([string]$SourcePath)
Write-Step "Transferring via SSH/SCP..."
$serverHost = $script:Config.server.host
$port = $script:Config.server.sshPort
$user = $script:Config.server.sshUser
$keyPath = $script:Config.server.sshKeyPath
$remotePath = $script:Config.transfer.scpRemotePath
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$deployFolder = "deploy-$timestamp"
# Expand tilde in key path (support both Unix / and Windows \ styles)
if ($keyPath -like "~/*" -or $keyPath -like "~\*") {
$keyPath = $keyPath -replace "^~", $env:USERPROFILE
}
try {
Write-Info "Host: ${serverHost}:${port}"
Write-Info "Destination: ${remotePath}/${deployFolder}"
# Create remote directory
Write-Info "Creating remote directory..."
$createDir = ssh -i "$keyPath" -p $port "${user}@${serverHost}" "mkdir -p '${remotePath}/${deployFolder}'" 2>&1
if ($LASTEXITCODE -ne 0) {
throw "Failed to create remote directory: $createDir"
}
# Transfer files via SCP
Write-Info "Transferring files (this may take a few minutes)..."
$scpResult = scp -i "$keyPath" -P $port -r "${SourcePath}/*" "${user}@${serverHost}:${remotePath}/${deployFolder}/" 2>&1
if ($LASTEXITCODE -ne 0) {
throw "SCP transfer failed: $scpResult"
}
Write-Success "Files transferred successfully"
Write-Success "Server will auto-deploy within $($script:Config.deployment.checkIntervalMinutes) minutes"
return $true
} catch {
Write-Error "SSH transfer failed: $_"
return $false
}
}
function Invoke-Transfer {
param(
[Parameter(Mandatory)]
[ValidateSet("Auto", "WindowsShare", "SSH")]
[string]$Method,
[Parameter(Mandatory)]
[string]$SourcePath
)
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
Write-Host " Transfer Method: $Method" -ForegroundColor Cyan
Write-Host ("=" * 70) -ForegroundColor Cyan
if ($Method -eq "Auto") {
Write-Info "Auto-detecting best transfer method..."
# Try Windows Share first (faster for LAN)
Write-Info "Checking Windows Share availability..."
if (Test-Path $script:Config.server.windowsShare) {
Write-Success "Windows Share accessible - using Windows Share"
return Copy-ViaWindowsShare -SourcePath $SourcePath
} else {
Write-Warning "Windows Share not accessible - falling back to SSH"
return Copy-ViaSSH -SourcePath $SourcePath
}
} elseif ($Method -eq "WindowsShare") {
return Copy-ViaWindowsShare -SourcePath $SourcePath
} else {
return Copy-ViaSSH -SourcePath $SourcePath
}
}
# =============================================================================
# CONFIGURATION MANAGEMENT
# =============================================================================
function Show-CurrentConfig {
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
Write-Host " Current Configuration" -ForegroundColor Cyan
Write-Host ("=" * 70) -ForegroundColor Cyan
Write-Host ""
Write-Host " Server Settings:" -ForegroundColor Yellow
Write-Host " Host: $($script:Config.server.host)" -ForegroundColor Gray
Write-Host " SSH Port: $($script:Config.server.sshPort)" -ForegroundColor Gray
Write-Host " SSH User: $($script:Config.server.sshUser)" -ForegroundColor Gray
Write-Host " SSH Key: $($script:Config.server.sshKeyPath)" -ForegroundColor Gray
Write-Host " Windows Share: $($script:Config.server.windowsShare)" -ForegroundColor Gray
Write-Host " Deploy Path: $($script:Config.server.deployPath)" -ForegroundColor Gray
Write-Host ""
Write-Host " Build Settings:" -ForegroundColor Yellow
Write-Host " Default Component: $($script:Config.build.defaultComponent)" -ForegroundColor Gray
Write-Host " Output Path: $($script:Config.build.outputPath)" -ForegroundColor Gray
Write-Host ""
Write-Host " Transfer Settings:" -ForegroundColor Yellow
Write-Host " Default Method: $($script:Config.transfer.defaultMethod)" -ForegroundColor Gray
Write-Host " SCP Remote Path: $($script:Config.transfer.scpRemotePath)" -ForegroundColor Gray
Write-Host " Retry Attempts: $($script:Config.transfer.retryAttempts)" -ForegroundColor Gray
Write-Host " Timeout (sec): $($script:Config.transfer.timeout)" -ForegroundColor Gray
Write-Host ""
Write-Host " Deployment Settings:" -ForegroundColor Yellow
Write-Host " Auto-Deploy: $($script:Config.deployment.autoDeployEnabled)" -ForegroundColor Gray
Write-Host " Check Interval: $($script:Config.deployment.checkIntervalMinutes) minutes" -ForegroundColor Gray
Write-Host ""
Write-Host " Config File: $script:ConfigFilePath" -ForegroundColor Gray
Write-Host ("=" * 70) -ForegroundColor Cyan
}
function Edit-Configuration {
Clear-Host
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
Write-Host " Configuration Editor" -ForegroundColor Cyan
Write-Host ("=" * 70) -ForegroundColor Cyan
Write-Host ""
Write-Host " Current Settings:" -ForegroundColor Yellow
Write-Host " Server Host: $($script:Config.server.host)" -ForegroundColor Gray
Write-Host " SSH Port: $($script:Config.server.sshPort)" -ForegroundColor Gray
Write-Host " SSH User: $($script:Config.server.sshUser)" -ForegroundColor Gray
Write-Host " SSH Key: $($script:Config.server.sshKeyPath)" -ForegroundColor Gray
Write-Host " Windows Share: $($script:Config.server.windowsShare)" -ForegroundColor Gray
Write-Host " Server Deploy Path: $($script:Config.server.deployPath)" -ForegroundColor Gray
Write-Host ""
Write-Host " [1] Edit SSH Connection Settings" -ForegroundColor White
Write-Host " [2] Edit Windows Share Path" -ForegroundColor White
Write-Host " [3] Edit Server Deploy Path" -ForegroundColor White
Write-Host " [4] Edit Build Settings" -ForegroundColor White
Write-Host " [5] Edit Transfer Settings" -ForegroundColor White
Write-Host " [S] Save Configuration" -ForegroundColor Green
Write-Host " [R] Reset to Defaults" -ForegroundColor Yellow
Write-Host " [B] Back (discard changes)" -ForegroundColor Red
Write-Host ""
Write-Host ("=" * 70) -ForegroundColor Cyan
do {
Write-Host "`nYour choice: " -ForegroundColor Yellow -NoNewline
$choice = Read-Host
switch ($choice.ToUpper()) {
"1" {
Write-Host "`nEdit SSH Connection Settings:" -ForegroundColor Yellow
Write-Host "Server Host [current: $($script:Config.server.host)]: " -NoNewline
$newHost = Read-Host
if ($newHost) { $script:Config.server.host = $newHost }
Write-Host "SSH Port [current: $($script:Config.server.sshPort)]: " -NoNewline
$newPort = Read-Host
if ($newPort) { $script:Config.server.sshPort = [int]$newPort }
Write-Host "SSH User [current: $($script:Config.server.sshUser)]: " -NoNewline
$newUser = Read-Host
if ($newUser) { $script:Config.server.sshUser = $newUser }
Write-Host "SSH Key Path [current: $($script:Config.server.sshKeyPath)]: " -NoNewline
$newKey = Read-Host
if ($newKey) { $script:Config.server.sshKeyPath = $newKey }
Write-Success "SSH settings updated (not saved yet)"
return Edit-Configuration
}
"2" {
Write-Host "`nEdit Windows Share Path:" -ForegroundColor Yellow
Write-Host "Windows Share [current: $($script:Config.server.windowsShare)]: " -NoNewline
$newShare = Read-Host
if ($newShare) { $script:Config.server.windowsShare = $newShare }
Write-Success "Windows Share updated (not saved yet)"
return Edit-Configuration
}
"3" {
Write-Host "`nEdit Server Deploy Path:" -ForegroundColor Yellow
Write-Host "Deploy Path [current: $($script:Config.server.deployPath)]: " -NoNewline
$newPath = Read-Host
if ($newPath) { $script:Config.server.deployPath = $newPath }
Write-Success "Deploy path updated (not saved yet)"
return Edit-Configuration
}
"4" {
Write-Host "`nEdit Build Settings:" -ForegroundColor Yellow
Write-Host "Default Component (All/Frontend/Backend/TelegramBot) [current: $($script:Config.build.defaultComponent)]: " -NoNewline
$newComp = Read-Host
if ($newComp) { $script:Config.build.defaultComponent = $newComp }
Write-Host "Output Path [current: $($script:Config.build.outputPath)]: " -NoNewline
$newOut = Read-Host
if ($newOut) { $script:Config.build.outputPath = $newOut }
Write-Success "Build settings updated (not saved yet)"
return Edit-Configuration
}
"5" {
Write-Host "`nEdit Transfer Settings:" -ForegroundColor Yellow
Write-Host "Default Method (Auto/WindowsShare/SSH) [current: $($script:Config.transfer.defaultMethod)]: " -NoNewline
$newMethod = Read-Host
if ($newMethod) { $script:Config.transfer.defaultMethod = $newMethod }
Write-Host "SCP Remote Path [current: $($script:Config.transfer.scpRemotePath)]: " -NoNewline
$newScp = Read-Host
if ($newScp) { $script:Config.transfer.scpRemotePath = $newScp }
Write-Success "Transfer settings updated (not saved yet)"
return Edit-Configuration
}
"S" {
if (Save-Configuration) {
Write-Host "`nConfiguration saved successfully!" -ForegroundColor Green
Wait-ForKeyPress
return
}
}
"R" {
Write-Host "`nAre you sure you want to reset to defaults? [Y/N]: " -ForegroundColor Yellow -NoNewline
$confirm = Read-Host
if ($confirm.ToUpper() -eq "Y") {
Load-Configuration
Write-Success "Configuration reset to defaults"
return Edit-Configuration
}
}
"B" {
Write-Host "`nChanges discarded" -ForegroundColor Yellow
Load-Configuration # Reload from file
return
}
default {
Write-Host "Invalid choice. Please select 1-5, S, R, or B." -ForegroundColor Red
}
}
} while ($true)
}
# =============================================================================
# MENU FUNCTIONS
# =============================================================================
function Show-MainMenu {
Clear-Host
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
Write-Host " ROA2WEB - Interactive Build & Publish Console" -ForegroundColor Cyan
Write-Host ("=" * 70) -ForegroundColor Cyan
Write-Host ""
Write-Host " Main Menu:" -ForegroundColor Yellow
Write-Host ""
Write-Host " [1] Build & Publish Package" -ForegroundColor White
Write-Host " (Build locally and transfer to server)" -ForegroundColor Gray
Write-Host ""
Write-Host " [2] Test Connections" -ForegroundColor White
Write-Host " (Verify Windows Share and SSH access)" -ForegroundColor Gray
Write-Host ""
Write-Host " [3] Configure Settings" -ForegroundColor White
Write-Host " (Edit deployment configuration)" -ForegroundColor Gray
Write-Host ""
Write-Host " [4] View Current Configuration" -ForegroundColor White
Write-Host " (Display active 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 "BuildPublish" }
"2" { return "TestConnections" }
"3" { return "Configure" }
"4" { return "ViewConfig" }
"Q" { return "Quit" }
default {
Write-Host "Invalid choice. Please select 1-4 or Q." -ForegroundColor Red
}
}
} while ($true)
}
function Show-ComponentMenu {
Clear-Host
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
Write-Host " Select Component to Build" -ForegroundColor Cyan
Write-Host ("=" * 70) -ForegroundColor Cyan
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)" -ForegroundColor Gray
Write-Host ""
Write-Host " [3] Backend Only" -ForegroundColor White
Write-Host " (FastAPI backend + 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 " [B] Back to Main Menu" -ForegroundColor Yellow
Write-Host ""
Write-Host ("=" * 70) -ForegroundColor Cyan
do {
Write-Host "`nYour choice: " -ForegroundColor Yellow -NoNewline
$choice = Read-Host
switch ($choice.ToUpper()) {
"1" { return "All" }
"2" { return "Frontend" }
"3" { return "Backend" }
"4" { return "TelegramBot" }
"B" { return "Back" }
default {
Write-Host "Invalid choice. Please select 1-4 or B." -ForegroundColor Red
}
}
} while ($true)
}
function Show-TransferMenu {
Clear-Host
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
Write-Host " Select Transfer Method" -ForegroundColor Cyan
Write-Host ("=" * 70) -ForegroundColor Cyan
Write-Host ""
Write-Host " [1] Auto-detect (Recommended)" -ForegroundColor White
Write-Host " Try Windows Share first, fallback to SSH" -ForegroundColor Gray
Write-Host ""
Write-Host " [2] Windows Share (LAN)" -ForegroundColor White
Write-Host " Fast transfer via network share ($($script:Config.server.windowsShare))" -ForegroundColor Gray
Write-Host ""
Write-Host " [3] SSH/SCP (Remote)" -ForegroundColor White
Write-Host " Secure transfer via SSH (port $($script:Config.server.sshPort))" -ForegroundColor Gray
Write-Host ""
Write-Host " [B] Back" -ForegroundColor Yellow
Write-Host ""
Write-Host ("=" * 70) -ForegroundColor Cyan
do {
Write-Host "`nYour choice: " -ForegroundColor Yellow -NoNewline
$choice = Read-Host
switch ($choice.ToUpper()) {
"1" { return "Auto" }
"2" { return "WindowsShare" }
"3" { return "SSH" }
"B" { return "Back" }
default {
Write-Host "Invalid choice. Please select 1-3 or B." -ForegroundColor Red
}
}
} while ($true)
}
# =============================================================================
# ACTION HANDLERS
# =============================================================================
function Invoke-BuildAndPublish {
param(
[string]$Component = "",
[string]$TransferMethod = ""
)
# Get component if not provided
if (-not $Component) {
$Component = Show-ComponentMenu
if ($Component -eq "Back") { return }
}
# Get transfer method if not provided
if (-not $TransferMethod) {
$TransferMethod = Show-TransferMenu
if ($TransferMethod -eq "Back") { return }
}
# Build
$buildSuccess = Invoke-Build -Component $Component
if (-not $buildSuccess) {
Write-Host "`n[BUILD FAILED] Cannot proceed with transfer" -ForegroundColor Red
if (-not $NonInteractive) { Wait-ForKeyPress }
return
}
# Transfer
$outputPath = $script:Config.build.outputPath
# Resolve the output path for transfer
$resolvedOutputPath = if ([System.IO.Path]::IsPathRooted($outputPath)) {
$outputPath
} else {
Resolve-FullPath -Path $outputPath
}
$transferSuccess = Invoke-Transfer -Method $TransferMethod -SourcePath $resolvedOutputPath
if ($transferSuccess) {
Write-Host "`n" + ("=" * 70) -ForegroundColor Green
Write-Host " BUILD & PUBLISH COMPLETED SUCCESSFULLY" -ForegroundColor Green
Write-Host ("=" * 70) -ForegroundColor Green
Write-Host "`nPackage published to server: $($script:Config.server.host)" -ForegroundColor Gray
Write-Host "Server will auto-deploy within $($script:Config.deployment.checkIntervalMinutes) minutes" -ForegroundColor Gray
Write-Host ("=" * 70) -ForegroundColor Green
} else {
Write-Host "`n[TRANSFER FAILED] Package built but transfer failed" -ForegroundColor Red
}
if (-not $NonInteractive) { Wait-ForKeyPress }
}
# =============================================================================
# MAIN EXECUTION FLOW
# =============================================================================
function Main {
# Load configuration
if (-not (Load-Configuration)) {
Write-Host "`nPress any key to exit..." -ForegroundColor Gray
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
exit 1
}
# Non-interactive mode
if ($NonInteractive -and $Action) {
switch ($Action) {
"Build" {
$comp = if ($Component) { $Component } else { $script:Config.build.defaultComponent }
$method = if ($TransferMethod) { $TransferMethod } else { $script:Config.transfer.defaultMethod }
Invoke-BuildAndPublish -Component $comp -TransferMethod $method
}
"TestConnections" {
Test-AllConnections | Out-Null
}
"ViewConfig" {
Show-CurrentConfig
}
}
return
}
# Interactive mode - main loop
do {
$mainChoice = Show-MainMenu
switch ($mainChoice) {
"BuildPublish" {
Invoke-BuildAndPublish
}
"TestConnections" {
Test-AllConnections | Out-Null
Wait-ForKeyPress
}
"Configure" {
Edit-Configuration
}
"ViewConfig" {
Show-CurrentConfig
Wait-ForKeyPress
}
"Quit" {
Write-Host "`nGoodbye!`n" -ForegroundColor Cyan
return
}
}
} while ($true)
}
# Run main
Main

View File

@@ -53,8 +53,14 @@ $ErrorActionPreference = "Stop"
# GLOBAL CONFIGURATION
# =============================================================================
# Auto-detect source path: if running from scripts/ subdirectory, use parent
$detectedSourcePath = if ((Split-Path $PSScriptRoot -Leaf) -eq "scripts") {
# Auto-detect source path with priority:
# 1. Environment variable ROA2WEB_SOURCE (set by Check-And-Deploy.ps1)
# 2. If running from scripts/ subdirectory, use parent
# 3. Otherwise use script root
$detectedSourcePath = if ($env:ROA2WEB_SOURCE) {
Write-Host "[INFO] Using source path from environment: $env:ROA2WEB_SOURCE" -ForegroundColor Cyan
$env:ROA2WEB_SOURCE
} elseif ((Split-Path $PSScriptRoot -Leaf) -eq "scripts") {
Split-Path $PSScriptRoot -Parent
} else {
$PSScriptRoot
@@ -1649,16 +1655,27 @@ function Execute-ManageAction {
function Execute-DeployAction {
param([string]$Component)
# Execute deployment and capture only the boolean result
$success = switch ($Component) {
"Backend" { Deploy-Backend }
"TelegramBot" { Deploy-TelegramBot }
"Backend" {
$result = Deploy-Backend
$result # Explicitly output the boolean
}
"TelegramBot" {
$result = Deploy-TelegramBot
$result # Explicitly output the boolean
}
"All" {
$backendOk = Deploy-Backend
$telegramOk = Deploy-TelegramBot
$backendOk -and $telegramOk
# Return combined result
($backendOk -and $telegramOk)
}
}
# Ensure $success is boolean
$success = [bool]$success
if ($success) {
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
Write-Host " DEPLOYMENT COMPLETED" -ForegroundColor Green
@@ -1668,6 +1685,9 @@ function Execute-DeployAction {
Write-Host " DEPLOYMENT FAILED" -ForegroundColor Red
Write-Host ("=" * 70) -ForegroundColor Cyan
}
# Explicitly return boolean value
return $success
}
function Show-AllStatus {
@@ -1698,16 +1718,37 @@ function Main {
# Non-interactive mode
if ($NonInteractive -and $Action) {
switch ($Action) {
"DeployBackend" { Execute-DeployAction -Component "Backend" }
"DeployTelegramBot" { Execute-DeployAction -Component "TelegramBot" }
"DeployAll" { Execute-DeployAction -Component "All" }
"StartAll" { Execute-ManageAction -Action "Start" -Component "All" }
"StopAll" { Execute-ManageAction -Action "Stop" -Component "All" }
"RestartAll" { Execute-ManageAction -Action "Restart" -Component "All" }
"Status" { Show-AllStatus }
try {
$success = switch ($Action) {
"DeployBackend" { Execute-DeployAction -Component "Backend" }
"DeployTelegramBot" { Execute-DeployAction -Component "TelegramBot" }
"DeployAll" { Execute-DeployAction -Component "All" }
"StartAll" { Execute-ManageAction -Action "Start" -Component "All"; $true }
"StopAll" { Execute-ManageAction -Action "Stop" -Component "All"; $true }
"RestartAll" { Execute-ManageAction -Action "Restart" -Component "All"; $true }
"Status" { Show-AllStatus; $true }
default { $false }
}
# Ensure boolean
$success = [bool]$success
# Debug output
Write-Host "`n[DEBUG] Action: $Action, Success: $success, Type: $($success.GetType().Name)" -ForegroundColor Magenta
if ($success) {
Write-Host "[DEBUG] Exiting with code 0 (success)" -ForegroundColor Magenta
exit 0
} else {
Write-Host "[DEBUG] Exiting with code 1 (failure)" -ForegroundColor Magenta
exit 1
}
} catch {
Write-Host "`n[ERROR] Unhandled exception: $($_.Exception.Message)" -ForegroundColor Red
Write-Host "[ERROR] Stack trace: $($_.ScriptStackTrace)" -ForegroundColor Red
Write-Host "[DEBUG] Exiting with code 1 (exception)" -ForegroundColor Magenta
exit 1
}
return
}
# Interactive mode - main loop

View File

@@ -0,0 +1,472 @@
<#
.SYNOPSIS
ROA2WEB - Auto-Deploy Setup Wizard (Server-Side)
.DESCRIPTION
Interactive wizard to configure automatic deployment monitoring on Windows Server.
Creates scheduled task that runs Check-And-Deploy.ps1 at specified intervals.
.PARAMETER MonitorPath
Path to monitor for deployment packages (default: C:\Temp)
.PARAMETER ScriptsPath
Path where Check-And-Deploy.ps1 is located (default: C:\Temp\ROA2WEB-Scripts)
.PARAMETER CheckIntervalMinutes
How often to check for new packages in minutes (default: 5)
.PARAMETER AutoDeploy
Automatically deploy new packages when found (default: true)
.PARAMETER CreateScheduledTask
Create Windows Scheduled Task for automation (default: true)
.PARAMETER NonInteractive
Run in non-interactive mode with provided parameters
.EXAMPLE
.\Setup-AutoDeploy.ps1
Launch interactive setup wizard
.EXAMPLE
.\Setup-AutoDeploy.ps1 -NonInteractive -MonitorPath "C:\Temp" -CheckIntervalMinutes 5
Non-interactive setup with defaults
.NOTES
Author: ROA2WEB Team
Version: 1.0 (Auto-Deploy Setup)
Requires: Administrator privileges, PowerShell 5.1+
#>
[CmdletBinding()]
param(
[string]$MonitorPath = "C:\Temp",
[string]$ScriptsPath = "C:\Temp\ROA2WEB-Scripts",
[int]$CheckIntervalMinutes = 5,
[bool]$AutoDeploy = $true,
[bool]$CreateScheduledTask = $true,
[switch]$NonInteractive
)
$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 Write-Info {
param([string]$Message)
Write-Host " [*] $Message" -ForegroundColor Yellow
}
# =============================================================================
# WIZARD FUNCTIONS
# =============================================================================
function Show-WizardWelcome {
Clear-Host
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
Write-Host " ROA2WEB - Auto-Deploy Setup Wizard" -ForegroundColor Cyan
Write-Host ("=" * 70) -ForegroundColor Cyan
Write-Host ""
Write-Host " This wizard will configure automatic deployment monitoring." -ForegroundColor Yellow
Write-Host ""
Write-Host " What it does:" -ForegroundColor White
Write-Host " • Creates directory structure for auto-deployment" -ForegroundColor Gray
Write-Host " • Copies Check-And-Deploy.ps1 to scripts folder" -ForegroundColor Gray
Write-Host " • Creates Windows Scheduled Task for monitoring" -ForegroundColor Gray
Write-Host " • Configures auto-deploy settings" -ForegroundColor Gray
Write-Host ""
Write-Host ("=" * 70) -ForegroundColor Cyan
Write-Host ""
Write-Host "Press any key to continue or Ctrl+C to cancel..." -ForegroundColor Yellow
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}
function Get-WizardInput {
param(
[string]$Prompt,
[string]$Default,
[switch]$IsNumber,
[switch]$IsYesNo
)
do {
Write-Host "`n$Prompt" -ForegroundColor Yellow
if ($Default) {
Write-Host " Default: $Default" -ForegroundColor Gray
}
Write-Host " > " -NoNewline -ForegroundColor Cyan
$input = Read-Host
# Use default if empty
if ([string]::IsNullOrWhiteSpace($input) -and $Default) {
return $Default
}
# Validate number
if ($IsNumber) {
$num = 0
if ([int]::TryParse($input, [ref]$num) -and $num -gt 0) {
return $num
} else {
Write-Host " Invalid number. Please enter a positive integer." -ForegroundColor Red
continue
}
}
# Validate Yes/No
if ($IsYesNo) {
$normalized = $input.ToUpper()
if ($normalized -eq "Y" -or $normalized -eq "YES") {
return $true
} elseif ($normalized -eq "N" -or $normalized -eq "NO") {
return $false
} else {
Write-Host " Invalid input. Please enter Y or N." -ForegroundColor Red
continue
}
}
return $input
} while ($true)
}
# =============================================================================
# SETUP FUNCTIONS
# =============================================================================
function Test-Prerequisites {
Write-Step "Checking prerequisites..."
# Check Administrator
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
$isAdmin = $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
throw "This script requires Administrator privileges"
}
Write-Success "Running as Administrator"
# Check PowerShell version
if ($PSVersionTable.PSVersion.Major -lt 5) {
throw "PowerShell 5.1 or higher is required"
}
Write-Success "PowerShell version: $($PSVersionTable.PSVersion)"
# Check if Check-And-Deploy.ps1 exists
$checkDeployScript = Join-Path $PSScriptRoot "Check-And-Deploy.ps1"
if (-not (Test-Path $checkDeployScript)) {
throw "Check-And-Deploy.ps1 not found in: $PSScriptRoot"
}
Write-Success "Check-And-Deploy.ps1 found"
return $true
}
function New-DirectoryStructure {
param(
[string]$MonitorPath,
[string]$ScriptsPath
)
Write-Step "Creating directory structure..."
# Create monitor path
if (-not (Test-Path $MonitorPath)) {
New-Item -ItemType Directory -Path $MonitorPath -Force | Out-Null
Write-Success "Created: $MonitorPath"
} else {
Write-Success "Already exists: $MonitorPath"
}
# Create scripts path
if (-not (Test-Path $ScriptsPath)) {
New-Item -ItemType Directory -Path $ScriptsPath -Force | Out-Null
Write-Success "Created: $ScriptsPath"
} else {
Write-Success "Already exists: $ScriptsPath"
}
# Create logs directory
$logsPath = Join-Path $ScriptsPath "Logs"
if (-not (Test-Path $logsPath)) {
New-Item -ItemType Directory -Path $logsPath -Force | Out-Null
Write-Success "Created: $logsPath"
} else {
Write-Success "Already exists: $logsPath"
}
return $true
}
function Copy-MonitorScript {
param(
[string]$SourcePath,
[string]$DestPath
)
Write-Step "Copying Check-And-Deploy.ps1 to scripts folder..."
$sourceScript = Join-Path $SourcePath "Check-And-Deploy.ps1"
$destScript = Join-Path $DestPath "Check-And-Deploy.ps1"
# Check if source and destination are the same file
$sourceFull = (Resolve-Path $sourceScript -ErrorAction SilentlyContinue).Path
$destFull = (Resolve-Path $destScript -ErrorAction SilentlyContinue).Path
if ($sourceFull -and $destFull -and ($sourceFull -eq $destFull)) {
Write-Success "Script already in target location: $destScript"
return $destScript
}
try {
Copy-Item -Path $sourceScript -Destination $destScript -Force
Write-Success "Script copied to: $destScript"
return $destScript
} catch {
Write-Error "Failed to copy script: $_"
return $null
}
}
function New-ScheduledTaskForAutoDeployNew {
param(
[string]$ScriptPath,
[int]$IntervalMinutes,
[string]$MonitorPath
)
Write-Step "Creating Windows Scheduled Task..."
$taskName = "ROA2WEB-AutoDeploy"
# Remove existing task if present
$existingTask = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
if ($existingTask) {
Write-Info "Removing existing task..."
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
}
try {
# Create action
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File `"$ScriptPath`" -WatchPath `"$MonitorPath`""
# Create trigger (repeat indefinitely every X minutes)
# Note: When RepetitionDuration is not specified, the task repeats indefinitely
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) `
-RepetitionInterval (New-TimeSpan -Minutes $IntervalMinutes)
# Create settings
$settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-StartWhenAvailable `
-RunOnlyIfNetworkAvailable:$false `
-DontStopOnIdleEnd `
-MultipleInstances IgnoreNew
# Register task (run as SYSTEM)
Register-ScheduledTask -TaskName $taskName `
-Action $action `
-Trigger $trigger `
-Settings $settings `
-User "SYSTEM" `
-RunLevel Highest `
-Description "Monitors $MonitorPath for new deployment packages and auto-deploys them" | Out-Null
Write-Success "Scheduled task created: $taskName"
Write-Success "Interval: Every $IntervalMinutes minutes"
Write-Success "User: SYSTEM"
Write-Success "Status: Enabled"
return $true
} catch {
Write-Error "Failed to create scheduled task: $_"
return $false
}
}
function Show-SetupSummary {
param(
[hashtable]$Config
)
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
Write-Host " Setup Summary" -ForegroundColor Cyan
Write-Host ("=" * 70) -ForegroundColor Cyan
Write-Host ""
Write-Host " Monitor Path: $($Config.MonitorPath)" -ForegroundColor Gray
Write-Host " Scripts Path: $($Config.ScriptsPath)" -ForegroundColor Gray
Write-Host " Check Interval: $($Config.CheckIntervalMinutes) minutes" -ForegroundColor Gray
Write-Host " Auto-Deploy: $($Config.AutoDeploy)" -ForegroundColor Gray
Write-Host " Scheduled Task: $($Config.CreateScheduledTask)" -ForegroundColor Gray
Write-Host ""
Write-Host ("=" * 70) -ForegroundColor Cyan
Write-Host ""
Write-Host "Proceed with setup? [Y/N]: " -ForegroundColor Yellow -NoNewline
$confirm = Read-Host
return ($confirm.ToUpper() -eq "Y" -or $confirm.ToUpper() -eq "YES")
}
# =============================================================================
# MAIN SETUP FLOW
# =============================================================================
function Invoke-Setup {
param([hashtable]$Config)
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
Write-Host " Starting Setup" -ForegroundColor Cyan
Write-Host ("=" * 70) -ForegroundColor Cyan
try {
# Test prerequisites
Test-Prerequisites | Out-Null
# Create directory structure
New-DirectoryStructure -MonitorPath $Config.MonitorPath -ScriptsPath $Config.ScriptsPath | Out-Null
# Copy monitor script
$scriptPath = Copy-MonitorScript -SourcePath $PSScriptRoot -DestPath $Config.ScriptsPath
if (-not $scriptPath) {
throw "Failed to copy monitor script"
}
# Create scheduled task if requested
if ($Config.CreateScheduledTask) {
$taskSuccess = New-ScheduledTaskForAutoDeployNew `
-ScriptPath $scriptPath `
-IntervalMinutes $Config.CheckIntervalMinutes `
-MonitorPath $Config.MonitorPath
if (-not $taskSuccess) {
throw "Failed to create scheduled task"
}
}
Write-Host "`n" + ("=" * 70) -ForegroundColor Green
Write-Host " SETUP COMPLETED SUCCESSFULLY" -ForegroundColor Green
Write-Host ("=" * 70) -ForegroundColor Green
Write-Host ""
Write-Host " Next Steps:" -ForegroundColor Yellow
Write-Host " 1. Deploy packages will be placed in: $($Config.MonitorPath)" -ForegroundColor Gray
Write-Host " 2. Server will check for updates every $($Config.CheckIntervalMinutes) minutes" -ForegroundColor Gray
Write-Host " 3. New packages will be deployed automatically" -ForegroundColor Gray
Write-Host ""
Write-Host " Manual Testing:" -ForegroundColor Yellow
Write-Host " - Test: cd $($Config.ScriptsPath) && .\\Check-And-Deploy.ps1 -Interactive" -ForegroundColor Gray
Write-Host " - View Task: Get-ScheduledTask -TaskName 'ROA2WEB-AutoDeploy'" -ForegroundColor Gray
Write-Host ""
Write-Host ("=" * 70) -ForegroundColor Green
return $true
} catch {
Write-Host "`n" + ("=" * 70) -ForegroundColor Red
Write-Host " SETUP FAILED" -ForegroundColor Red
Write-Host ("=" * 70) -ForegroundColor Red
Write-Host ""
Write-Error $_
Write-Host ""
Write-Host ("=" * 70) -ForegroundColor Red
return $false
}
}
# =============================================================================
# MAIN EXECUTION FLOW
# =============================================================================
function Main {
# Non-interactive mode
if ($NonInteractive) {
$config = @{
MonitorPath = $MonitorPath
ScriptsPath = $ScriptsPath
CheckIntervalMinutes = $CheckIntervalMinutes
AutoDeploy = $AutoDeploy
CreateScheduledTask = $CreateScheduledTask
}
Invoke-Setup -Config $config | Out-Null
return
}
# Interactive mode - Wizard
Show-WizardWelcome
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
Write-Host " Configuration Wizard" -ForegroundColor Cyan
Write-Host ("=" * 70) -ForegroundColor Cyan
# Step 1: Monitor Path
$monitorPath = Get-WizardInput -Prompt "[1/5] Deploy Folder Path`n Path to monitor for new deployment packages:" -Default $MonitorPath
# Step 2: Scripts Path
$scriptsPath = Get-WizardInput -Prompt "[2/5] Scripts Folder Path`n Path to store monitoring scripts:" -Default $ScriptsPath
# Step 3: Check Interval
$checkInterval = Get-WizardInput -Prompt "[3/5] Check Interval`n How often to check for updates (minutes):" -Default $CheckIntervalMinutes -IsNumber
# Step 4: Auto-Deploy
Write-Host "`n[4/5] Auto-Deploy Enabled" -ForegroundColor Yellow
Write-Host " Deploy automatically when new package found?" -ForegroundColor Yellow
Write-Host " [Y] Yes (recommended) [N] No (manual trigger only)" -ForegroundColor Gray
$autoDeploy = Get-WizardInput -Prompt " Your choice:" -Default "Y" -IsYesNo
# Step 5: Scheduled Task
Write-Host "`n[5/5] Create Scheduled Task" -ForegroundColor Yellow
Write-Host " Create Windows Scheduled Task for automatic monitoring?" -ForegroundColor Yellow
Write-Host " [Y] Yes [N] No" -ForegroundColor Gray
$createTask = Get-WizardInput -Prompt " Your choice:" -Default "Y" -IsYesNo
# Configuration object
$config = @{
MonitorPath = $monitorPath
ScriptsPath = $scriptsPath
CheckIntervalMinutes = [int]$checkInterval
AutoDeploy = $autoDeploy
CreateScheduledTask = $createTask
}
# Show summary and confirm
$confirmed = Show-SetupSummary -Config $config
if ($confirmed) {
Invoke-Setup -Config $config
} else {
Write-Host "`nSetup cancelled by user" -ForegroundColor Yellow
exit 0
}
Write-Host "`nPress any key to exit..." -ForegroundColor Gray
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}
# Run main
Main

View File

@@ -0,0 +1,24 @@
{
"server": {
"host": "10.0.20.36",
"sshPort": 22122,
"sshUser": "Administrator",
"sshKeyPath": "~\\.ssh\\id_ed25519",
"windowsShare": "\\\\10.0.20.36\\Temp",
"deployPath": "C:\\Temp"
},
"build": {
"defaultComponent": "All",
"outputPath": "../deploy-package"
},
"transfer": {
"defaultMethod": "Auto",
"scpRemotePath": "C:/Temp",
"retryAttempts": 3,
"timeout": 300
},
"deployment": {
"autoDeployEnabled": true,
"checkIntervalMinutes": 5
}
}