<# .SYNOPSIS ROA2WEB Telegram Bot - Installation Script for Windows Server .DESCRIPTION This script performs complete installation of ROA2WEB Telegram Bot on Windows Server: - Checks prerequisites (Admin rights, Python) - Installs NSSM (service manager) if needed - Creates directory structure - Installs Python dependencies - Creates Windows Service for Telegram bot - Configures internal API - Starts service .PARAMETER InstallPath Installation path (default: C:\inetpub\wwwroot\roa2web\telegram-bot) .PARAMETER ServicePort Internal API service port (default: 8002) .PARAMETER SourcePath Source path for deployment package (auto-detected if run from scripts/ directory) .PARAMETER SkipPython Skip Python installation check (use existing Python) .EXAMPLE .\Install-TelegramBot.ps1 Standard installation with defaults (auto-detects source from scripts/ directory) .EXAMPLE .\Install-TelegramBot.ps1 -InstallPath "D:\Apps\roa2web\telegram-bot" -ServicePort 8003 Custom installation path and port .EXAMPLE .\Install-TelegramBot.ps1 -SourcePath "C:\Deploy\telegram-bot" Install from specific source path .NOTES Author: ROA2WEB Team Requires: PowerShell 5.1+, Administrator privileges, Python 3.11+ #> [CmdletBinding()] param( [string]$InstallPath = "C:\inetpub\wwwroot\roa2web\telegram-bot", [int]$ServicePort = 8002, [string]$SourcePath = "", [switch]$SkipPython ) # Strict error handling $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" # ============================================================================= # CONFIGURATION # ============================================================================= # Auto-detect source path: if running from scripts/ subdirectory, use parent $detectedSourcePath = if ($SourcePath) { $SourcePath } elseif ((Split-Path $PSScriptRoot -Leaf) -eq "scripts") { Split-Path $PSScriptRoot -Parent } else { $PSScriptRoot } $script:Config = @{ AppName = "ROA2WEB-TelegramBot" ServiceName = "ROA2WEB-TelegramBot" ServiceDisplayName = "ROA2WEB Telegram Bot Service" ServiceDescription = "Telegram bot frontend for ROA2WEB with Claude Agent SDK" InstallPath = $InstallPath DataPath = Join-Path $InstallPath "data" LogsPath = Join-Path $InstallPath "logs" TempPath = Join-Path $InstallPath "temp" BackupPath = Join-Path $InstallPath "backups" ServicePort = $ServicePort SourcePath = $detectedSourcePath } # ============================================================================= # 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 Test-Administrator { $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = [Security.Principal.WindowsPrincipal]$identity return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } function Test-CommandExists { param([string]$Command) try { if (Get-Command $Command -ErrorAction Stop) { return $true } } catch { return $false } } function Test-PythonInstallation { Write-Step "Checking Python installation..." if ($SkipPython) { Write-Warning "Skipping Python check (as requested)" return } try { $pythonCmd = Get-Command python -ErrorAction Stop $pythonVersionOutput = & python --version 2>&1 if ($pythonVersionOutput -match "Python (\d+\.\d+\.\d+)") { $installedVersion = $matches[1] $versionParts = $installedVersion -split '\.' $major = [int]$versionParts[0] $minor = [int]$versionParts[1] if ($major -ge 3 -and $minor -ge 11) { Write-Success "Python $installedVersion found at $($pythonCmd.Source)" return } else { throw "Python 3.11+ required, found $installedVersion" } } } catch { Write-Error "Python 3.11+ not found. Please install Python first." Write-Host " Download from: https://www.python.org/downloads/" -ForegroundColor Yellow throw "Python not installed" } } function Copy-ApplicationFiles { Write-Step "Copying application files from deployment package..." $sourceApp = Join-Path $Config.SourcePath "app" $sourceReq = Join-Path $Config.SourcePath "requirements.txt" $sourceScripts = Join-Path $Config.SourcePath "scripts" # Validate source files exist if (-not (Test-Path $sourceApp)) { Write-Warning "Source app directory not found: $sourceApp" Write-Warning "You may need to copy application files manually" return $false } if (-not (Test-Path $sourceReq)) { Write-Warning "Source requirements.txt not found: $sourceReq" Write-Warning "You may need to copy requirements.txt manually" return $false } try { # Copy app directory $destApp = Join-Path $Config.InstallPath "app" if (Test-Path $destApp) { Write-Warning "App directory already exists, removing..." Remove-Item -Path $destApp -Recurse -Force } Copy-Item -Path $sourceApp -Destination $destApp -Recurse -Force Write-Success "Application files copied" # Copy requirements.txt $destReq = Join-Path $Config.InstallPath "requirements.txt" Copy-Item -Path $sourceReq -Destination $destReq -Force Write-Success "requirements.txt copied" # Copy .env.example if exists (but don't overwrite .env) $sourceEnvExample = Join-Path $Config.SourcePath ".env.example" if (Test-Path $sourceEnvExample) { $destEnvExample = Join-Path $Config.InstallPath ".env.example" Copy-Item -Path $sourceEnvExample -Destination $destEnvExample -Force Write-Success ".env.example copied" } # Copy management scripts (but exclude installation/deployment scripts) if (Test-Path $sourceScripts) { $destScripts = Join-Path $Config.InstallPath "scripts" if (-not (Test-Path $destScripts)) { New-Item -ItemType Directory -Path $destScripts -Force | Out-Null } # List of management scripts to copy $managementScripts = @( "Start-TelegramBot.ps1", "Stop-TelegramBot.ps1", "Restart-TelegramBot.ps1", "Backup-TelegramDB.ps1", "Setup-DailyBackup.ps1", "Setup-ClaudeAuth.ps1" ) $copiedScriptsCount = 0 foreach ($script in $managementScripts) { $sourcePath = Join-Path $sourceScripts $script if (Test-Path $sourcePath) { $destPath = Join-Path $destScripts $script Copy-Item -Path $sourcePath -Destination $destPath -Force $copiedScriptsCount++ } } if ($copiedScriptsCount -gt 0) { Write-Success "Copied $copiedScriptsCount management scripts to scripts/ directory" } else { Write-Warning "No management scripts found to copy" } } else { Write-Warning "Source scripts directory not found: $sourceScripts" } return $true } catch { Write-Error "Failed to copy application files: $_" return $false } } function Install-NSSM { Write-Step "Installing NSSM (service manager)..." if (Test-Path "C:\nssm\nssm.exe") { Write-Success "NSSM already installed" return } # Check if Chocolatey is available if (Test-CommandExists "choco") { try { choco install nssm -y Write-Success "NSSM installed via Chocolatey" return } catch { Write-Warning "Chocolatey installation failed, trying direct download..." } } # Direct download as fallback try { $nssmUrl = "https://nssm.cc/release/nssm-2.24.zip" $nssmZip = "$env:TEMP\nssm.zip" $nssmExtract = "$env:TEMP\nssm" Write-Step "Downloading NSSM..." Invoke-WebRequest -Uri $nssmUrl -OutFile $nssmZip Write-Step "Extracting NSSM..." Expand-Archive -Path $nssmZip -DestinationPath $nssmExtract -Force # Copy nssm.exe to C:\nssm New-Item -ItemType Directory -Path "C:\nssm" -Force | Out-Null Copy-Item -Path "$nssmExtract\nssm-2.24\win64\nssm.exe" -Destination "C:\nssm\nssm.exe" -Force # Add to PATH $currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine") if ($currentPath -notlike "*C:\nssm*") { [Environment]::SetEnvironmentVariable("Path", "$currentPath;C:\nssm", "Machine") $env:Path += ";C:\nssm" } # Cleanup Remove-Item $nssmZip -Force Remove-Item $nssmExtract -Recurse -Force Write-Success "NSSM installed successfully" } catch { throw "Failed to install NSSM: $_" } } function New-DirectoryStructure { Write-Step "Creating directory structure..." $directories = @( $Config.InstallPath, (Join-Path $Config.InstallPath "app"), $Config.DataPath, $Config.LogsPath, $Config.TempPath, $Config.BackupPath ) foreach ($dir in $directories) { if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null Write-Success "Created: $dir" } else { Write-Success "Already exists: $dir" } } # Set permissions (service needs full access to data, logs, backups) try { $acl = Get-Acl $Config.InstallPath # Grant SYSTEM full control $systemRule = New-Object System.Security.AccessControl.FileSystemAccessRule( "SYSTEM", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow" ) $acl.SetAccessRule($systemRule) # Grant Administrators full control $adminRule = New-Object System.Security.AccessControl.FileSystemAccessRule( "Administrators", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow" ) $acl.SetAccessRule($adminRule) Set-Acl -Path $Config.InstallPath -AclObject $acl Write-Success "Permissions set for SYSTEM and Administrators" } catch { Write-Warning "Could not set permissions: $_" } } function Install-PythonDependencies { Write-Step "Installing Python dependencies..." $requirementsPath = Join-Path $Config.InstallPath "requirements.txt" if (-not (Test-Path $requirementsPath)) { Write-Warning "requirements.txt not found at $requirementsPath" Write-Warning "Please copy application files first, then run: pip install -r requirements.txt" return } try { # Create virtual environment $venvPath = Join-Path $Config.InstallPath "venv" if (-not (Test-Path $venvPath)) { Write-Step "Creating virtual environment..." & python -m venv $venvPath Write-Success "Virtual environment created" } else { Write-Success "Virtual environment already exists" } # Activate and install dependencies $pipPath = Join-Path $venvPath "Scripts\pip.exe" $pythonPath = Join-Path $venvPath "Scripts\python.exe" Write-Step "Upgrading pip..." & $pythonPath -m pip install --upgrade pip Write-Step "Installing dependencies..." & $pipPath install -r $requirementsPath Write-Success "Python dependencies installed successfully" } catch { throw "Failed to install Python dependencies: $_" } } function New-WindowsService { Write-Step "Creating Windows Service for Telegram bot..." # Check if service already exists $oldErrorAction = $ErrorActionPreference $ErrorActionPreference = "SilentlyContinue" $nssmOutput = & nssm status $Config.ServiceName 2>&1 $serviceExists = $LASTEXITCODE -eq 0 $ErrorActionPreference = $oldErrorAction if ($serviceExists) { Write-Warning "Service already exists, stopping and removing..." & nssm stop $Config.ServiceName 2>&1 | Out-Null Start-Sleep -Seconds 2 & nssm remove $Config.ServiceName confirm 2>&1 | Out-Null Start-Sleep -Seconds 2 Write-Success "Existing service removed" } # Get Python path from virtual environment $venvPath = Join-Path $Config.InstallPath "venv" $pythonPath = Join-Path $venvPath "Scripts\python.exe" if (-not (Test-Path $pythonPath)) { throw "Virtual environment not found. Please run Install-PythonDependencies first." } $appModule = "-m" $appMain = "app.main" # NSSM service creation try { # Install service & nssm install $Config.ServiceName $pythonPath $appModule $appMain # Set service configuration & nssm set $Config.ServiceName DisplayName $Config.ServiceDisplayName & nssm set $Config.ServiceName Description $Config.ServiceDescription & nssm set $Config.ServiceName Start SERVICE_AUTO_START & nssm set $Config.ServiceName AppDirectory $Config.InstallPath # Set environment variables $envFile = Join-Path $Config.InstallPath ".env" if (Test-Path $envFile) { & nssm set $Config.ServiceName AppEnvironmentExtra "PYTHONPATH=$($Config.InstallPath)" Write-Success ".env file will be loaded by application" } else { Write-Warning ".env file not found - create it before starting service" } # Set logging $stdoutLog = Join-Path $Config.LogsPath "stdout.log" $stderrLog = Join-Path $Config.LogsPath "stderr.log" & nssm set $Config.ServiceName AppStdout $stdoutLog & nssm set $Config.ServiceName AppStderr $stderrLog & nssm set $Config.ServiceName AppStdoutCreationDisposition 4 & nssm set $Config.ServiceName AppStderrCreationDisposition 4 # Set restart policy (important for bot reliability) & nssm set $Config.ServiceName AppExit Default Restart & nssm set $Config.ServiceName AppRestartDelay 5000 & nssm set $Config.ServiceName AppThrottle 10000 Write-Success "Windows Service created successfully" } catch { throw "Failed to create Windows Service: $_" } } function New-ConfigurationFile { Write-Step "Creating configuration template..." $envExample = Join-Path $Config.InstallPath ".env.example" $envFile = Join-Path $Config.InstallPath ".env" # Create .env.example template $envTemplate = @" # ROA2WEB Telegram Bot - Production Configuration # Telegram Bot Configuration TELEGRAM_BOT_TOKEN=your_production_bot_token_here # Claude Authentication Configuration # ===================================== # Two authentication methods are supported: # # Method 1: Claude Pro/Max Subscription (RECOMMENDED) # - Leave CLAUDE_API_KEY empty or remove the line # - Run: scripts\Setup-ClaudeAuth.ps1 # - Authenticate via browser with your Claude Pro/Max account # - No additional costs! # # Method 2: Claude API Key (Alternative) # - Get API key from: https://console.anthropic.com/settings/keys # - Set CLAUDE_API_KEY below # - This will take precedence over browser login # - Usage-based billing applies # # Leave empty to use Claude Pro/Max subscription: CLAUDE_API_KEY= # Backend API Configuration BACKEND_URL=http://localhost:8000 BACKEND_TIMEOUT=30 # SQLite Database Configuration SQLITE_DB_PATH=$($Config.DataPath -replace '\\', '\\')\telegram_bot.db # Internal API Configuration (for backend callbacks) INTERNAL_API_HOST=127.0.0.1 INTERNAL_API_PORT=$($Config.ServicePort) # Logging Configuration LOG_LEVEL=INFO LOG_FILE=$($Config.LogsPath -replace '\\', '\\')\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 "@ # Write .env.example Set-Content -Path $envExample -Value $envTemplate -Encoding UTF8 Write-Success "Created .env.example template" # Copy to .env if it doesn't exist if (-not (Test-Path $envFile)) { Copy-Item -Path $envExample -Destination $envFile Write-Warning "Created .env file - PLEASE UPDATE WITH PRODUCTION VALUES" Write-Host " Edit: $envFile" -ForegroundColor Yellow } else { Write-Success ".env file already exists (not overwriting)" } } function Test-ServiceHealth { Write-Step "Testing service health..." Start-Sleep -Seconds 5 try { $response = Invoke-WebRequest -Uri "http://localhost:$($Config.ServicePort)/internal/health" -UseBasicParsing -TimeoutSec 10 if ($response.StatusCode -eq 200) { $content = $response.Content | ConvertFrom-Json Write-Success "Health check passed: $($content.status)" return $true } } catch { Write-Warning "Health check failed (service may need configuration): $_" return $false } } function Show-Summary { Write-Host "`n" + ("=" * 80) -ForegroundColor Cyan Write-Host " ROA2WEB TELEGRAM BOT INSTALLATION COMPLETED" -ForegroundColor Green Write-Host ("=" * 80) -ForegroundColor Cyan Write-Host "`nInstallation Details:" -ForegroundColor Yellow Write-Host " Source Path: $($Config.SourcePath)" Write-Host " Install Path: $($Config.InstallPath)" Write-Host " Scripts Path: $($Config.InstallPath)\scripts\" Write-Host " Data Path: $($Config.DataPath)" Write-Host " Logs Path: $($Config.LogsPath)" Write-Host " Backup Path: $($Config.BackupPath)" Write-Host " Service Name: $($Config.ServiceName)" Write-Host " Internal API Port: $($Config.ServicePort)" Write-Host "`nService Endpoints:" -ForegroundColor Yellow Write-Host " Health Check: http://localhost:$($Config.ServicePort)/internal/health" Write-Host " Stats: http://localhost:$($Config.ServicePort)/internal/stats" Write-Host "`nNext Steps:" -ForegroundColor Yellow Write-Host " 1. Edit configuration: $($Config.InstallPath)\.env" Write-Host " - Set TELEGRAM_BOT_TOKEN (from @BotFather)" Write-Host " - Set CLAUDE_API_KEY (from Anthropic console)" Write-Host " - Verify BACKEND_URL=http://localhost:8000" Write-Host " 2. Navigate to scripts: cd $($Config.InstallPath)\scripts" Write-Host " 3. Start service: .\Start-TelegramBot.ps1" Write-Host " 4. Check logs: Get-Content $($Config.LogsPath)\stdout.log -Tail 50" Write-Host "`nManagement Scripts Location:" -ForegroundColor Yellow Write-Host " $($Config.InstallPath)\scripts\" Write-Host "`nManagement Commands:" -ForegroundColor Yellow Write-Host " cd $($Config.InstallPath)\scripts" Write-Host " .\Start-TelegramBot.ps1 # Start service" Write-Host " .\Stop-TelegramBot.ps1 # Stop service" Write-Host " .\Restart-TelegramBot.ps1 # Restart service" Write-Host " .\Backup-TelegramDB.ps1 # Backup database" Write-Host " .\Setup-DailyBackup.ps1 # Setup automated daily backups" Write-Host "`n" + ("=" * 80) -ForegroundColor Cyan } # ============================================================================= # MAIN INSTALLATION FLOW # ============================================================================= function Main { Write-Host @" ==================================================================== ROA2WEB Telegram Bot - Windows Server Installation Script Telegram Bot Frontend with Claude Agent SDK ==================================================================== "@ -ForegroundColor Cyan # Check prerequisites Write-Step "Checking prerequisites..." if (-not (Test-Administrator)) { Write-Error "This script must be run as Administrator" Write-Host " Right-click PowerShell and select 'Run as Administrator'" -ForegroundColor Yellow exit 1 } Write-Success "Running as Administrator" try { # Installation steps Test-PythonInstallation Install-NSSM New-DirectoryStructure # Copy application files from deployment package $filesCopied = Copy-ApplicationFiles if (-not $filesCopied) { throw "Failed to copy application files. Please ensure you're running this script from the deployment package directory." } Install-PythonDependencies New-ConfigurationFile New-WindowsService Show-Summary Write-Host "`nInstallation completed successfully!" -ForegroundColor Green Write-Host "IMPORTANT: Configure .env file before starting service" -ForegroundColor Yellow } catch { Write-Host "`n[FATAL ERROR] Installation failed: $_" -ForegroundColor Red Write-Host $_.ScriptStackTrace -ForegroundColor Red exit 1 } } # Run main installation Main