<# .SYNOPSIS Backup ROA2WEB Telegram Bot SQLite Database .DESCRIPTION This script creates a backup of the Telegram bot's SQLite database: - Copies database file with timestamp - Compresses backup (optional) - Cleans up old backups (keeps last 30 days) - Logs backup operations - Can run manually or via scheduled task .PARAMETER InstallPath Installation path (default: C:\inetpub\wwwroot\roa2web\telegram-bot) .PARAMETER RetentionDays Number of days to keep backups (default: 30) .PARAMETER Compress Compress backup file (default: true) .PARAMETER LogToFile Write backup log to file (default: true) .EXAMPLE .\Backup-TelegramDB.ps1 Standard backup with defaults .EXAMPLE .\Backup-TelegramDB.ps1 -RetentionDays 60 -Compress $true Backup with 60-day retention and compression .EXAMPLE .\Backup-TelegramDB.ps1 -LogToFile $false Backup without logging to file (console only) .NOTES Author: ROA2WEB Team Requires: PowerShell 5.1+ Can be run manually or via Task Scheduler #> [CmdletBinding()] param( [string]$InstallPath = "C:\inetpub\wwwroot\roa2web\telegram-bot", [int]$RetentionDays = 30, [bool]$Compress = $true, [bool]$LogToFile = $true ) $ErrorActionPreference = "Stop" # ============================================================================= # CONFIGURATION # ============================================================================= $script:Config = @{ InstallPath = $InstallPath DataPath = Join-Path $InstallPath "data" BackupPath = Join-Path $InstallPath "backups" LogsPath = Join-Path $InstallPath "logs" DatabaseFile = "telegram_bot.db" RetentionDays = $RetentionDays Compress = $Compress } # ============================================================================= # HELPER FUNCTIONS # ============================================================================= function Write-Log { param( [string]$Message, [string]$Level = "INFO" ) $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $logMessage = "[$timestamp] [$Level] $Message" # Console output switch ($Level) { "ERROR" { Write-Host $logMessage -ForegroundColor Red } "WARN" { Write-Host $logMessage -ForegroundColor Yellow } "SUCCESS" { Write-Host $logMessage -ForegroundColor Green } default { Write-Host $logMessage -ForegroundColor Cyan } } # File output if ($LogToFile) { $logFile = Join-Path $Config.LogsPath "backup.log" Add-Content -Path $logFile -Value $logMessage -Encoding UTF8 } } function Test-DatabaseFile { $dbPath = Join-Path $Config.DataPath $Config.DatabaseFile if (-not (Test-Path $dbPath)) { Write-Log "Database file not found: $dbPath" -Level "ERROR" return $false } # Check if file is accessible (not locked) try { $stream = [System.IO.File]::Open($dbPath, 'Open', 'Read', 'Read') $stream.Close() Write-Log "Database file is accessible: $dbPath" -Level "INFO" return $true } catch { Write-Log "Database file is locked or inaccessible: $_" -Level "ERROR" return $false } } function Get-DatabaseSize { $dbPath = Join-Path $Config.DataPath $Config.DatabaseFile $size = (Get-Item $dbPath).Length / 1KB return [math]::Round($size, 2) } function New-BackupDirectory { if (-not (Test-Path $Config.BackupPath)) { New-Item -ItemType Directory -Path $Config.BackupPath -Force | Out-Null Write-Log "Created backup directory: $($Config.BackupPath)" -Level "INFO" } } function Backup-Database { Write-Log "Starting database backup..." -Level "INFO" # Ensure backup directory exists New-BackupDirectory # Generate backup filename with timestamp $timestamp = Get-Date -Format "yyyyMMdd-HHmmss" $backupFileName = "telegram_bot_backup_$timestamp.db" $backupFilePath = Join-Path $Config.BackupPath $backupFileName # Source database path $dbPath = Join-Path $Config.DataPath $Config.DatabaseFile try { # Copy database file Copy-Item -Path $dbPath -Destination $backupFilePath -Force Write-Log "Database backed up to: $backupFileName" -Level "SUCCESS" # Get backup file size $backupSize = (Get-Item $backupFilePath).Length / 1KB Write-Log "Backup size: $([math]::Round($backupSize, 2)) KB" -Level "INFO" # Compress if enabled if ($Config.Compress) { $compressedPath = Compress-Backup -BackupPath $backupFilePath if ($compressedPath) { # Remove uncompressed backup Remove-Item -Path $backupFilePath -Force Write-Log "Uncompressed backup removed" -Level "INFO" return $compressedPath } } return $backupFilePath } catch { Write-Log "Backup failed: $_" -Level "ERROR" return $null } } function Compress-Backup { param([string]$BackupPath) Write-Log "Compressing backup..." -Level "INFO" $zipPath = "$BackupPath.zip" try { # Create ZIP archive Compress-Archive -Path $BackupPath -DestinationPath $zipPath -CompressionLevel Optimal -Force # Get compressed size $originalSize = (Get-Item $BackupPath).Length / 1KB $compressedSize = (Get-Item $zipPath).Length / 1KB $ratio = [math]::Round((1 - ($compressedSize / $originalSize)) * 100, 1) Write-Log "Backup compressed: $([math]::Round($compressedSize, 2)) KB (saved $ratio%)" -Level "SUCCESS" return $zipPath } catch { Write-Log "Compression failed: $_" -Level "WARN" return $null } } function Remove-OldBackups { Write-Log "Cleaning up old backups (keeping last $($Config.RetentionDays) days)..." -Level "INFO" $cutoffDate = (Get-Date).AddDays(-$Config.RetentionDays) try { # Get all backup files (both .db and .zip) $allBackups = Get-ChildItem -Path $Config.BackupPath -File | Where-Object { $_.Name -like "telegram_bot_backup_*.db" -or $_.Name -like "telegram_bot_backup_*.db.zip" } $removedCount = 0 $freedSpace = 0 foreach ($backup in $allBackups) { if ($backup.LastWriteTime -lt $cutoffDate) { $size = $backup.Length / 1MB Remove-Item -Path $backup.FullName -Force $removedCount++ $freedSpace += $size Write-Log "Removed old backup: $($backup.Name)" -Level "INFO" } } if ($removedCount -gt 0) { Write-Log "Removed $removedCount old backup(s), freed $([math]::Round($freedSpace, 2)) MB" -Level "SUCCESS" } else { Write-Log "No old backups to remove" -Level "INFO" } } catch { Write-Log "Failed to clean up old backups: $_" -Level "WARN" } } function Get-BackupStatistics { Write-Log "Backup Statistics:" -Level "INFO" # Count backups $allBackups = Get-ChildItem -Path $Config.BackupPath -File | Where-Object { $_.Name -like "telegram_bot_backup_*.db" -or $_.Name -like "telegram_bot_backup_*.db.zip" } $totalBackups = $allBackups.Count $totalSize = ($allBackups | Measure-Object -Property Length -Sum).Sum / 1MB Write-Log " Total backups: $totalBackups" -Level "INFO" Write-Log " Total size: $([math]::Round($totalSize, 2)) MB" -Level "INFO" # Latest backup if ($totalBackups -gt 0) { $latest = $allBackups | Sort-Object LastWriteTime -Descending | Select-Object -First 1 Write-Log " Latest backup: $($latest.Name) ($([math]::Round($latest.Length / 1KB, 2)) KB)" -Level "INFO" Write-Log " Created: $($latest.LastWriteTime)" -Level "INFO" } # Oldest backup if ($totalBackups -gt 1) { $oldest = $allBackups | Sort-Object LastWriteTime | Select-Object -First 1 $age = (Get-Date) - $oldest.LastWriteTime Write-Log " Oldest backup: $($oldest.Name) (Age: $([math]::Round($age.TotalDays, 1)) days)" -Level "INFO" } } function Test-BackupIntegrity { param([string]$BackupPath) Write-Log "Testing backup integrity..." -Level "INFO" try { # For ZIP files, test archive if ($BackupPath -like "*.zip") { # Try to extract to temp location $tempExtract = Join-Path $env:TEMP "telegram_bot_backup_test" if (Test-Path $tempExtract) { Remove-Item -Path $tempExtract -Recurse -Force } Expand-Archive -Path $BackupPath -DestinationPath $tempExtract -Force # Check if database file exists in extracted folder $extractedDb = Get-ChildItem -Path $tempExtract -Filter "*.db" -Recurse if ($extractedDb) { Write-Log "Backup integrity test PASSED (ZIP archive valid)" -Level "SUCCESS" Remove-Item -Path $tempExtract -Recurse -Force return $true } else { Write-Log "Backup integrity test FAILED (No database file in archive)" -Level "ERROR" Remove-Item -Path $tempExtract -Recurse -Force return $false } } else { # For .db files, try to open $stream = [System.IO.File]::Open($BackupPath, 'Open', 'Read', 'Read') $stream.Close() Write-Log "Backup integrity test PASSED (Database file readable)" -Level "SUCCESS" return $true } } catch { Write-Log "Backup integrity test FAILED: $_" -Level "ERROR" return $false } } # ============================================================================= # MAIN BACKUP FLOW # ============================================================================= function Main { Write-Host @" ==================================================================== ROA2WEB Telegram Bot - Database Backup SQLite database backup and retention management ==================================================================== "@ -ForegroundColor Cyan Write-Log "Backup started by: $env:USERNAME" -Level "INFO" Write-Log "Backup script: $($MyInvocation.MyCommand.Path)" -Level "INFO" # Check if database exists if (-not (Test-DatabaseFile)) { Write-Log "Backup aborted: Database file not accessible" -Level "ERROR" exit 1 } # Get current database size $dbSize = Get-DatabaseSize Write-Log "Current database size: $dbSize KB" -Level "INFO" try { # Perform backup $backupPath = Backup-Database if ($backupPath) { # Test backup integrity $integrityOk = Test-BackupIntegrity -BackupPath $backupPath if ($integrityOk) { # Cleanup old backups Remove-OldBackups # Show statistics Get-BackupStatistics Write-Log "Backup completed successfully" -Level "SUCCESS" exit 0 } else { Write-Log "Backup created but failed integrity test" -Level "ERROR" exit 1 } } else { Write-Log "Backup failed" -Level "ERROR" exit 1 } } catch { Write-Log "Backup process failed: $_" -Level "ERROR" Write-Log $_.ScriptStackTrace -Level "ERROR" exit 1 } } # Run main backup Main