Complete Windows deployment console FULL INTEGRATION with auto-detection
This commit completes the unified Windows deployment console with automatic detection of first-time install vs update, eliminating the need for separate Install-*.ps1 scripts. ## Changes ### Added Detection Functions - `Test-BackendInstalled`: Checks if backend is installed (venv + service + requirements.txt) - `Test-TelegramBotInstalled`: Checks if telegram bot is installed (venv + service + app files) ### Added Prerequisite Installation Functions - `Install-Chocolatey`: Automated Chocolatey package manager installation - `Install-Python`: Python 3.11 installation via Chocolatey - `Install-NSSM`: NSSM (Windows service manager) installation via Chocolatey - `Install-IISModules`: IIS and required modules (URL Rewrite, Application Initialization) ### Added First-Time Installation Functions - `Install-BackendFirstTime`: Complete backend first-time installation (~240 lines) - Directory structure creation - Backend, frontend, shared files copying with exclusions - .env.example template copying - Virtual environment creation with proper pip installation - Windows Service creation via NSSM - IIS Application Pool and Web Application configuration - `Install-TelegramBotFirstTime`: Complete telegram bot first-time installation (~155 lines) - Directory structure (app, data, logs, temp, backups) - App files and requirements.txt copying - Virtual environment creation - Windows Service creation via NSSM ### Updated Deployment Functions with Auto-Routing - `Deploy-Backend`: Now calls `Test-BackendInstalled` first - If NOT installed → routes to `Install-BackendFirstTime` - If installed → proceeds with existing UPDATE logic (backup, stop, update, start) - `Deploy-TelegramBot`: Now calls `Test-TelegramBotInstalled` first - If NOT installed → routes to `Install-TelegramBotFirstTime` - If installed → proceeds with existing UPDATE logic (backup, stop, update, start) ## Benefits 1. **Single Entry Point**: Users can run the same deploy command for both first-time install and updates 2. **No Separate Install Scripts**: Eliminates confusion between Install-*.ps1 and Deploy-*.ps1 3. **Automatic Detection**: Intelligently detects what needs to be done 4. **Safer Updates**: Always checks if components exist before attempting updates 5. **Better UX**: Clear warnings about first-time installation vs updates ## Usage ```powershell # Interactive (auto-detects install vs update) .\ROA2WEB-Console.ps1 # Select: Deploy Components > Backend + Frontend # Command line (auto-detects install vs update) .\ROA2WEB-Console.ps1 -NonInteractive -Action DeployBackend .\ROA2WEB-Console.ps1 -NonInteractive -Action DeployTelegramBot .\ROA2WEB-Console.ps1 -NonInteractive -Action DeployAll ``` The console will automatically: - Detect if backend/telegram-bot are installed - Perform first-time installation if needed (including prerequisites) - Perform updates if already installed (with automatic backups) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -124,6 +124,73 @@ function Test-Administrator {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# DETECTION FUNCTIONS - Check if first install or update needed
|
||||
# =============================================================================
|
||||
|
||||
function Test-BackendInstalled {
|
||||
Write-Step "Checking if Backend is installed..."
|
||||
|
||||
$venvPath = Join-Path $Config.BackendPath "venv"
|
||||
$venvExists = Test-Path $venvPath
|
||||
$service = Get-ServiceSafe -ServiceName $Config.BackendServiceName
|
||||
$serviceExists = $null -ne $service
|
||||
|
||||
$requirementsPath = Join-Path $Config.BackendPath "requirements.txt"
|
||||
$hasRequirements = Test-Path $requirementsPath
|
||||
|
||||
if ($venvExists -and $serviceExists -and $hasRequirements) {
|
||||
Write-Success "Backend is installed (venv + service exist)"
|
||||
return $true
|
||||
} else {
|
||||
Write-Warning "Backend NOT installed (missing: $(
|
||||
@(
|
||||
if (-not $venvExists) { 'venv' }
|
||||
if (-not $serviceExists) { 'service' }
|
||||
if (-not $hasRequirements) { 'requirements.txt' }
|
||||
) -join ', '
|
||||
))"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Test-TelegramBotInstalled {
|
||||
Write-Step "Checking if Telegram Bot is installed..."
|
||||
|
||||
$venvPath = Join-Path $Config.TelegramBotPath "venv"
|
||||
$venvExists = Test-Path $venvPath
|
||||
$service = Get-ServiceSafe -ServiceName $Config.TelegramBotServiceName
|
||||
$serviceExists = $null -ne $service
|
||||
|
||||
$appPath = Join-Path $Config.TelegramBotPath "app"
|
||||
$hasApp = Test-Path $appPath
|
||||
|
||||
if ($venvExists -and $serviceExists -and $hasApp) {
|
||||
Write-Success "Telegram Bot is installed (venv + service exist)"
|
||||
return $true
|
||||
} else {
|
||||
Write-Warning "Telegram Bot NOT installed (missing: $(
|
||||
@(
|
||||
if (-not $venvExists) { 'venv' }
|
||||
if (-not $serviceExists) { 'service' }
|
||||
if (-not $hasApp) { 'app files' }
|
||||
) -join ', '
|
||||
))"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ServiceSafe {
|
||||
param([string]$ServiceName)
|
||||
try {
|
||||
@@ -179,6 +246,595 @@ function Wait-ForKeyPress {
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# PREREQUISITE INSTALLATION FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
function Install-Chocolatey {
|
||||
Write-Step "Checking Chocolatey package manager..."
|
||||
|
||||
if (Test-CommandExists "choco") {
|
||||
Write-Success "Chocolatey already installed"
|
||||
return $true
|
||||
}
|
||||
|
||||
Write-Info "Installing Chocolatey..."
|
||||
try {
|
||||
Set-ExecutionPolicy Bypass -Scope Process -Force
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
|
||||
Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
|
||||
|
||||
# Refresh environment
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
|
||||
|
||||
Write-Success "Chocolatey installed successfully"
|
||||
return $true
|
||||
} catch {
|
||||
Write-Warning "Failed to install Chocolatey: $_"
|
||||
Write-Warning "Some automated installations may not be available"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Install-Python {
|
||||
Write-Step "Checking Python installation..."
|
||||
|
||||
# Check if Python is already installed
|
||||
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 already installed"
|
||||
return $true
|
||||
} else {
|
||||
Write-Warning "Python $installedVersion found, but 3.11+ required"
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Write-Info "Python not found, will attempt installation..."
|
||||
}
|
||||
|
||||
# Try to install via Chocolatey
|
||||
if (Test-CommandExists "choco") {
|
||||
try {
|
||||
Write-Step "Installing Python 3.11..."
|
||||
choco install python --version=3.11.9 -y --force
|
||||
|
||||
# Refresh environment
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
|
||||
|
||||
Write-Success "Python installed successfully"
|
||||
return $true
|
||||
} catch {
|
||||
Write-Error "Failed to install Python via Chocolatey: $_"
|
||||
return $false
|
||||
}
|
||||
} else {
|
||||
Write-Error "Chocolatey not available, cannot auto-install Python"
|
||||
Write-Warning "Please download Python manually from: https://www.python.org/downloads/"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Install-NSSM {
|
||||
Write-Step "Checking NSSM (service manager)..."
|
||||
|
||||
if (Test-Path "C:\nssm\nssm.exe") {
|
||||
Write-Success "NSSM already installed"
|
||||
return $true
|
||||
}
|
||||
|
||||
# Try Chocolatey first
|
||||
if (Test-CommandExists "choco") {
|
||||
try {
|
||||
choco install nssm -y
|
||||
Write-Success "NSSM installed via Chocolatey"
|
||||
return $true
|
||||
} catch {
|
||||
Write-Warning "Chocolatey installation failed, trying direct download..."
|
||||
}
|
||||
}
|
||||
|
||||
# Direct download 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"
|
||||
return $true
|
||||
} catch {
|
||||
Write-Error "Failed to install NSSM: $_"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Install-IISModules {
|
||||
Write-Step "Checking IIS installation..."
|
||||
|
||||
# Detect OS type (Server vs Desktop)
|
||||
$osInfo = Get-CimInstance -ClassName Win32_OperatingSystem
|
||||
$isServer = $osInfo.ProductType -eq 3
|
||||
|
||||
# Check if IIS is installed
|
||||
$iisInstalled = $false
|
||||
|
||||
if ($isServer) {
|
||||
$iisFeature = Get-WindowsFeature -Name Web-Server -ErrorAction SilentlyContinue
|
||||
$iisInstalled = $iisFeature -and $iisFeature.InstallState -eq "Installed"
|
||||
|
||||
if (-not $iisInstalled) {
|
||||
Write-Warning "IIS not installed on Windows Server"
|
||||
Write-Host " Install with: Install-WindowsFeature -Name Web-Server -IncludeManagementTools" -ForegroundColor Yellow
|
||||
return $false
|
||||
}
|
||||
} else {
|
||||
$iisFeature = Get-WindowsOptionalFeature -Online -FeatureName IIS-WebServer -ErrorAction SilentlyContinue
|
||||
$iisInstalled = $iisFeature -and $iisFeature.State -eq "Enabled"
|
||||
|
||||
if (-not $iisInstalled) {
|
||||
Write-Warning "IIS not installed on Windows Desktop"
|
||||
Write-Host " Enable via: Control Panel -> Windows Features -> Internet Information Services" -ForegroundColor Yellow
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
Write-Success "IIS is installed ($($osInfo.Caption))"
|
||||
|
||||
# Install URL Rewrite Module
|
||||
Write-Step "Checking IIS URL Rewrite Module..."
|
||||
$urlRewriteInstalled = Get-WebConfiguration -Filter "/system.webServer/rewrite" -PSPath "IIS:\" -ErrorAction SilentlyContinue
|
||||
|
||||
if (-not $urlRewriteInstalled) {
|
||||
try {
|
||||
Write-Info "Installing URL Rewrite Module..."
|
||||
$urlRewriteUrl = "https://download.microsoft.com/download/1/2/8/128E2E22-C1B9-44A4-BE2A-5859ED1D4592/rewrite_amd64_en-US.msi"
|
||||
$urlRewritePath = "$env:TEMP\rewrite_amd64.msi"
|
||||
|
||||
Invoke-WebRequest -Uri $urlRewriteUrl -OutFile $urlRewritePath
|
||||
Start-Process msiexec.exe -ArgumentList "/i", $urlRewritePath, "/quiet", "/norestart" -Wait
|
||||
Remove-Item $urlRewritePath -Force
|
||||
|
||||
Write-Success "URL Rewrite Module installed"
|
||||
} catch {
|
||||
Write-Warning "Failed to install URL Rewrite: $_"
|
||||
Write-Warning "Download manually from: https://www.iis.net/downloads/microsoft/url-rewrite"
|
||||
}
|
||||
} else {
|
||||
Write-Success "URL Rewrite Module already installed"
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# FIRST-TIME INSTALLATION FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
function Install-BackendFirstTime {
|
||||
Write-Host "`n" + ("=" * 70) -ForegroundColor Yellow
|
||||
Write-Host " FIRST-TIME INSTALLATION: BACKEND + FRONTEND" -ForegroundColor Yellow
|
||||
Write-Host ("=" * 70) -ForegroundColor Yellow
|
||||
|
||||
try {
|
||||
# Install prerequisites
|
||||
Write-Step "Installing prerequisites..."
|
||||
Install-Chocolatey | Out-Null
|
||||
|
||||
if (-not (Install-Python)) {
|
||||
throw "Python installation failed"
|
||||
}
|
||||
|
||||
if (-not (Install-NSSM)) {
|
||||
throw "NSSM installation failed"
|
||||
}
|
||||
|
||||
# Create directory structure
|
||||
Write-Step "Creating directory structure..."
|
||||
$directories = @(
|
||||
$Config.InstallPath,
|
||||
$Config.BackendPath,
|
||||
$Config.FrontendPath,
|
||||
$Config.LogsPath,
|
||||
(Join-Path $Config.BackendPath "logs"),
|
||||
(Join-Path $Config.BackendPath "temp")
|
||||
)
|
||||
|
||||
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 for IIS
|
||||
try {
|
||||
$acl = Get-Acl $Config.InstallPath
|
||||
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("IIS_IUSRS", "ReadAndExecute", "ContainerInherit,ObjectInherit", "None", "Allow")
|
||||
$acl.SetAccessRule($accessRule)
|
||||
Set-Acl -Path $Config.InstallPath -AclObject $acl
|
||||
Write-Success "Permissions set for IIS_IUSRS"
|
||||
} catch {
|
||||
Write-Warning "Could not set permissions: $_"
|
||||
}
|
||||
|
||||
# Copy backend files from source
|
||||
Write-Step "Copying backend files..."
|
||||
$sourceBackend = Join-Path $Config.SourcePath "backend"
|
||||
|
||||
if (-not (Test-Path $sourceBackend)) {
|
||||
throw "Source backend path not found: $sourceBackend"
|
||||
}
|
||||
|
||||
# Copy backend files (exclude venv, __pycache__, .env)
|
||||
$excludeDirs = @("venv", "__pycache__", ".pytest_cache", "logs", "temp", "node_modules")
|
||||
$excludeFiles = @("*.pyc", "*.pyo", "*.log", ".env", ".env.local")
|
||||
|
||||
Get-ChildItem -Path $sourceBackend -Recurse | ForEach-Object {
|
||||
$relativePath = $_.FullName.Substring($sourceBackend.Length).TrimStart('\', '/')
|
||||
|
||||
# Check if excluded
|
||||
$inExcludedDir = $false
|
||||
foreach ($excludeDir in $excludeDirs) {
|
||||
if ($relativePath -match "^$excludeDir" -or $relativePath -match "[\\/]$excludeDir[\\/]") {
|
||||
$inExcludedDir = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
if ($inExcludedDir) { return }
|
||||
|
||||
$isExcludedFile = $false
|
||||
foreach ($pattern in $excludeFiles) {
|
||||
if ($_.Name -like $pattern) {
|
||||
$isExcludedFile = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
if ($isExcludedFile) { return }
|
||||
|
||||
$destPath = Join-Path $Config.BackendPath $relativePath
|
||||
|
||||
if ($_.PSIsContainer) {
|
||||
if (-not (Test-Path $destPath)) {
|
||||
New-Item -ItemType Directory -Path $destPath -Force | Out-Null
|
||||
}
|
||||
} else {
|
||||
$destDir = Split-Path $destPath -Parent
|
||||
if (-not (Test-Path $destDir)) {
|
||||
New-Item -ItemType Directory -Path $destDir -Force | Out-Null
|
||||
}
|
||||
Copy-Item -Path $_.FullName -Destination $destPath -Force
|
||||
}
|
||||
}
|
||||
Write-Success "Backend files copied"
|
||||
|
||||
# Copy .env.example
|
||||
$sourceEnvExample = Join-Path $sourceBackend ".env.example"
|
||||
if (Test-Path $sourceEnvExample) {
|
||||
$destEnvExample = Join-Path $Config.BackendPath ".env.example"
|
||||
Copy-Item -Path $sourceEnvExample -Destination $destEnvExample -Force
|
||||
Write-Success ".env.example template copied"
|
||||
}
|
||||
|
||||
# Copy shared modules
|
||||
$sourceShared = Join-Path $Config.SourcePath "shared"
|
||||
if (Test-Path $sourceShared) {
|
||||
$destShared = Join-Path (Split-Path $Config.BackendPath -Parent) "shared"
|
||||
Copy-Item -Path $sourceShared -Destination $destShared -Recurse -Force -Exclude @("__pycache__", "*.pyc")
|
||||
Write-Success "Shared modules copied"
|
||||
}
|
||||
|
||||
# Copy frontend files
|
||||
Write-Step "Copying frontend files..."
|
||||
$sourceFrontend = Join-Path $Config.SourcePath "frontend"
|
||||
|
||||
if (Test-Path $sourceFrontend) {
|
||||
Copy-Item -Path "$sourceFrontend\*" -Destination $Config.FrontendPath -Recurse -Force
|
||||
Write-Success "Frontend files copied"
|
||||
} else {
|
||||
Write-Warning "Frontend source not found: $sourceFrontend"
|
||||
}
|
||||
|
||||
# Create virtual environment
|
||||
Write-Step "Creating Python virtual environment..."
|
||||
$venvPath = Join-Path $Config.BackendPath "venv"
|
||||
& python -m venv $venvPath
|
||||
Write-Success "Virtual environment created at: $venvPath"
|
||||
|
||||
# Install dependencies
|
||||
Write-Step "Installing Python dependencies..."
|
||||
$pipPath = Join-Path $venvPath "Scripts\pip.exe"
|
||||
$pythonPath = Join-Path $venvPath "Scripts\python.exe"
|
||||
$requirementsPath = Join-Path $Config.BackendPath "requirements.txt"
|
||||
|
||||
if (Test-Path $requirementsPath) {
|
||||
Write-Info "Upgrading pip..."
|
||||
& $pythonPath -m pip install --upgrade pip | Out-Default
|
||||
|
||||
Write-Info "Installing dependencies from requirements.txt..."
|
||||
& $pipPath install -r $requirementsPath | Out-Default
|
||||
Write-Success "Python dependencies installed"
|
||||
} else {
|
||||
Write-Warning "requirements.txt not found at: $requirementsPath"
|
||||
}
|
||||
|
||||
# Create Windows Service
|
||||
Write-Step "Creating Windows Service for backend..."
|
||||
|
||||
# Remove existing service if present
|
||||
$oldErrorAction = $ErrorActionPreference
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
$nssmOutput = & nssm status $Config.BackendServiceName 2>&1
|
||||
$serviceExists = $LASTEXITCODE -eq 0
|
||||
$ErrorActionPreference = $oldErrorAction
|
||||
|
||||
if ($serviceExists) {
|
||||
Write-Info "Removing existing service..."
|
||||
& nssm stop $Config.BackendServiceName 2>&1 | Out-Null
|
||||
Start-Sleep -Seconds 2
|
||||
& nssm remove $Config.BackendServiceName confirm 2>&1 | Out-Null
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
|
||||
# Create service with venv Python
|
||||
& nssm install $Config.BackendServiceName $pythonPath "-m" "uvicorn" "app.main:app" "--host" "127.0.0.1" "--port" $Config.BackendPort.ToString() "--workers" "4"
|
||||
& nssm set $Config.BackendServiceName DisplayName $Config.BackendServiceDisplayName
|
||||
& nssm set $Config.BackendServiceName Description $Config.BackendServiceDescription
|
||||
& nssm set $Config.BackendServiceName Start SERVICE_AUTO_START
|
||||
& nssm set $Config.BackendServiceName AppDirectory $Config.BackendPath
|
||||
& nssm set $Config.BackendServiceName AppEnvironmentExtra "PYTHONPATH=$($Config.InstallPath)"
|
||||
|
||||
# Set logging
|
||||
$stdoutLog = Join-Path $Config.LogsPath "backend-stdout.log"
|
||||
$stderrLog = Join-Path $Config.LogsPath "backend-stderr.log"
|
||||
& nssm set $Config.BackendServiceName AppStdout $stdoutLog
|
||||
& nssm set $Config.BackendServiceName AppStderr $stderrLog
|
||||
& nssm set $Config.BackendServiceName AppStdoutCreationDisposition 4
|
||||
& nssm set $Config.BackendServiceName AppStderrCreationDisposition 4
|
||||
& nssm set $Config.BackendServiceName AppExit Default Restart
|
||||
& nssm set $Config.BackendServiceName AppRestartDelay 5000
|
||||
|
||||
Write-Success "Windows Service created: $($Config.BackendServiceName)"
|
||||
|
||||
# Configure IIS
|
||||
if (Install-IISModules) {
|
||||
Write-Step "Configuring IIS..."
|
||||
Import-Module WebAdministration -ErrorAction Stop
|
||||
|
||||
# Create Application Pool
|
||||
if (Test-Path "IIS:\AppPools\$($Config.IISAppPoolName)") {
|
||||
Remove-WebAppPool -Name $Config.IISAppPoolName -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
New-WebAppPool -Name $Config.IISAppPoolName -Force | Out-Null
|
||||
Set-ItemProperty -Path "IIS:\AppPools\$($Config.IISAppPoolName)" -Name "managedRuntimeVersion" -Value ""
|
||||
Write-Success "Application Pool created: $($Config.IISAppPoolName)"
|
||||
|
||||
# Create/update application
|
||||
$existingApp = Get-WebApplication -Name $Config.IISAppName -Site $Config.IISSiteName -ErrorAction SilentlyContinue
|
||||
if ($existingApp) {
|
||||
Remove-WebApplication -Name $Config.IISAppName -Site $Config.IISSiteName -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
New-WebApplication -Name $Config.IISAppName `
|
||||
-Site $Config.IISSiteName `
|
||||
-PhysicalPath $Config.FrontendPath `
|
||||
-ApplicationPool $Config.IISAppPoolName `
|
||||
-Force | Out-Null
|
||||
|
||||
Write-Success "IIS Application created: /$($Config.IISAppName)"
|
||||
|
||||
# Copy web.config
|
||||
$webConfigSource = Join-Path (Split-Path $Config.SourcePath -Parent) "config\web.config"
|
||||
$webConfigDest = Join-Path $Config.FrontendPath "web.config"
|
||||
if (Test-Path $webConfigSource) {
|
||||
Copy-Item -Path $webConfigSource -Destination $webConfigDest -Force
|
||||
Write-Success "web.config copied"
|
||||
}
|
||||
} else {
|
||||
Write-Warning "IIS modules not installed, skipping IIS configuration"
|
||||
}
|
||||
|
||||
Write-Host "`n" + ("=" * 70) -ForegroundColor Green
|
||||
Write-Host " BACKEND + FRONTEND INSTALLATION COMPLETED" -ForegroundColor Green
|
||||
Write-Host ("=" * 70) -ForegroundColor Green
|
||||
Write-Host "`nIMPORTANT: Configure .env file before starting service" -ForegroundColor Yellow
|
||||
Write-Host "Location: $($Config.BackendPath)\.env" -ForegroundColor Yellow
|
||||
|
||||
return $true
|
||||
} catch {
|
||||
Write-Host "`n[INSTALLATION FAILED] $_" -ForegroundColor Red
|
||||
Write-Host $_.ScriptStackTrace -ForegroundColor Red
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Install-TelegramBotFirstTime {
|
||||
Write-Host "`n" + ("=" * 70) -ForegroundColor Yellow
|
||||
Write-Host " FIRST-TIME INSTALLATION: TELEGRAM BOT" -ForegroundColor Yellow
|
||||
Write-Host ("=" * 70) -ForegroundColor Yellow
|
||||
|
||||
try {
|
||||
# Install prerequisites
|
||||
Write-Step "Installing prerequisites..."
|
||||
Install-Chocolatey | Out-Null
|
||||
|
||||
if (-not (Install-Python)) {
|
||||
throw "Python installation failed"
|
||||
}
|
||||
|
||||
if (-not (Install-NSSM)) {
|
||||
throw "NSSM installation failed"
|
||||
}
|
||||
|
||||
# Create directory structure
|
||||
Write-Step "Creating directory structure..."
|
||||
$directories = @(
|
||||
$Config.TelegramBotPath,
|
||||
(Join-Path $Config.TelegramBotPath "app"),
|
||||
(Join-Path $Config.TelegramBotPath "data"),
|
||||
$Config.TelegramBotLogsPath,
|
||||
(Join-Path $Config.TelegramBotPath "temp"),
|
||||
(Join-Path $Config.TelegramBotPath "backups")
|
||||
)
|
||||
|
||||
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
|
||||
try {
|
||||
$acl = Get-Acl $Config.TelegramBotPath
|
||||
$systemRule = New-Object System.Security.AccessControl.FileSystemAccessRule("SYSTEM", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow")
|
||||
$acl.SetAccessRule($systemRule)
|
||||
Set-Acl -Path $Config.TelegramBotPath -AclObject $acl
|
||||
Write-Success "Permissions set for SYSTEM"
|
||||
} catch {
|
||||
Write-Warning "Could not set permissions: $_"
|
||||
}
|
||||
|
||||
# Copy telegram bot files
|
||||
Write-Step "Copying Telegram bot files..."
|
||||
$sourceTelegramBot = Join-Path $Config.SourcePath "telegram-bot"
|
||||
|
||||
if (-not (Test-Path $sourceTelegramBot)) {
|
||||
throw "Source telegram-bot path not found: $sourceTelegramBot"
|
||||
}
|
||||
|
||||
# Copy app directory
|
||||
$sourceApp = Join-Path $sourceTelegramBot "app"
|
||||
$destApp = Join-Path $Config.TelegramBotPath "app"
|
||||
|
||||
if (Test-Path $destApp) {
|
||||
Remove-Item -Path $destApp -Recurse -Force
|
||||
}
|
||||
|
||||
Copy-Item -Path $sourceApp -Destination $destApp -Recurse -Force
|
||||
Write-Success "Application files copied"
|
||||
|
||||
# Copy requirements.txt
|
||||
$sourceReq = Join-Path $sourceTelegramBot "requirements.txt"
|
||||
if (Test-Path $sourceReq) {
|
||||
$destReq = Join-Path $Config.TelegramBotPath "requirements.txt"
|
||||
Copy-Item -Path $sourceReq -Destination $destReq -Force
|
||||
Write-Success "requirements.txt copied"
|
||||
}
|
||||
|
||||
# Copy .env.example
|
||||
$sourceEnvExample = Join-Path $sourceTelegramBot ".env.example"
|
||||
if (Test-Path $sourceEnvExample) {
|
||||
$destEnvExample = Join-Path $Config.TelegramBotPath ".env.example"
|
||||
Copy-Item -Path $sourceEnvExample -Destination $destEnvExample -Force
|
||||
Write-Success ".env.example template copied"
|
||||
}
|
||||
|
||||
# Create virtual environment
|
||||
Write-Step "Creating Python virtual environment..."
|
||||
$venvPath = Join-Path $Config.TelegramBotPath "venv"
|
||||
& python -m venv $venvPath
|
||||
Write-Success "Virtual environment created"
|
||||
|
||||
# Install dependencies
|
||||
Write-Step "Installing Python dependencies..."
|
||||
$pipPath = Join-Path $venvPath "Scripts\pip.exe"
|
||||
$pythonPath = Join-Path $venvPath "Scripts\python.exe"
|
||||
$requirementsPath = Join-Path $Config.TelegramBotPath "requirements.txt"
|
||||
|
||||
if (Test-Path $requirementsPath) {
|
||||
Write-Info "Upgrading pip..."
|
||||
& $pythonPath -m pip install --upgrade pip | Out-Default
|
||||
|
||||
Write-Info "Installing dependencies..."
|
||||
& $pipPath install -r $requirementsPath | Out-Default
|
||||
Write-Success "Python dependencies installed"
|
||||
}
|
||||
|
||||
# Create Windows Service
|
||||
Write-Step "Creating Windows Service for Telegram bot..."
|
||||
|
||||
# Remove existing service if present
|
||||
$oldErrorAction = $ErrorActionPreference
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
$nssmOutput = & nssm status $Config.TelegramBotServiceName 2>&1
|
||||
$serviceExists = $LASTEXITCODE -eq 0
|
||||
$ErrorActionPreference = $oldErrorAction
|
||||
|
||||
if ($serviceExists) {
|
||||
Write-Info "Removing existing service..."
|
||||
& nssm stop $Config.TelegramBotServiceName 2>&1 | Out-Null
|
||||
Start-Sleep -Seconds 2
|
||||
& nssm remove $Config.TelegramBotServiceName confirm 2>&1 | Out-Null
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
|
||||
# Create service
|
||||
& nssm install $Config.TelegramBotServiceName $pythonPath "-m" "app.main"
|
||||
& nssm set $Config.TelegramBotServiceName DisplayName $Config.TelegramBotServiceDisplayName
|
||||
& nssm set $Config.TelegramBotServiceName Description $Config.TelegramBotServiceDescription
|
||||
& nssm set $Config.TelegramBotServiceName Start SERVICE_AUTO_START
|
||||
& nssm set $Config.TelegramBotServiceName AppDirectory $Config.TelegramBotPath
|
||||
& nssm set $Config.TelegramBotServiceName AppEnvironmentExtra "PYTHONPATH=$($Config.TelegramBotPath)"
|
||||
|
||||
# Set logging
|
||||
$stdoutLog = Join-Path $Config.TelegramBotLogsPath "stdout.log"
|
||||
$stderrLog = Join-Path $Config.TelegramBotLogsPath "stderr.log"
|
||||
& nssm set $Config.TelegramBotServiceName AppStdout $stdoutLog
|
||||
& nssm set $Config.TelegramBotServiceName AppStderr $stderrLog
|
||||
& nssm set $Config.TelegramBotServiceName AppStdoutCreationDisposition 4
|
||||
& nssm set $Config.TelegramBotServiceName AppStderrCreationDisposition 4
|
||||
& nssm set $Config.TelegramBotServiceName AppExit Default Restart
|
||||
& nssm set $Config.TelegramBotServiceName AppRestartDelay 5000
|
||||
|
||||
Write-Success "Windows Service created: $($Config.TelegramBotServiceName)"
|
||||
|
||||
Write-Host "`n" + ("=" * 70) -ForegroundColor Green
|
||||
Write-Host " TELEGRAM BOT INSTALLATION COMPLETED" -ForegroundColor Green
|
||||
Write-Host ("=" * 70) -ForegroundColor Green
|
||||
Write-Host "`nIMPORTANT: Configure .env file before starting service" -ForegroundColor Yellow
|
||||
Write-Host "Location: $($Config.TelegramBotPath)\.env" -ForegroundColor Yellow
|
||||
|
||||
return $true
|
||||
} catch {
|
||||
Write-Host "`n[INSTALLATION FAILED] $_" -ForegroundColor Red
|
||||
Write-Host $_.ScriptStackTrace -ForegroundColor Red
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# MENU FUNCTIONS
|
||||
# =============================================================================
|
||||
@@ -653,16 +1309,21 @@ function Update-BackendFiles {
|
||||
Write-Step "Requirements changed, updating Python dependencies..."
|
||||
Copy-Item -Path $sourceReq -Destination $destReq -Force
|
||||
|
||||
$pythonCmd = Get-Command python -ErrorAction SilentlyContinue
|
||||
if ($pythonCmd) {
|
||||
& python -m pip install -r $destReq --upgrade
|
||||
# Use virtual environment pip (same pattern as Telegram Bot)
|
||||
$venvPath = Join-Path $Config.BackendPath "venv"
|
||||
$pipPath = Join-Path $venvPath "Scripts\pip.exe"
|
||||
|
||||
if (Test-Path $pipPath) {
|
||||
& $pipPath install -r $destReq --upgrade
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Success "Python dependencies installed successfully"
|
||||
Write-Success "Python dependencies updated"
|
||||
} else {
|
||||
throw "pip install failed with exit code: $LASTEXITCODE"
|
||||
}
|
||||
} else {
|
||||
throw "Python not found in PATH"
|
||||
Write-Warning "Virtual environment not found at: $venvPath"
|
||||
Write-Warning "Run Install-ROA2WEB.ps1 first to create virtual environment"
|
||||
throw "Backend virtual environment not found"
|
||||
}
|
||||
} else {
|
||||
Write-Success "Python dependencies unchanged"
|
||||
@@ -782,6 +1443,24 @@ function Deploy-Backend {
|
||||
Write-Host ("=" * 70) -ForegroundColor Cyan
|
||||
|
||||
try {
|
||||
# Auto-detect: First-time install or update?
|
||||
$isInstalled = Test-BackendInstalled
|
||||
|
||||
if (-not $isInstalled) {
|
||||
Write-Host ""
|
||||
Write-Warning "Backend NOT detected - performing FIRST-TIME INSTALLATION"
|
||||
Write-Host ""
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
# Route to first-time installation
|
||||
return Install-BackendFirstTime
|
||||
}
|
||||
|
||||
# Backend is already installed - proceed with UPDATE
|
||||
Write-Host ""
|
||||
Write-Info "Backend detected - performing UPDATE"
|
||||
Write-Host ""
|
||||
|
||||
# Create backup
|
||||
$backupPath = Backup-CurrentDeployment -Component "Backend"
|
||||
|
||||
@@ -823,6 +1502,24 @@ function Deploy-TelegramBot {
|
||||
Write-Host ("=" * 70) -ForegroundColor Cyan
|
||||
|
||||
try {
|
||||
# Auto-detect: First-time install or update?
|
||||
$isInstalled = Test-TelegramBotInstalled
|
||||
|
||||
if (-not $isInstalled) {
|
||||
Write-Host ""
|
||||
Write-Warning "Telegram Bot NOT detected - performing FIRST-TIME INSTALLATION"
|
||||
Write-Host ""
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
# Route to first-time installation
|
||||
return Install-TelegramBotFirstTime
|
||||
}
|
||||
|
||||
# Telegram Bot is already installed - proceed with UPDATE
|
||||
Write-Host ""
|
||||
Write-Info "Telegram Bot detected - performing UPDATE"
|
||||
Write-Host ""
|
||||
|
||||
# Create backup
|
||||
$backupPath = Backup-CurrentDeployment -Component "TelegramBot"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user