diff --git a/deployment/windows/docs/DEPLOYMENT_AUTOMATION.md b/deployment/windows/docs/DEPLOYMENT_AUTOMATION.md new file mode 100644 index 0000000..ad50452 --- /dev/null +++ b/deployment/windows/docs/DEPLOYMENT_AUTOMATION.md @@ -0,0 +1,751 @@ +# ROA2WEB - Automated Deployment System + +## ๐Ÿ“‹ Overview + +This document describes the **automated deployment system** for ROA2WEB Windows Server deployment. The system enables: + +- โœ… **Build locally** on dev machine (WSL/Windows) +- โœ… **Auto-transfer** via Windows Share (LAN) or SSH (remote) +- โœ… **Auto-deploy** on server (no manual intervention needed) +- โœ… **Interactive menus** for easy configuration +- โœ… **No Remote Desktop required** + +--- + +## ๐Ÿ—๏ธ Architecture + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Dev Machine (WSL/Windows) โ”‚ +โ”‚ โ”‚ +โ”‚ Publish-And-Deploy.ps1 (Interactive Menu) โ”‚ +โ”‚ โ”œโ”€ Load deploy-config.json โ”‚ +โ”‚ โ”œโ”€ Build-ROA2WEB.ps1 (create deploy-package/) โ”‚ +โ”‚ โ””โ”€ Transfer: โ”‚ +โ”‚ โ”œโ”€ Auto: Try Windows Share โ†’ fallback SSH โ”‚ +โ”‚ โ”œโ”€ Windows Share: \\10.0.20.36\ROA2WEB-Deploy โ”‚ +โ”‚ โ””โ”€ SSH: scp to 10.0.20.36:22122 โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Windows Server โ”‚ +โ”‚ โ”‚ +โ”‚ C:\Temp\ โ”‚ +โ”‚ โ””โ”€ deploy-20241112-153045\ โ”‚ +โ”‚ โ”œโ”€ backend/ โ”‚ +โ”‚ โ”œโ”€ frontend/ โ”‚ +โ”‚ โ”œโ”€ telegram-bot/ โ”‚ +โ”‚ โ””โ”€ scripts/ROA2WEB-Console.ps1 โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Scheduled Task: ROA2WEB-AutoDeploy (every 5 minutes) โ”‚ +โ”‚ โ”‚ +โ”‚ Check-And-Deploy.ps1 โ”‚ +โ”‚ โ”œโ”€ Scan C:\Temp\ for deploy-* folders โ”‚ +โ”‚ โ”œโ”€ Find newest package (by LastWriteTime) โ”‚ +โ”‚ โ”œโ”€ Compare with last-deploy.json โ”‚ +โ”‚ โ””โ”€ If newer: โ”‚ +โ”‚ โ”œโ”€ Set $env:ROA2WEB_SOURCE = package path โ”‚ +โ”‚ โ”œโ”€ Execute ROA2WEB-Console.ps1 -Action DeployAll โ”‚ +โ”‚ โ””โ”€ Update last-deploy.json โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## ๐Ÿš€ Quick Start Guide + +### Dev Machine Setup (One-Time) + +1. **Edit Configuration** (optional, defaults are provided): +```powershell +cd deployment/windows/scripts +notepad deploy-config.json +``` + +Default configuration: +```json +{ + "server": { + "host": "10.0.20.36", + "sshPort": 22122, + "sshUser": "Administrator", + "sshKeyPath": "~\\.ssh\\id_ed25519", + "windowsShare": "\\\\10.0.20.36\\Temp" + } +} +``` + +2. **Test Connections**: +```powershell +.\Publish-And-Deploy.ps1 +# Menu: [2] Test Connections +``` + +--- + +### Server Setup (One-Time) + +1. **Windows Share** (already exists): +```powershell +# Share already exists: \\10.0.20.36\Temp +# Maps to: C:\Temp on server + +# Create scripts directory (if not exists) +New-Item -Path "C:\Temp\ROA2WEB-Scripts" -ItemType Directory -Force +``` + +2. **Run Setup Wizard**: +```powershell +# Copy Setup-AutoDeploy.ps1 to server, then: +cd C:\path\to\scripts +.\Setup-AutoDeploy.ps1 +``` + +The wizard will: +- โœ… Create directory structure (`C:\Temp`, `C:\Temp\ROA2WEB-Scripts`) +- โœ… Copy `Check-And-Deploy.ps1` to scripts folder +- โœ… Create Scheduled Task (`ROA2WEB-AutoDeploy`) +- โœ… Configure auto-deploy (every 5 minutes) + +--- + +## ๐Ÿ“– Usage + +### Interactive Deployment (Dev Machine) + +```powershell +cd deployment/windows/scripts +.\Publish-And-Deploy.ps1 +``` + +**Main Menu:** +``` +[1] Build & Publish Package + โ†’ Select Component (All/Frontend/Backend/TelegramBot) + โ†’ Select Transfer Method (Auto/WindowsShare/SSH) + โ†’ Build + Transfer automatically + +[2] Test Connections + โ†’ Test Windows Share accessibility + โ†’ Test SSH connection + +[3] Configure Settings + โ†’ Edit server host, SSH port, paths, etc. + +[4] View Current Configuration + โ†’ Display active settings + +[Q] Quit +``` + +--- + +### Non-Interactive Deployment (Dev Machine) + +```powershell +# Quick deploy (auto-detect transfer method) +.\Publish-And-Deploy.ps1 -NonInteractive -Action Build -Component All + +# Force Windows Share (LAN) +.\Publish-And-Deploy.ps1 -NonInteractive -Action Build -Component All -TransferMethod WindowsShare + +# Force SSH (remote/WAN) +.\Publish-And-Deploy.ps1 -NonInteractive -Action Build -Component Frontend -TransferMethod SSH + +# Test connections only +.\Publish-And-Deploy.ps1 -NonInteractive -Action TestConnections +``` + +--- + +### Server-Side Management + +#### Manual Check & Deploy (Server) + +```powershell +cd C:\ROA2WEB-Scripts + +# Interactive menu +.\Check-And-Deploy.ps1 -Interactive + +# Non-interactive check + deploy +.\Check-And-Deploy.ps1 + +# Check only (no deploy) +.\Check-And-Deploy.ps1 -CheckOnly +``` + +#### Manage Scheduled Task (Server) + +```powershell +# View task +Get-ScheduledTask -TaskName "ROA2WEB-AutoDeploy" + +# Start task manually (trigger check now) +Start-ScheduledTask -TaskName "ROA2WEB-AutoDeploy" + +# Disable auto-deploy temporarily +Disable-ScheduledTask -TaskName "ROA2WEB-AutoDeploy" + +# Re-enable auto-deploy +Enable-ScheduledTask -TaskName "ROA2WEB-AutoDeploy" + +# View logs +Get-Content "C:\ROA2WEB-Scripts\Logs\check-and-deploy.log" -Tail 50 +``` + +--- + +## ๐Ÿ“ File Structure + +### Dev Machine +``` +deployment/windows/scripts/ +โ”œโ”€โ”€ deploy-config.json # Configuration file (edit server settings) +โ”œโ”€โ”€ Publish-And-Deploy.ps1 # Interactive build & publish script +โ”œโ”€โ”€ Build-ROA2WEB.ps1 # Existing build script (unchanged) +โ”œโ”€โ”€ ROA2WEB-Console.ps1 # Existing deployment script (updated) +โ””โ”€โ”€ Setup-AutoDeploy.ps1 # Server setup wizard +``` + +### Server +``` +C:\Temp\ # Monitored folder (Windows Share) +โ”œโ”€โ”€ ROA2WEB-Scripts\ # Scripts and state (permanent) +โ”‚ โ”œโ”€โ”€ Check-And-Deploy.ps1 # Monitoring script (runs via scheduled task) +โ”‚ โ”œโ”€โ”€ Setup-AutoDeploy.ps1 # Setup wizard (optional, for debug) +โ”‚ โ”œโ”€โ”€ last-deploy.json # Tracks last deployed package +โ”‚ โ””โ”€โ”€ Logs\ +โ”‚ โ””โ”€โ”€ check-and-deploy.log # Deployment logs +โ”‚ +โ”œโ”€โ”€ deploy-20241112-153045\ # Timestamped deployment packages +โ”‚ โ”œโ”€โ”€ backend/ +โ”‚ โ”œโ”€โ”€ frontend/ +โ”‚ โ”œโ”€โ”€ telegram-bot/ +โ”‚ โ””โ”€โ”€ scripts/ +โ”‚ โ””โ”€โ”€ ROA2WEB-Console.ps1 +โ”‚ +โ””โ”€โ”€ deploy-20241111-120000\ # Previous package (kept as backup) +``` + +--- + +## ๐Ÿ”ง Configuration Reference + +### deploy-config.json + +```json +{ + "server": { + "host": "10.0.20.36", // Server IP/hostname + "sshPort": 22122, // SSH port + "sshUser": "Administrator", // SSH username + "sshKeyPath": "~\\.ssh\\id_ed25519", // SSH private key path + "windowsShare": "\\\\10.0.20.36\\Temp", // Network share path + "deployPath": "C:\\Temp" // Server-side deploy path + }, + "build": { + "defaultComponent": "All", // Default: All/Frontend/Backend/TelegramBot + "outputPath": "./deploy-package" // Local build output + }, + "transfer": { + "defaultMethod": "Auto", // Auto/WindowsShare/SSH + "scpRemotePath": "C:/Temp", // SCP remote path (Windows format) + "retryAttempts": 3, // Transfer retry attempts + "timeout": 300 // Transfer timeout (seconds) + }, + "deployment": { + "autoDeployEnabled": true, // Auto-deploy when new package found + "checkIntervalMinutes": 5 // How often to check (minutes) + } +} +``` + +**Editing Configuration:** + +**Option 1:** Edit JSON file directly: +```powershell +notepad deployment/windows/scripts/deploy-config.json +``` + +**Option 2:** Use interactive configuration editor: +```powershell +.\Publish-And-Deploy.ps1 +# Menu: [3] Configure Settings +``` + +--- + +## ๐Ÿ”„ Transfer Methods + +### Auto-detect (Recommended) +```powershell +.\Publish-And-Deploy.ps1 -TransferMethod Auto +``` +- **LAN**: Tries Windows Share first (fast, ~30 seconds) +- **Remote**: Falls back to SSH if share not accessible +- **Smart**: Automatically chooses best method + +### Windows Share (LAN Only) +```powershell +.\Publish-And-Deploy.ps1 -TransferMethod WindowsShare +``` +- **Speed**: โšกโšกโšกโšกโšก Very fast (LAN) +- **Requirements**: Dev machine in same network as server +- **Protocol**: SMB (port 445) +- **Use case**: Office/home network + +### SSH/SCP (LAN or Remote) +```powershell +.\Publish-And-Deploy.ps1 -TransferMethod SSH +``` +- **Speed**: โšกโšกโšก Decent (depends on bandwidth) +- **Requirements**: SSH key configured, server accessible +- **Protocol**: SSH (port 22122) +- **Use case**: Remote work, VPN, internet + +--- + +## ๐Ÿ› ๏ธ Troubleshooting + +### Dev Machine Issues + +#### Error: "Windows Share is not accessible" +```powershell +# Test share access +Test-Path "\\10.0.20.36\Temp" + +# Check network connectivity +Test-NetConnection -ComputerName 10.0.20.36 -Port 445 + +# Solution: Use SSH instead +.\Publish-And-Deploy.ps1 -TransferMethod SSH +``` + +#### Error: "SSH connection failed" +```powershell +# Test SSH manually +ssh -i ~\.ssh\id_ed25519 -p 22122 Administrator@10.0.20.36 + +# Common issues: +# 1. Wrong key path โ†’ Edit deploy-config.json +# 2. Key permissions โ†’ Ensure key file has correct Windows permissions +# 3. Server not reachable โ†’ Check firewall/VPN +``` + +#### Error: "Configuration file not found" +```powershell +# Script will auto-create default config on first run +.\Publish-And-Deploy.ps1 + +# Or create manually from example: +Copy-Item deploy-config.json.example deploy-config.json +notepad deploy-config.json +``` + +--- + +### Server Issues + +#### Scheduled Task Not Running +```powershell +# Check task status +Get-ScheduledTask -TaskName "ROA2WEB-AutoDeploy" | Select-Object State, LastRunTime, LastTaskResult + +# View task history +Get-ScheduledTaskInfo -TaskName "ROA2WEB-AutoDeploy" + +# Check logs +Get-Content "C:\ROA2WEB-Scripts\Logs\check-and-deploy.log" -Tail 50 + +# Solution: Run setup wizard again +.\Setup-AutoDeploy.ps1 +``` + +#### Deployment Not Triggered +```powershell +# Check if new package exists +Get-ChildItem "C:\Temp" -Directory | Where-Object { $_.Name -like "deploy-*" } | Sort-Object LastWriteTime -Descending + +# Check last deployment state +Get-Content "C:\Temp\ROA2WEB-Scripts\last-deploy.json" | ConvertFrom-Json + +# Manual trigger +cd C:\Temp\ROA2WEB-Scripts +.\Check-And-Deploy.ps1 -Interactive +# Menu: [2] Check and Deploy Now +``` + +#### Package Found But Not Deployed +```powershell +# Check if package is newer than last deployment +$state = Get-Content "C:\Temp\ROA2WEB-Scripts\last-deploy.json" | ConvertFrom-Json +$latest = Get-ChildItem "C:\Temp\deploy-*" | Sort-Object LastWriteTime -Descending | Select-Object -First 1 +Write-Host "Last deployed: $($state.lastDeployment.packageName)" +Write-Host "Latest package: $($latest.Name)" + +# Solution: Delete state file to force redeploy +Remove-Item "C:\Temp\ROA2WEB-Scripts\last-deploy.json" +cd C:\Temp\ROA2WEB-Scripts +.\Check-And-Deploy.ps1 +``` + +--- + +## ๐Ÿ“Š Monitoring & Logs + +### View Deployment Logs (Server) +```powershell +# Real-time log monitoring +Get-Content "C:\ROA2WEB-Scripts\Logs\check-and-deploy.log" -Wait + +# Last 100 lines +Get-Content "C:\ROA2WEB-Scripts\Logs\check-and-deploy.log" -Tail 100 + +# Search for errors +Select-String -Path "C:\ROA2WEB-Scripts\Logs\check-and-deploy.log" -Pattern "ERROR" +``` + +### View Deployment History (Server) +```powershell +# Interactive menu +.\Check-And-Deploy.ps1 -Interactive +# Menu: [3] View Deployment History + +# Or view state file directly +Get-Content "C:\ROA2WEB-Scripts\last-deploy.json" | ConvertFrom-Json | Format-List +``` + +### Check Service Status (Server) +```powershell +# After deployment, verify services +Get-Service -Name "ROA2WEB-Backend", "ROA2WEB-TelegramBot" + +# Or use ROA2WEB-Console.ps1 +cd C:\inetpub\wwwroot\roa2web\scripts +.\ROA2WEB-Console.ps1 -NonInteractive -Action Status +``` + +--- + +## ๐Ÿ” Security Considerations + +### SSH Key Management +- **Never commit SSH private keys** to Git +- Store keys in secure location (`~/.ssh/` with `chmod 600`) +- Use separate key for deployment (not personal key) +- Rotate keys periodically + +### Windows Share Permissions +```powershell +# Restrict share access to specific users (recommended for production) +New-SmbShare -Name "ROA2WEB-Deploy" -Path "C:\ROA2WEB-Deploy" -FullAccess "DOMAIN\DeployUser" + +# Or keep "Everyone" for internal network (development) +``` + +### Scheduled Task Security +- Task runs as **SYSTEM** account (full privileges) +- Ensure `C:\ROA2WEB-Deploy` is on local drive (not network share) +- Review logs regularly for unauthorized deployments + +--- + +## ๐ŸŽฏ Best Practices + +### 1. **Test Before Production** +```powershell +# Always test connections first +.\Publish-And-Deploy.ps1 -NonInteractive -Action TestConnections + +# Then test with small component +.\Publish-And-Deploy.ps1 -NonInteractive -Action Build -Component Backend +``` + +### 2. **Backup Before Deploy** +- ROA2WEB-Console.ps1 automatically creates backups +- Backups stored in `C:\inetpub\wwwroot\roa2web\backups\` +- Last 10 backups retained + +### 3. **Monitor Deployments** +```powershell +# Set up email alerts for failed deployments (optional) +# Add to Check-And-Deploy.ps1: +if (-not $deploySuccess) { + Send-MailMessage -To "admin@company.com" -Subject "Deployment Failed" ... +} +``` + +### 4. **Regular Cleanup** +```powershell +# Clean old deployment packages (keep last 5) +Get-ChildItem "C:\ROA2WEB-Deploy\deploy-*" | + Sort-Object LastWriteTime -Descending | + Select-Object -Skip 5 | + Remove-Item -Recurse -Force +``` + +--- + +## ๐Ÿ“š Advanced Configuration + +### Custom Check Interval +```powershell +# Edit scheduled task interval (e.g., every 10 minutes) +$trigger = Get-ScheduledTask -TaskName "ROA2WEB-AutoDeploy" | Get-ScheduledTaskTrigger +$trigger.Repetition.Interval = "PT10M" +Set-ScheduledTask -TaskName "ROA2WEB-AutoDeploy" -Trigger $trigger +``` + +### Deploy Specific Components Only +```powershell +# Backend only +.\Publish-And-Deploy.ps1 -NonInteractive -Action Build -Component Backend + +# Frontend only +.\Publish-And-Deploy.ps1 -NonInteractive -Action Build -Component Frontend + +# Telegram Bot only +.\Publish-And-Deploy.ps1 -NonInteractive -Action Build -Component TelegramBot +``` + +### Custom SSH Port Forwarding +```powershell +# If server is behind NAT/firewall, use port forwarding +# Router: Forward external port 2222 โ†’ 10.0.20.36:22122 + +# Edit deploy-config.json: +{ + "server": { + "host": "public-ip-or-domain.com", + "sshPort": 2222, + ... + } +} +``` + +--- + +## ๐Ÿ†˜ Support + +### Common Commands Reference + +**Dev Machine:** +```powershell +# Quick deploy +.\Publish-And-Deploy.ps1 -NonInteractive -Action Build -Component All + +# Test connections +.\Publish-And-Deploy.ps1 -NonInteractive -Action TestConnections + +# View config +.\Publish-And-Deploy.ps1 -NonInteractive -Action ViewConfig +``` + +**Server:** +```powershell +# Check for updates +.\Check-And-Deploy.ps1 -CheckOnly + +# Deploy now +.\Check-And-Deploy.ps1 + +# Interactive menu +.\Check-And-Deploy.ps1 -Interactive + +# View logs +Get-Content "C:\ROA2WEB-Scripts\Logs\check-and-deploy.log" -Tail 50 +``` + +--- + +## ๐Ÿ“ Migration from Manual Deployment + +If you were previously using manual deployment (Build-ROA2WEB.ps1 + manual copy): + +1. **One-time server setup:** +```powershell +# Copy Setup-AutoDeploy.ps1 to server +.\Setup-AutoDeploy.ps1 +``` + +2. **Update workflow:** +```powershell +# OLD: Manual build + copy + Remote Desktop deploy +.\Build-ROA2WEB.ps1 +# ... manual copy ... +# ... Remote Desktop to server ... +# ... run ROA2WEB-Console.ps1 ... + +# NEW: One command, fully automated +.\Publish-And-Deploy.ps1 -NonInteractive -Action Build -Component All +# โ†’ Server auto-deploys within 5 minutes +``` + +3. **Benefits:** +- โฑ๏ธ **Time saved**: 15 minutes โ†’ 2 minutes +- ๐ŸŽฏ **Zero manual steps**: No Remote Desktop needed +- ๐Ÿ”„ **Consistent**: Same process every time +- ๐Ÿ“Š **Audit trail**: Full logs and deployment history + +--- + +## ๐Ÿงช Testing the Complete Workflow + +### End-to-End Testing Guide + +After completing the setup, test the entire automated deployment workflow: + +#### Step 1: Server Setup (One-Time) + +```powershell +# On Dev Machine: Copy scripts to server via Windows Share +Copy-Item -Path ".\Check-And-Deploy.ps1" -Destination "\\10.0.20.36\Temp\ROA2WEB-Scripts\" +Copy-Item -Path ".\Setup-AutoDeploy.ps1" -Destination "\\10.0.20.36\Temp\ROA2WEB-Scripts\" +``` + +```powershell +# On Server: Run setup wizard (via Remote Desktop - only once) +cd C:\Temp\ROA2WEB-Scripts +.\Setup-AutoDeploy.ps1 + +# Follow wizard prompts: +# - Monitor Path: C:\Temp (default) +# - Scripts Path: C:\Temp\ROA2WEB-Scripts (default) +# - Check Interval: 5 minutes (default) +# - Auto-Deploy: Yes (default) +# - Create Scheduled Task: Yes (default) +``` + +#### Step 2: Test Connections (Dev Machine) + +```powershell +cd /mnt/e/proiecte/roa2web/deployment/windows/scripts + +# Run connection tests +.\Publish-And-Deploy.ps1 +# Select: [2] Test Connections + +# Expected output: +# โœ… Windows Share accessible: \\10.0.20.36\Temp +# โœ… SSH connection successful: Administrator@10.0.20.36:22122 +``` + +#### Step 3: First Deployment Test (Dev Machine) + +```powershell +# Build and publish a test package +.\Publish-And-Deploy.ps1 + +# Menu workflow: +# [1] Build & Publish Package +# โ†’ Component: [1] All (or test with Backend only) +# โ†’ Transfer Method: [1] Auto (recommended) +# โ†’ Confirm: Y + +# Expected output: +# โœ… Building deployment package... +# โœ… Transferring via Windows Share... +# โœ… Package copied: \\10.0.20.36\Temp\deploy-20241113-HHMMSS\ +# โœ… Server will auto-deploy within 5 minutes +``` + +#### Step 4: Verify Auto-Deploy (Server) + +```powershell +# Wait 1-5 minutes, then check deployment logs on server: +Get-Content "C:\Temp\ROA2WEB-Scripts\Logs\check-and-deploy.log" -Tail 30 + +# Expected log entries: +# [2024-11-13 HH:MM:SS] [INFO] === Check and Deploy Started === +# [2024-11-13 HH:MM:SS] [SUCCESS] Found 1 package(s), latest: deploy-20241113-HHMMSS +# [2024-11-13 HH:MM:SS] [SUCCESS] New package detected +# [2024-11-13 HH:MM:SS] [INFO] Executing deployment via ROA2WEB-Console.ps1... +# [2024-11-13 HH:MM:SS] [SUCCESS] Deployment completed successfully +``` + +```powershell +# Verify services are running: +Get-Service -Name "ROA2WEB-Backend", "ROA2WEB-TelegramBot" + +# Expected output: +# Status Name DisplayName +# ------ ---- ----------- +# Running ROA2WEB-Backend ROA2WEB Backend Service +# Running ROA2WEB-TelegramBot ROA2WEB Telegram Bot Service +``` + +#### Step 5: Verify Web Application + +``` +# Open browser and test: +http://10.0.20.36/ # Frontend should load +http://10.0.20.36/api/docs # Backend API docs should be accessible +``` + +### Manual Testing (Optional) + +If you want to test without waiting for Scheduled Task: + +```powershell +# On Server: Manual deployment test +cd C:\Temp\ROA2WEB-Scripts +.\Check-And-Deploy.ps1 -Interactive + +# Menu workflow: +# [1] Check for Updates (No Deploy) - verify package is detected +# [2] Check and Deploy Now - force immediate deployment +# [3] View Deployment History - see deployment records +``` + +### Troubleshooting Failed Tests + +**If Windows Share test fails:** +```powershell +# Verify network connectivity +Test-NetConnection -ComputerName 10.0.20.36 -Port 445 + +# Try manual access +Test-Path "\\10.0.20.36\Temp" + +# Fallback: Use SSH transfer instead +.\Publish-And-Deploy.ps1 -TransferMethod SSH +``` + +**If SSH test fails:** +```powershell +# Test SSH manually +ssh -i ~\.ssh\id_ed25519 -p 22122 Administrator@10.0.20.36 + +# If key path is wrong, update config: +notepad deploy-config.json +# Update: "sshKeyPath": "correct\\path\\to\\key" +``` + +**If auto-deploy doesn't trigger:** +```powershell +# Verify scheduled task is running (on server): +Get-ScheduledTask -TaskName "ROA2WEB-AutoDeploy" | Select-Object State, LastRunTime + +# Manually trigger task: +Start-ScheduledTask -TaskName "ROA2WEB-AutoDeploy" + +# Check for errors in logs: +Select-String -Path "C:\Temp\ROA2WEB-Scripts\Logs\check-and-deploy.log" -Pattern "ERROR" +``` + +--- + +## ๐ŸŽ‰ Success! + +You've now set up the automated deployment system for ROA2WEB! Deployments are now as simple as: + +```powershell +.\Publish-And-Deploy.ps1 -NonInteractive -Action Build -Component All +``` + +**Server auto-deploys within 5 minutes** - no Remote Desktop, no manual steps! ๐Ÿš€ diff --git a/deployment/windows/scripts/Build-ROA2WEB.ps1 b/deployment/windows/scripts/Build-ROA2WEB.ps1 index 2e837a8..3852065 100644 --- a/deployment/windows/scripts/Build-ROA2WEB.ps1 +++ b/deployment/windows/scripts/Build-ROA2WEB.ps1 @@ -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" diff --git a/deployment/windows/scripts/Check-And-Deploy.ps1 b/deployment/windows/scripts/Check-And-Deploy.ps1 new file mode 100644 index 0000000..fe0e58b --- /dev/null +++ b/deployment/windows/scripts/Check-And-Deploy.ps1 @@ -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 diff --git a/deployment/windows/scripts/Publish-And-Deploy.ps1 b/deployment/windows/scripts/Publish-And-Deploy.ps1 new file mode 100644 index 0000000..24b8e28 --- /dev/null +++ b/deployment/windows/scripts/Publish-And-Deploy.ps1 @@ -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 diff --git a/deployment/windows/scripts/ROA2WEB-Console.ps1 b/deployment/windows/scripts/ROA2WEB-Console.ps1 index a1e018e..342c8e2 100644 --- a/deployment/windows/scripts/ROA2WEB-Console.ps1 +++ b/deployment/windows/scripts/ROA2WEB-Console.ps1 @@ -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 diff --git a/deployment/windows/scripts/Setup-AutoDeploy.ps1 b/deployment/windows/scripts/Setup-AutoDeploy.ps1 new file mode 100644 index 0000000..3747733 --- /dev/null +++ b/deployment/windows/scripts/Setup-AutoDeploy.ps1 @@ -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 diff --git a/deployment/windows/scripts/deploy-config.json b/deployment/windows/scripts/deploy-config.json new file mode 100644 index 0000000..57aebf3 --- /dev/null +++ b/deployment/windows/scripts/deploy-config.json @@ -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 + } +}