d35c806d5b
- Added comprehensive comments and JSDoc-style documentation to NotificationProvider, ThemeProvider, UserProvider, and WebsiteThemeProvider for better clarity and maintainability. - Improved type definitions in index.ts for better code understanding and usage. - Introduced Docker support with Dockerfiles for various services including AI server, signaling server, and browser-use service. - Created a docker-compose.yml file to orchestrate multiple services including PostgreSQL, AI, scraper, and frontend. - Added a startup guide (startup.txt) for setting up the CRM environment on Ubuntu with Docker. - Included a .dockerignore file to exclude unnecessary files from Docker builds.
145 lines
6.0 KiB
PowerShell
145 lines
6.0 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
PostgreSQL database backup with retention management and logging.
|
|
.DESCRIPTION
|
|
Uses pg_dump (custom format) to back up the CRM database. Tracks the
|
|
backup in the backup_logs table and auto-prunes files older than
|
|
$RetentionDays. Reads DATABASE_URL from the environment or .env.local.
|
|
.PARAMETER BackupDir
|
|
Directory where backup files are stored (default: ../backups).
|
|
.PARAMETER RetentionDays
|
|
Number of days to retain backups (default: 30).
|
|
#>
|
|
param(
|
|
[string]$BackupDir = "$PSScriptRoot\..\backups",
|
|
[int]$RetentionDays = 30
|
|
)
|
|
|
|
# Fail fast on any error
|
|
$ErrorActionPreference = "Stop"
|
|
$startTime = Get-Date
|
|
|
|
# ── Ensure backup directory exists ────────────────────────────────
|
|
if (-not (Test-Path $BackupDir)) {
|
|
New-Item -ItemType Directory -Path $BackupDir -Force | Out-Null
|
|
}
|
|
|
|
# ── Determine database URL from env or .env.local ─────────────────
|
|
$dbUrl = $env:DATABASE_URL
|
|
if (-not $dbUrl -and (Test-Path "$PSScriptRoot\..\.env.local")) {
|
|
$envContent = Get-Content "$PSScriptRoot\..\.env.local" -Raw
|
|
if ($envContent -match 'DATABASE_URL=(.+)') {
|
|
$dbUrl = $Matches[1].Trim()
|
|
}
|
|
}
|
|
|
|
if (-not $dbUrl) {
|
|
Write-Error "DATABASE_URL not found. Set it as an environment variable or in .env.local"
|
|
exit 1
|
|
}
|
|
|
|
# ── Parse the database URL into connection components ─────────────
|
|
$uri = [System.Uri]$dbUrl
|
|
$pgUser = $uri.UserInfo.Split(':')[0]
|
|
$pgPass = $uri.UserInfo.Split(':')[1]
|
|
$pgHost = $uri.Host
|
|
$pgPort = $uri.Port
|
|
$pgDb = $uri.AbsolutePath.TrimStart('/')
|
|
|
|
# pg_dump reads password from this env var (avoids interactive prompt)
|
|
$env:PGPASSWORD = $pgPass
|
|
|
|
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
|
|
$filename = "crm_backup_$timestamp.sql"
|
|
$filepath = Join-Path $BackupDir $filename
|
|
|
|
Write-Output "Starting backup of database '$pgDb' to $filepath"
|
|
Write-Output "Database: $pgHost:$pgPort/$pgDb"
|
|
|
|
try {
|
|
# ── Run pg_dump (custom format) ──────────────────────────────
|
|
# Check PATH first, then fall back to the bundled psql location
|
|
$pgDumpPath = if (Get-Command pg_dump -ErrorAction SilentlyContinue) {
|
|
"pg_dump"
|
|
} else {
|
|
"$env:TEMP\pg\pgsql\bin\pg_dump.exe"
|
|
}
|
|
|
|
# Custom format (-Fc) for compressed, restorable output
|
|
$output = & $pgDumpPath -h $pgHost -p $pgPort -U $pgUser -d $pgDb --format=custom --verbose --file $filename 2>&1
|
|
$exitCode = $LASTEXITCODE
|
|
|
|
if ($exitCode -ne 0 -or -not (Test-Path $filename)) {
|
|
throw "pg_dump failed with exit code $exitCode"
|
|
}
|
|
|
|
# Move to final backup location (dg_dump writes to CWD first)
|
|
Move-Item -Path $filename -Destination $filepath -Force
|
|
$fileInfo = Get-Item $filepath
|
|
$fileSize = $fileInfo.Length
|
|
|
|
# ── Verify backup integrity ──────────────────────────────────
|
|
# Run a schema-only dump against the same file to check it's valid custom format
|
|
$verifyOutput = & $pgDumpPath -h $pgHost -p $pgPort -U $pgUser -d $pgDb --format=custom --schema-only --file "$filename.verify" 2>&1
|
|
$verifyExitCode = $LASTEXITCODE
|
|
if (Test-Path "$filename.verify") { Remove-Item "$filename.verify" -Force }
|
|
|
|
$verificationStatus = if ($verifyExitCode -eq 0) { "verified" } else { "failed" }
|
|
|
|
# Calculate duration
|
|
$endTime = Get-Date
|
|
$durationSeconds = [math]::Round(($endTime - $startTime).TotalSeconds)
|
|
|
|
# ── Log backup to database ───────────────────────────────────
|
|
# Records metadata in backup_logs table for audit trail
|
|
$logQuery = @"
|
|
INSERT INTO backup_logs (backup_type, status, file_name, file_size_bytes, pg_dump_exit_code, verification_status, started_at, completed_at, duration_seconds, retention_days)
|
|
VALUES ('full', 'completed', '$filename', $fileSize, $exitCode, '$verificationStatus', '$($startTime.ToString("yyyy-MM-dd HH:mm:ss"))', '$($endTime.ToString("yyyy-MM-dd HH:mm:ss"))', $durationSeconds, $RetentionDays);
|
|
"@
|
|
|
|
# Try to log to database (may fail if db is not reachable for logging, that's OK)
|
|
try {
|
|
$env:PGPASSWORD = $pgPass
|
|
& $env:TEMP\pg\pgsql\bin\psql.exe -h $pgHost -p $pgPort -U $pgUser -d $pgDb -c $logQuery 2>&1 | Out-Null
|
|
} catch {
|
|
Write-Warning "Could not log backup to database: $_"
|
|
}
|
|
|
|
Write-Output "Backup completed successfully:"
|
|
Write-Output " File: $filename"
|
|
Write-Output " Size: $([math]::Round($fileSize / 1MB, 2)) MB"
|
|
Write-Output " Duration: ${durationSeconds}s"
|
|
Write-Output " Status: $verificationStatus"
|
|
|
|
# ── Cleanup old backups beyond retention period ──────────────
|
|
$cutoff = (Get-Date).AddDays(-$RetentionDays)
|
|
$oldBackups = Get-ChildItem $BackupDir -Filter "crm_backup_*.sql" | Where-Object {
|
|
$_.CreationTime -lt $cutoff
|
|
}
|
|
foreach ($old in $oldBackups) {
|
|
Remove-Item $old.FullName -Force
|
|
Write-Output "Removed old backup: $($old.Name)"
|
|
}
|
|
|
|
} catch {
|
|
Write-Error "Backup failed: $_"
|
|
|
|
# Log failure to database for monitoring
|
|
$endTime = Get-Date
|
|
$durationSeconds = [math]::Round(($endTime - $startTime).TotalSeconds)
|
|
$errorMsg = $_.ToString().Replace("'", "''")
|
|
$failQuery = @"
|
|
INSERT INTO backup_logs (backup_type, status, file_name, error_message, pg_dump_exit_code, verification_status, started_at, completed_at, duration_seconds, retention_days)
|
|
VALUES ('full', 'failed', '$filename', '$errorMsg', $exitCode, 'failed', '$($startTime.ToString("yyyy-MM-dd HH:mm:ss"))', '$($endTime.ToString("yyyy-MM-dd HH:mm:ss"))', $durationSeconds, $RetentionDays);
|
|
"@
|
|
try {
|
|
$env:PGPASSWORD = $pgPass
|
|
& $env:TEMP\pg\pgsql\bin\psql.exe -h $pgHost -p $pgPort -U $pgUser -d $pgDb -c $failQuery 2>&1 | Out-Null
|
|
} catch {}
|
|
|
|
exit 1
|
|
} finally {
|
|
# Clean up the password environment variable for security
|
|
Remove-Item "Env:PGPASSWORD" -ErrorAction SilentlyContinue
|
|
}
|