zscripts-token-savers/zbackup_ec2.ps1
KellyMichels 3546a12564 Evomedia.net Token Savers - initial public release
Config-driven PowerShell scripts to run infrastructure tasks (deploy, restart, backup, diagnostics) yourself instead of having an AI agent orchestrate them, to save agent tokens. Environment specifics live in zconfig.json (gitignored).
2026-07-15 21:33:22 -05:00

137 lines
5.4 KiB
PowerShell

# Evomedia.net Token Savers — https://github.com/kellymichels/zscripts-token-savers
# Created by Kelly Michels · dev@evomedia.net
# Licensed under the MIT License. See LICENSE.
# zbackup_ec2.ps1 — pull backups down from the server: a Postgres dump for projects
# with a "db" config block, plus a zip of server-side data dirs (uploads/archive/dist).
#
# Usage:
# zbackup_ec2 # every project with a remote.path
# zbackup_ec2 <project> [<project> ...]
#
# Output:
# <paths.backupsEc2>\<project>\<timestamp>_<project>_db.sql (projects with a db block)
# <paths.backupsEc2>\<project>\<timestamp>_<project>_files.zip
param(
[Parameter(Position = 0, ValueFromRemainingArguments = $true)]
[string[]]$Projects = @()
)
$ErrorActionPreference = "Stop"
. (Join-Path $PSScriptRoot "ZHelpers.ps1")
Start-ZTracking
$cfg = Get-ZConfig
$PEM_KEY = $cfg.ec2.pemKey
$SSH_TARGET = Get-Ec2Target
$RemoteHome = Get-Ec2Home
$Ec2BackupRoot = $cfg.paths.backupsEc2
if ($Projects.Count -eq 0) {
$Projects = @(Get-ZProjectKeys | Where-Object { $cfg.projects.$_.remote -and $cfg.projects.$_.remote.path })
}
function Get-BackupTimestamp { return Get-Date -Format "yyyyMMdd-HHmmss" }
function Ensure-Ec2BackupDir {
param([string]$ProjectKey)
$dir = Join-Path $Ec2BackupRoot $ProjectKey
if (-not (Test-Path -LiteralPath $dir)) {
New-Item -ItemType Directory -Path $dir -Force | Out-Null
}
return $dir
}
function Invoke-ScpFromEc2 {
param([string]$RemotePath, [string]$LocalPath)
scp -o StrictHostKeyChecking=no -i $PEM_KEY "${SSH_TARGET}:$RemotePath" $LocalPath
if ($LASTEXITCODE -ne 0) { throw "SCP download failed: $RemotePath -> $LocalPath (exit $LASTEXITCODE)" }
}
function Invoke-ProjectEc2Backup {
param([string]$Key)
$proj = Get-ZProject -Key $Key
$remotePath = $proj.remote.path
$composeDir = Get-RemoteComposeDir -Key $Key
$ts = Get-BackupTimestamp
$outDir = Ensure-Ec2BackupDir -ProjectKey $Key
$remoteDb = "$RemoteHome/ec2_${Key}_db_$ts.sql"
$remoteZip = "$RemoteHome/ec2_${Key}_files_$ts.zip"
$localDb = Join-Path $outDir "${ts}_${Key}_db.sql"
$localZip = Join-Path $outDir "${ts}_${Key}_files.zip"
$hasDb = ($proj.db -and $proj.db.user -and $proj.db.name)
Write-Host ""
Write-Host "=== [$($Key.ToUpper())] Server backup ($($proj.label)) ===" -ForegroundColor Cyan
if ($hasDb) {
Write-Host " [1/4] PostgreSQL dump on the server..." -ForegroundColor Yellow
$dumpCmd = "cd $composeDir && sudo docker compose exec -T db pg_dump -U $($proj.db.user) -d $($proj.db.name) > $remoteDb"
Invoke-Ec2Step "pg_dump $($proj.db.name)" $dumpCmd
} else {
Write-Host " [1/4] No db config block - skipping database dump." -ForegroundColor DarkGray
}
Write-Host " [2/4] Zipping server-side data (uploads/archive/dist if present)..." -ForegroundColor Yellow
$zipCmd = @(
"sudo apt-get install -y zip >/dev/null 2>&1",
"FILES=''",
"test -d $remotePath/uploads && FILES=`"`$FILES $remotePath/uploads`"",
"test -d $remotePath/archive && FILES=`"`$FILES $remotePath/archive`"",
"test -d $remotePath/dist && FILES=`"`$FILES $remotePath/dist`"",
"test -f $remotePath/build-version.json && FILES=`"`$FILES $remotePath/build-version.json`"",
"if [ -z `"`$FILES`" ]; then echo 'no data dirs found' | sudo zip $remoteZip - >/dev/null; else sudo zip -r $remoteZip `$FILES; fi"
) -join '; '
try {
Invoke-Ec2Step "zip $Key files" $zipCmd
} catch {
Write-Host " WARNING: files zip failed - continuing: $($_.Exception.Message)" -ForegroundColor DarkYellow
Invoke-Ec2Step "placeholder zip" "sudo apt-get install -y zip >/dev/null 2>&1; echo 'zip failed' | sudo zip $remoteZip -"
}
Write-Host " [3/4] Downloading to local..." -ForegroundColor Yellow
if ($hasDb) { Invoke-ScpFromEc2 -RemotePath $remoteDb -LocalPath $localDb }
Invoke-ScpFromEc2 -RemotePath $remoteZip -LocalPath $localZip
Write-Host " [4/4] Cleaning up remote..." -ForegroundColor Yellow
Invoke-Ec2Step "remove remote temp files" "rm -f $remoteDb $remoteZip"
$result = @{ ok = $true; zipPath = $localZip }
$result.zipMb = if (Test-Path $localZip) { [math]::Round((Get-Item $localZip).Length / 1MB, 2) } else { 0 }
if ($hasDb -and (Test-Path $localDb)) {
$result.dbPath = $localDb
$result.dbMb = [math]::Round((Get-Item $localDb).Length / 1MB, 2)
}
return $result
}
$exitCode = 0
$results = @()
foreach ($key in $Projects) {
try {
$results += @{ project = $key; result = Invoke-ProjectEc2Backup -Key $key }
} catch {
Write-Host " FAILED [$key]: $($_.Exception.Message)" -ForegroundColor Red
$results += @{ project = $key; failed = $_.Exception.Message }
if ($exitCode -eq 0) { $exitCode = 1 }
}
}
Write-Host ""
Write-Host "=== Server backup summary ===" -ForegroundColor Cyan
foreach ($r in $results) {
if ($r.result) {
$line = " $($r.project) ->"
if ($r.result.dbPath) { $line += " $($r.result.dbPath) ($($r.result.dbMb) MB) |" }
if ($r.result.zipPath) { $line += " $($r.result.zipPath) ($($r.result.zipMb) MB)" }
Write-Host $line -ForegroundColor Green
} else {
Write-Host (" {0} -> FAILED: {1}" -f $r.project, $r.failed) -ForegroundColor Red
}
}
Write-Host ""
Stop-ZTracking; exit $exitCode