<# .SYNOPSIS Build ROA2WEB Telegram Bot for Windows Server Deployment .DESCRIPTION This script creates a deployment package for the Telegram bot: - Copies application source files (app/) - Copies requirements.txt - Copies PowerShell deployment scripts - Creates .env.example template - Creates deployment README - Excludes development files (venv, data, logs, etc.) - Optionally transfers to remote server .PARAMETER SourcePath Path to telegram-bot source (default: ../../reports-app/telegram-bot) .PARAMETER OutputPath Output path for deployment package (default: ../deploy-package/telegram-bot) .PARAMETER ServerHost Remote server hostname/IP for automatic deployment (optional) .PARAMETER ServerPath Remote server path for automatic deployment (optional) .PARAMETER Clean Clean output directory before building (default: true) .EXAMPLE .\Build-TelegramBot.ps1 Build with defaults .EXAMPLE .\Build-TelegramBot.ps1 -OutputPath "D:\deployments\telegram-bot-$(Get-Date -Format 'yyyyMMdd')" Build to custom output path .EXAMPLE .\Build-TelegramBot.ps1 -ServerHost "10.0.20.36" -ServerPath "C:\Temp\telegram-bot-deploy" Build and transfer to remote server .NOTES Author: ROA2WEB Team Requires: PowerShell 5.1+ Can run on: WSL, Windows, Linux #> [CmdletBinding()] param( [string]$SourcePath = "../../../reports-app/telegram-bot", [string]$OutputPath = "../deploy-package/telegram-bot", [string]$ServerHost = "", [string]$ServerPath = "", [bool]$Clean = $true ) $ErrorActionPreference = "Stop" # ============================================================================= # HELPER FUNCTIONS # ============================================================================= function Write-Step { param([string]$Message) Write-Host "`n[*] $Message" -ForegroundColor Cyan } function Write-Success { param([string]$Message) Write-Host " [OK] $Message" -ForegroundColor Green } function Write-Error { param([string]$Message) Write-Host " [ERROR] $Message" -ForegroundColor Red } function Write-Warning { param([string]$Message) Write-Host " [WARN] $Message" -ForegroundColor Yellow } function Resolve-FullPath { param([string]$Path) $scriptDir = $PSScriptRoot $fullPath = Join-Path $scriptDir $Path # Convert to absolute path and resolve .. and . properly $fullPath = [System.IO.Path]::GetFullPath($fullPath) return $fullPath } function Test-SourceDirectory { param([string]$Path) Write-Step "Validating source directory..." if (-not (Test-Path $Path)) { throw "Source path not found: $Path" } # Check for required files/directories $requiredPaths = @( (Join-Path $Path "app"), (Join-Path $Path "requirements.txt") ) foreach ($reqPath in $requiredPaths) { if (-not (Test-Path $reqPath)) { throw "Required path not found: $reqPath" } } Write-Success "Source directory validated: $Path" } function New-CleanOutputDirectory { param([string]$Path) if ($Clean -and (Test-Path $Path)) { Write-Step "Cleaning output directory..." Remove-Item -Path $Path -Recurse -Force Write-Success "Output directory cleaned" } if (-not (Test-Path $Path)) { New-Item -ItemType Directory -Path $Path -Force | Out-Null Write-Success "Created output directory: $Path" } } function Copy-ApplicationFiles { param( [string]$SourcePath, [string]$DestPath ) Write-Step "Copying application files..." # Exclude patterns $excludeDirs = @( "venv", "data", "logs", "temp", "backups", "__pycache__", ".pytest_cache", "tests", ".git" ) $excludeFiles = @( ".env", "*.pyc", "*.pyo", "*.log", "*.db", ".DS_Store", "Thumbs.db" ) # Create app directory in destination $destApp = Join-Path $DestPath "app" New-Item -ItemType Directory -Path $destApp -Force | Out-Null # Copy app/ directory $sourceApp = Join-Path $SourcePath "app" $fileCount = 0 Get-ChildItem -Path $sourceApp -Recurse | ForEach-Object { # Check if in excluded directory $inExcludedDir = $false foreach ($excludeDir in $excludeDirs) { if ($_.FullName -like "*\$excludeDir\*" -or $_.FullName -like "*/$excludeDir/*") { $inExcludedDir = $true break } } if ($inExcludedDir) { return } # Check if excluded file $isExcludedFile = $false foreach ($pattern in $excludeFiles) { if ($_.Name -like $pattern) { $isExcludedFile = $true break } } if ($isExcludedFile) { return } # Calculate relative path and destination $relativePath = $_.FullName.Substring($sourceApp.Length) $destFile = Join-Path $destApp $relativePath if ($_.PSIsContainer) { # Create directory if (-not (Test-Path $destFile)) { New-Item -ItemType Directory -Path $destFile -Force | Out-Null } } else { # Copy file $destFileDir = Split-Path $destFile -Parent if (-not (Test-Path $destFileDir)) { New-Item -ItemType Directory -Path $destFileDir -Force | Out-Null } Copy-Item -Path $_.FullName -Destination $destFile -Force $fileCount++ } } Write-Success "Copied $fileCount application files" } function Copy-RequirementsFile { param( [string]$SourcePath, [string]$DestPath ) Write-Step "Copying requirements.txt..." $sourceReq = Join-Path $SourcePath "requirements.txt" $destReq = Join-Path $DestPath "requirements.txt" if (Test-Path $sourceReq) { Copy-Item -Path $sourceReq -Destination $destReq -Force Write-Success "requirements.txt copied" } else { Write-Warning "requirements.txt not found in source" } } function New-EnvironmentTemplate { param([string]$DestPath) Write-Step "Creating .env.example template..." $envTemplate = @" # ROA2WEB Telegram Bot - Production Configuration Template # Telegram Bot Configuration TELEGRAM_BOT_TOKEN=your_production_bot_token_from_@BotFather # Claude API Configuration CLAUDE_API_KEY=your_production_claude_api_key_from_anthropic_console # Backend API Configuration BACKEND_URL=http://localhost:8000 BACKEND_TIMEOUT=30 # SQLite Database Configuration SQLITE_DB_PATH=C:\inetpub\wwwroot\roa2web\telegram-bot\data\telegram_bot.db # Internal API Configuration (for backend callbacks) INTERNAL_API_HOST=127.0.0.1 INTERNAL_API_PORT=8002 # Logging Configuration LOG_LEVEL=INFO LOG_FILE=C:\inetpub\wwwroot\roa2web\telegram-bot\logs\bot.log # Environment ENVIRONMENT=production # Authentication Configuration AUTH_CODE_EXPIRY_MINUTES=15 JWT_REFRESH_THRESHOLD_MINUTES=5 # Session Configuration SESSION_TIMEOUT_MINUTES=60 MAX_CONVERSATION_HISTORY=20 "@ $envPath = Join-Path $DestPath ".env.example" Set-Content -Path $envPath -Value $envTemplate -Encoding UTF8 Write-Success ".env.example template created" } function Copy-DeploymentScripts { param( [string]$SourceScriptsPath, [string]$DestPath ) Write-Step "Copying deployment scripts..." $scriptsDir = Join-Path $DestPath "scripts" New-Item -ItemType Directory -Path $scriptsDir -Force | Out-Null # List of scripts to copy $scripts = @( "Install-TelegramBot.ps1", "Deploy-TelegramBot.ps1", "Start-TelegramBot.ps1", "Stop-TelegramBot.ps1", "Restart-TelegramBot.ps1", "Backup-TelegramDB.ps1", "Setup-DailyBackup.ps1", "Setup-ClaudeAuth.ps1" ) $copiedCount = 0 foreach ($script in $scripts) { $sourcePath = Join-Path $SourceScriptsPath $script if (Test-Path $sourcePath) { $destScript = Join-Path $scriptsDir $script Copy-Item -Path $sourcePath -Destination $destScript -Force $copiedCount++ } else { Write-Warning "Script not found: $script" } } Write-Success "Copied $copiedCount deployment scripts" } function Copy-ConfigTemplates { param( [string]$SourceConfigPath, [string]$DestPath ) Write-Step "Copying configuration templates..." $configDir = Join-Path $DestPath "config" New-Item -ItemType Directory -Path $configDir -Force | Out-Null # Copy .env.production.windows.telegram if it exists $prodEnv = Join-Path $SourceConfigPath ".env.production.windows.telegram" if (Test-Path $prodEnv) { Copy-Item -Path $prodEnv -Destination (Join-Path $configDir ".env.production.windows.telegram") -Force Write-Success "Production config template copied" } } function New-DeploymentReadme { param([string]$DestPath) Write-Step "Creating deployment README..." $readme = @" # ROA2WEB Telegram Bot - Deployment Package **Created**: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss") **Version**: Production Deployment Package ## Contents - ``app/`` - Telegram bot application source code - ``scripts/`` - PowerShell deployment and management scripts - ``config/`` - Configuration templates - ``requirements.txt`` - Python dependencies - ``.env.example`` - Environment configuration template - ``README.txt`` - This file ## Deployment Instructions ### 1. Initial Installation Run as Administrator on Windows Server: ```powershell cd scripts .\Install-TelegramBot.ps1 ``` This will: - Check Python 3.11+ installation - Install NSSM (service manager) - Create directory structure - Create virtual environment - Install Python dependencies - Create Windows Service (ROA2WEB-TelegramBot) - Create configuration template ### 2. Configuration Edit the ``.env`` file: ```powershell notepad C:\inetpub\wwwroot\roa2web\telegram-bot\.env ``` Required settings: - ``TELEGRAM_BOT_TOKEN`` - Get from @BotFather on Telegram - ``CLAUDE_API_KEY`` - Get from Anthropic console - ``BACKEND_URL`` - Usually http://localhost:8000 ### 3. Start Service ```powershell cd C:\inetpub\wwwroot\roa2web\telegram-bot\scripts .\Start-TelegramBot.ps1 ``` ### 4. Verify Deployment Check health endpoint: ```powershell Invoke-WebRequest http://localhost:8002/internal/health ``` View logs: ```powershell Get-Content C:\inetpub\wwwroot\roa2web\telegram-bot\logs\stdout.log -Tail 50 -Wait ``` ## Management Commands - **Start**: ``.\Start-TelegramBot.ps1`` - **Stop**: ``.\Stop-TelegramBot.ps1`` - **Restart**: ``.\Restart-TelegramBot.ps1`` - **Deploy Update**: ``.\Deploy-TelegramBot.ps1 -SourcePath "path\to\new\package"`` - **Backup Database**: ``.\Backup-TelegramDB.ps1`` - **Setup Daily Backup**: ``.\Setup-DailyBackup.ps1`` ## Directory Structure ``` C:\inetpub\wwwroot\roa2web\telegram-bot\ ├── app\ # Application source code ├── venv\ # Python virtual environment ├── data\ # SQLite database (telegram_bot.db) ├── logs\ # Application logs ├── backups\ # Database backups ├── temp\ # Temporary files ├── scripts\ # Management scripts ├── config\ # Configuration templates ├── requirements.txt # Python dependencies └── .env # Environment configuration ``` ## Troubleshooting ### Service won't start Check logs: ```powershell Get-Content C:\inetpub\wwwroot\roa2web\telegram-bot\logs\stderr.log -Tail 100 ``` ### Bot not responding on Telegram 1. Verify service is running: ``Get-Service ROA2WEB-TelegramBot`` 2. Check health endpoint: ``Invoke-WebRequest http://localhost:8002/internal/health`` 3. Verify ``.env`` configuration (TELEGRAM_BOT_TOKEN) 4. Check logs for errors ### Database errors Run database backup and check integrity: ```powershell .\Backup-TelegramDB.ps1 ``` ## Support - Documentation: ``C:\inetpub\wwwroot\roa2web\deployment\windows\docs\TELEGRAM_BOT_DEPLOYMENT.md`` - Project repository: ROA2WEB on GitHub - Contact: ROA2WEB Team "@ $readmePath = Join-Path $DestPath "README.txt" Set-Content -Path $readmePath -Value $readme -Encoding UTF8 Write-Success "Deployment README created" } function Copy-ClaudeCredentials { param([string]$PackagePath) Write-Host "`n" + ("=" * 60) -ForegroundColor Yellow Write-Host " OPTIONAL: Claude Credentials" -ForegroundColor Yellow Write-Host ("=" * 60) -ForegroundColor Yellow Write-Host "" Write-Host "If you have Claude Pro/Max credentials from 'claude-code login'," Write-Host "you can include them in the deployment package for easy setup." Write-Host "" $response = Read-Host "Copy Claude credentials to deployment package? (Y/N)" if ($response -eq "Y" -or $response -eq "y") { # Try to find credentials automatically in both possible locations $possiblePaths = @( (Join-Path $env:USERPROFILE ".claude\.credentials.json"), # Correct location (Join-Path $env:APPDATA "claude\credentials.json") # Alternative location ) $defaultCredPath = $null foreach ($path in $possiblePaths) { if (Test-Path $path) { $defaultCredPath = $path break } } if ($defaultCredPath) { Write-Host "`nFound credentials at: $defaultCredPath" -ForegroundColor Green $usePath = Read-Host "Use this path? (Y/N)" if ($usePath -eq "Y" -or $usePath -eq "y") { $credPath = $defaultCredPath } else { $credPath = Read-Host "Enter path to credentials.json" } } else { Write-Host "`nCredentials not found at default locations" -ForegroundColor Yellow Write-Host " Checked: $($possiblePaths -join ', ')" -ForegroundColor Gray $credPath = Read-Host "Enter full path to credentials.json" } if (Test-Path $credPath) { try { $destCredPath = Join-Path $PackagePath "claude-credentials.json" Copy-Item -Path $credPath -Destination $destCredPath -Force Write-Success "Claude credentials copied to deployment package" Write-Host " Location: $destCredPath" -ForegroundColor Gray Write-Host " The Setup-ClaudeAuth.ps1 script will detect and use this file automatically" -ForegroundColor Gray return $true } catch { Write-Warning "Failed to copy credentials: $_" return $false } } else { Write-Warning "Credentials file not found at: $credPath" return $false } } else { Write-Host "Skipping credentials copy. You can set up Claude auth manually on the server." -ForegroundColor Gray return $false } } function Transfer-ToServerSSH { param([string]$PackagePath) Write-Host "`n" + ("=" * 60) -ForegroundColor Yellow Write-Host " OPTIONAL: SSH Transfer to Server" -ForegroundColor Yellow Write-Host ("=" * 60) -ForegroundColor Yellow Write-Host "" Write-Host "You can automatically transfer the deployment package to" Write-Host "the Windows Server via SSH/SCP (requires SSH server on Windows)." Write-Host "" $response = Read-Host "Transfer package to server via SSH? (Y/N)" if ($response -eq "Y" -or $response -eq "y") { Write-Host "" $sshUser = Read-Host "Enter SSH username (e.g., Administrator)" $sshHost = Read-Host "Enter server hostname/IP (e.g., 10.0.20.36)" $sshPort = Read-Host "Enter SSH port (default: 22, press Enter for default)" if ([string]::IsNullOrWhiteSpace($sshPort)) { $sshPort = "22" } $remotePath = Read-Host "Enter remote path (e.g., C:/Temp/telegram-bot-deploy)" # Convert Windows path to SCP format if needed $remotePath = $remotePath -replace '\\', '/' if ($remotePath -match '^[A-Za-z]:') { # Convert C:/path to /c/path format for SCP $remotePath = $remotePath -replace '^([A-Za-z]):', '/$1' } Write-Host "`nTransfer Configuration:" -ForegroundColor Cyan Write-Host " Source: $PackagePath" Write-Host " Target: ${sshUser}@${sshHost}:${remotePath}" Write-Host " Port: $sshPort" Write-Host "" $confirm = Read-Host "Proceed with transfer? (Y/N)" if ($confirm -eq "Y" -or $confirm -eq "y") { Write-Step "Transferring package via SCP..." try { # Use SCP to transfer $scpTarget = "${sshUser}@${sshHost}:${remotePath}" # Build SCP command $scpArgs = @( "-P", $sshPort, "-r", $PackagePath, $scpTarget ) Write-Host " Running: scp $scpArgs" -ForegroundColor Gray & scp @scpArgs if ($LASTEXITCODE -eq 0) { Write-Success "Package transferred successfully!" Write-Host " Remote location: $scpTarget" -ForegroundColor Gray return $true } else { Write-Warning "SCP transfer failed with exit code: $LASTEXITCODE" Write-Host " You can transfer manually via RDP or network share" -ForegroundColor Yellow return $false } } catch { Write-Warning "Transfer failed: $_" Write-Host " Make sure 'scp' is available in PATH" -ForegroundColor Yellow Write-Host " Alternative: Use WinSCP, FileZilla, or manual RDP copy" -ForegroundColor Yellow return $false } } else { Write-Host "Transfer cancelled" -ForegroundColor Gray return $false } } else { Write-Host "Skipping SSH transfer. Transfer package manually via RDP or network share." -ForegroundColor Gray return $false } } function Show-PackageSummary { param( [string]$PackagePath, [bool]$CredentialsCopied, [bool]$TransferredToServer ) Write-Host "`n" + ("=" * 80) -ForegroundColor Cyan Write-Host " DEPLOYMENT PACKAGE CREATED SUCCESSFULLY" -ForegroundColor Green Write-Host ("=" * 80) -ForegroundColor Cyan Write-Host "`nPackage Location:" -ForegroundColor Yellow Write-Host " $PackagePath" # Calculate package size $files = Get-ChildItem -Path $PackagePath -Recurse -File $totalSize = ($files | Measure-Object -Property Length -Sum).Sum / 1MB Write-Host "`nPackage Contents:" -ForegroundColor Yellow Write-Host " Files: $($files.Count)" Write-Host " Total Size: $([math]::Round($totalSize, 2)) MB" if ($CredentialsCopied) { Write-Host " ✓ Claude credentials included" -ForegroundColor Green } if ($TransferredToServer) { Write-Host "`nDeployment Status:" -ForegroundColor Yellow Write-Host " ✓ Package transferred to server via SSH" -ForegroundColor Green Write-Host "" Write-Host "Next Steps on Server:" -ForegroundColor Yellow Write-Host " 1. SSH to server or RDP" Write-Host " 2. Navigate to deployment location" Write-Host " 3. Run: scripts\Install-TelegramBot.ps1 (as Administrator)" Write-Host " 4. Run: scripts\Setup-ClaudeAuth.ps1 (will auto-detect credentials)" Write-Host " 5. Configure: .env file with Telegram bot token" Write-Host " 6. Run: scripts\Start-TelegramBot.ps1" } else { Write-Host "`nNext Steps:" -ForegroundColor Yellow Write-Host " 1. Transfer package to Windows Server (10.0.20.36)" Write-Host " - Via network share: Copy-Item -Path $PackagePath -Destination \\10.0.20.36\C$\Temp\telegram-bot-deploy -Recurse" Write-Host " - Via RDP: Manual copy" Write-Host " 2. On server, run: scripts\Install-TelegramBot.ps1 (as Administrator)" Write-Host " 3. Configure: .env file with production credentials" Write-Host " 4. Run: scripts\Start-TelegramBot.ps1" } Write-Host "`n" + ("=" * 80) -ForegroundColor Cyan } function Transfer-ToServer { param( [string]$PackagePath, [string]$ServerHost, [string]$ServerPath ) if (-not $ServerHost -or -not $ServerPath) { return } Write-Step "Transferring to server $ServerHost..." try { # Check if server is reachable if (Test-Connection -ComputerName $ServerHost -Count 1 -Quiet) { Write-Success "Server is reachable" } else { Write-Warning "Server is not reachable, skipping transfer" return } # Transfer files (using Copy-Item for network path or RoboCopy) $networkPath = "\\$ServerHost\$(($ServerPath -replace ':', '$'))" Write-Step "Copying to: $networkPath" # Ensure destination exists if (-not (Test-Path $networkPath)) { New-Item -ItemType Directory -Path $networkPath -Force | Out-Null } # Copy package Copy-Item -Path "$PackagePath\*" -Destination $networkPath -Recurse -Force Write-Success "Package transferred to server" } catch { Write-Warning "Failed to transfer to server: $_" Write-Host " You can manually copy the package from: $PackagePath" -ForegroundColor Yellow } } # ============================================================================= # MAIN BUILD FLOW # ============================================================================= function Main { Write-Host @" ==================================================================== ROA2WEB Telegram Bot - Build Deployment Package Creating production-ready deployment package ==================================================================== "@ -ForegroundColor Cyan # Resolve paths $sourcePath = Resolve-FullPath -Path $SourcePath $outputPath = Resolve-FullPath -Path $OutputPath $scriptsPath = $PSScriptRoot Write-Step "Build Configuration" Write-Host " Source: $sourcePath" -ForegroundColor Gray Write-Host " Output: $outputPath" -ForegroundColor Gray try { # Build steps Test-SourceDirectory -Path $sourcePath New-CleanOutputDirectory -Path $outputPath Copy-ApplicationFiles -SourcePath $sourcePath -DestPath $outputPath Copy-RequirementsFile -SourcePath $sourcePath -DestPath $outputPath New-EnvironmentTemplate -DestPath $outputPath Copy-DeploymentScripts -SourceScriptsPath $scriptsPath -DestPath $outputPath # Copy config templates if they exist $configPath = Join-Path (Split-Path $scriptsPath -Parent) "config" if (Test-Path $configPath) { Copy-ConfigTemplates -SourceConfigPath $configPath -DestPath $outputPath } New-DeploymentReadme -DestPath $outputPath # Interactive: Copy Claude credentials to package $credentialsCopied = Copy-ClaudeCredentials -PackagePath $outputPath # Interactive: Transfer to server via SSH $transferredToServer = $false if (-not ($ServerHost -and $ServerPath)) { # Interactive SSH transfer $transferredToServer = Transfer-ToServerSSH -PackagePath $outputPath } else { # Legacy non-interactive transfer (if parameters provided) Transfer-ToServer -PackagePath $outputPath -ServerHost $ServerHost -ServerPath $ServerPath $transferredToServer = $true } # Show summary with deployment status Show-PackageSummary -PackagePath $outputPath -CredentialsCopied $credentialsCopied -TransferredToServer $transferredToServer Write-Host "`nBuild completed successfully!" -ForegroundColor Green } catch { Write-Host "`n[BUILD FAILED] $_" -ForegroundColor Red Write-Host $_.ScriptStackTrace -ForegroundColor Red exit 1 } } # Run main build Main