mirror of
https://github.com/kellymichels/zscripts-token-savers
synced 2026-10-07 07:18:18 +00:00
fix(ps): zbackup explicit target + robust DATABASE_URL parsing + real pg_dump error reporting (#10)
* fix(ps): zbackup explicit target + robust DATABASE_URL parsing; ssh-stderr deploy fix Restores parked, previously-uncommitted PowerShell improvements: - zbackup / zbackup_and_sync require an explicit target: bare invocation now prints usage instead of quietly backing up everything; 'all' does what bare used to (matching zdeploy). setup_backup_schedule.ps1 passes 'all' to the scheduled task; both tolerate switch-style args. - zbackup parses more DATABASE_URL styles: strips surrounding quotes (Prisma convention), accepts postgres:// and postgresql+driver:// schemes, and treats the port as optional (defaults to 5432). - Invoke-Ec2Step survives ssh stderr warnings: under ErrorActionPreference 'Stop', PS 5.1 turns any native stderr line (e.g. Docker's COMPOSE_BAKE deprecation notice) into a terminating NativeCommandError, aborting a deploy that actually succeeded. Drop to Continue locally and flatten stderr so only the real exit code decides success. * fix(ps): zbackup reports the real pg_dump failure, not "No DATABASE_URL" Mirror of the bash fix. Invoke-LocalPgDump now owns all its messaging (caller just captures success) and distinguishes the cases: - no .env / no DATABASE_URL -> calm "No local DATABASE_URL". - database not reachable (connection refused / could not connect / DNS / timeout) -> calm "Local database not running at host:port" - a stopped dev DB is a normal state. - any other failure (version mismatch, auth, missing db) -> the loud, full pg_dump error plus the host:port/db it tried, instead of a bare "pg_dump failed (exit N)" followed by a misleading "No DATABASE_URL". Captures pg_dump stderr (was 2>$null); drops ErrorActionPreference to Continue locally so PS 5.1 doesn't turn that stderr into a terminating NativeCommandError under the script's 'Stop' setting.
This commit is contained in:
parent
5844fc1614
commit
50b7c81736
10
CHANGELOG.md
10
CHANGELOG.md
@ -49,6 +49,16 @@ Notable changes to the Evomedia.net Token Savers.
|
||||
grew up on per-project switches.
|
||||
|
||||
### Changed
|
||||
- **`zbackup` / `zbackup_and_sync` require an explicit target** — running
|
||||
them bare now shows usage instead of quietly backing up every project;
|
||||
`all` does what bare invocation used to (matching `zdeploy`). The
|
||||
scheduled task created by `setup_backup_schedule.ps1` passes `all` —
|
||||
re-run it if your task was registered before this change.
|
||||
- **`zbackup` parses more `DATABASE_URL` styles** — double/single-quoted
|
||||
values (Prisma convention), `postgres://` and `postgresql+driver://`
|
||||
schemes, and URLs without an explicit port (defaults to 5432) all work;
|
||||
previously these skipped the Postgres dump with "Could not parse
|
||||
DATABASE_URL".
|
||||
- **`zkill` / port cleanup kills the whole process tree** — listeners on a
|
||||
project's port are now terminated children-first. Auto-reloading servers
|
||||
(uvicorn/watchfiles, nodemon) spawn workers that inherit the listening
|
||||
|
||||
11
ZHelpers.ps1
11
ZHelpers.ps1
@ -109,7 +109,16 @@ function Invoke-Ec2Step {
|
||||
)
|
||||
$cfg = Get-ZConfig
|
||||
Write-Host " >> $Label" -ForegroundColor DarkCyan
|
||||
ssh -o StrictHostKeyChecking=no -i $cfg.ec2.pemKey (Get-Ec2Target) $Bash
|
||||
# ssh can emit warnings on stderr (e.g. Docker's "COMPOSE_BAKE is
|
||||
# deprecated" notice during a compose build). The deploy runs under
|
||||
# ErrorActionPreference='Stop', and PowerShell 5.1 turns any native stderr
|
||||
# line into a terminating NativeCommandError — aborting the deploy before we
|
||||
# ever read the real exit code, even though the remote step succeeded. Drop
|
||||
# to Continue locally (function-scoped, auto-reverts) and flatten stderr
|
||||
# into normal output, so only the actual exit status decides success.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
ssh -o StrictHostKeyChecking=no -i $cfg.ec2.pemKey (Get-Ec2Target) $Bash 2>&1 |
|
||||
ForEach-Object { "$_" }
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
$msg = "Remote step failed: '$Label' (exit $LASTEXITCODE)."
|
||||
if ($FailHint) { $msg += " $FailHint" }
|
||||
|
||||
@ -46,7 +46,7 @@ if ($existingTask) {
|
||||
$trigger = New-ScheduledTaskTrigger -Daily -At "02:00"
|
||||
$action = New-ScheduledTaskAction `
|
||||
-Execute "powershell.exe" `
|
||||
-Argument "-ExecutionPolicy Bypass -NoProfile -File `"$ScriptPath`""
|
||||
-Argument "-ExecutionPolicy Bypass -NoProfile -File `"$ScriptPath`" all"
|
||||
$settings = New-ScheduledTaskSettingsSet `
|
||||
-AllowStartIfOnBatteries `
|
||||
-DontStopIfGoingOnBatteries `
|
||||
|
||||
65
zbackup.ps1
65
zbackup.ps1
@ -6,8 +6,8 @@
|
||||
# project's .env has a DATABASE_URL) into the backups folder.
|
||||
#
|
||||
# Usage:
|
||||
# zbackup # every project in zconfig.json + this scripts folder
|
||||
# zbackup <project> [<project> ...]
|
||||
# zbackup all # every project in zconfig.json + this scripts folder
|
||||
# zbackup scripts # just this scripts folder ('scripts' is a reserved word)
|
||||
# zbackup pyapp -Tag "pre-migration"
|
||||
#
|
||||
@ -26,8 +26,22 @@ Start-ZTracking
|
||||
$cfg = Get-ZConfig
|
||||
$ProjectsBackupRoot = $cfg.paths.backupsLocal
|
||||
|
||||
$includeScripts = $false
|
||||
# Tolerate switch-style args (zbackup -myproject) from muscle memory.
|
||||
$Projects = @($Projects | ForEach-Object { $_.TrimStart('-') })
|
||||
|
||||
# No args shows usage instead of quietly backing up everything — 'all' is
|
||||
# explicit, matching zdeploy and the other z-commands.
|
||||
if ($Projects.Count -eq 0) {
|
||||
$keys = (Get-ZProjectKeys) -join ', '
|
||||
Write-Host ""
|
||||
Write-Host "Usage: zbackup <project> [<project> ...] | all | scripts [-Tag `"label`"]" -ForegroundColor Yellow
|
||||
Write-Host " Projects in zconfig.json: $keys" -ForegroundColor Gray
|
||||
Write-Host " 'all' backs up every project plus this scripts folder; 'scripts' just this folder." -ForegroundColor Gray
|
||||
Stop-ZTracking; exit 1
|
||||
}
|
||||
|
||||
$includeScripts = $false
|
||||
if ($Projects -contains 'all') {
|
||||
$Projects = @(Get-ZProjectKeys)
|
||||
$includeScripts = $true
|
||||
} elseif ($Projects -contains 'scripts') {
|
||||
@ -53,19 +67,29 @@ function Ensure-BackupDir {
|
||||
|
||||
# Dump the project's Postgres database if its .env declares a DATABASE_URL.
|
||||
# Checks <root>\.env, then <root>\backend\.env (frontend/backend split projects).
|
||||
# Prints its own status line for every outcome; returns $true (dumped) or $false.
|
||||
# A not-running database (connection refused) is a calm, expected skip; a real
|
||||
# failure (version mismatch, auth, missing db) prints the loud pg_dump error.
|
||||
function Invoke-LocalPgDump {
|
||||
param([string]$Root, [string]$OutPath)
|
||||
$envFile = Join-Path $Root ".env"
|
||||
if (-not (Test-Path -LiteralPath $envFile)) { $envFile = Join-Path $Root "backend\.env" }
|
||||
if (-not (Test-Path -LiteralPath $envFile)) { return $false }
|
||||
if (-not (Test-Path -LiteralPath $envFile)) {
|
||||
Write-Host " No local DATABASE_URL - source-only backup." -ForegroundColor DarkGray; return $false
|
||||
}
|
||||
$dbLine = @(Get-Content -LiteralPath $envFile | Where-Object { $_ -match "^DATABASE_URL=" } | Select-Object -First 1)
|
||||
if ($dbLine.Count -eq 0) { return $false }
|
||||
if ($dbLine.Count -eq 0) {
|
||||
Write-Host " No local DATABASE_URL - source-only backup." -ForegroundColor DarkGray; return $false
|
||||
}
|
||||
|
||||
$dbUrl = ($dbLine[0] -replace "^DATABASE_URL=", "").Trim()
|
||||
$dbUrl = $dbUrl -replace '^postgresql\+[^:]+://', 'postgresql://'
|
||||
# Strip surrounding quotes (Prisma-style .env values are double-quoted),
|
||||
# accept postgres:// and postgresql+driver:// schemes, and treat the
|
||||
# port as optional (Postgres default 5432).
|
||||
$dbUrl = ($dbLine[0] -replace "^DATABASE_URL=", "").Trim().Trim('"').Trim("'")
|
||||
$dbUrl = $dbUrl -replace '^postgres(ql)?(\+[^:]+)?://', 'postgresql://'
|
||||
$rx = [regex]::Match(
|
||||
$dbUrl,
|
||||
'^postgresql://(?<user>[^:]+):(?<pass>[^@]+)@(?<host>[^:]+):(?<port>\d+)/(?<db>[^?]+)'
|
||||
'^postgresql://(?<user>[^:@/]+):(?<pass>[^@]+)@(?<host>[^:/?]+)(:(?<port>\d+))?/(?<db>[^?\s]+)'
|
||||
)
|
||||
if (-not $rx.Success) {
|
||||
Write-Host ' Could not parse DATABASE_URL - skipping PG backup' -ForegroundColor Red
|
||||
@ -74,7 +98,7 @@ function Invoke-LocalPgDump {
|
||||
$env:PGPASSWORD = [Uri]::UnescapeDataString($rx.Groups['pass'].Value)
|
||||
$pgUser = $rx.Groups['user'].Value
|
||||
$pgHost = $rx.Groups['host'].Value
|
||||
$pgPort = $rx.Groups['port'].Value
|
||||
$pgPort = if ($rx.Groups['port'].Success) { $rx.Groups['port'].Value } else { '5432' }
|
||||
$pgDb = $rx.Groups['db'].Value
|
||||
|
||||
$pgDump = "pg_dump"
|
||||
@ -87,16 +111,31 @@ function Invoke-LocalPgDump {
|
||||
foreach ($candidate in $pgBinPaths) {
|
||||
if (Test-Path $candidate) { $pgDump = $candidate; break }
|
||||
}
|
||||
& $pgDump -h $pgHost -p $pgPort -U $pgUser -d $pgDb -F p -f $OutPath 2>$null
|
||||
# Capture stderr so a failure is diagnosable (version mismatch, unreachable
|
||||
# host, auth) instead of a bare "pg_dump failed". PS 5.1 turns native stderr
|
||||
# into a terminating NativeCommandError under 'Stop', so drop to Continue
|
||||
# locally (function-scoped, auto-reverts) while running pg_dump.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$errOut = & $pgDump -h $pgHost -p $pgPort -U $pgUser -d $pgDb -F p -f $OutPath 2>&1
|
||||
$ok = ($LASTEXITCODE -eq 0) -and (Test-Path -LiteralPath $OutPath)
|
||||
Remove-Item Env:\PGPASSWORD -ErrorAction SilentlyContinue
|
||||
if ($ok) {
|
||||
$size = [math]::Round((Get-Item $OutPath).Length / 1KB, 1)
|
||||
Write-Host " PostgreSQL dump: ${size} KB" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " pg_dump failed (exit $LASTEXITCODE)" -ForegroundColor Red
|
||||
return $true
|
||||
}
|
||||
return $ok
|
||||
$errText = ($errOut | Out-String).Trim()
|
||||
# Not reachable (usually just not running locally) is expected - stay calm.
|
||||
if ($errText -match '(?i)connection refused|could not connect|no route to host|could not translate host|timeout expired') {
|
||||
Write-Host " Local database not running at ${pgHost}:${pgPort} - source-only backup." -ForegroundColor DarkGray
|
||||
return $false
|
||||
}
|
||||
# A real failure (version mismatch, auth, missing db) - show the reason.
|
||||
Write-Host " pg_dump failed (${pgHost}:${pgPort}/${pgDb} as ${pgUser}):" -ForegroundColor Red
|
||||
foreach ($line in ($errText -split '\r?\n' | Where-Object { $_.Trim() -ne '' })) {
|
||||
Write-Host " $line" -ForegroundColor Red
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function Invoke-ProjectBackup {
|
||||
@ -124,8 +163,6 @@ function Invoke-ProjectBackup {
|
||||
$dbDumpPath = Join-Path $dumpDir "database_pg.sql"
|
||||
if (Invoke-LocalPgDump -Root $root -OutPath $dbDumpPath) {
|
||||
$extraFiles += $dbDumpPath
|
||||
} else {
|
||||
Write-Host " No local DATABASE_URL - source-only backup." -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
Write-Host " [2/3] Archiving source..." -ForegroundColor Yellow
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
# zbackup_and_sync.ps1 — run backups, then sync the backups folder offsite.
|
||||
#
|
||||
# Usage:
|
||||
# zbackup_and_sync.ps1 # backup everything + sync
|
||||
# zbackup_and_sync.ps1 <project> [<project> ...]
|
||||
# zbackup_and_sync.ps1 all # backup everything + sync
|
||||
#
|
||||
# Scheduled Task example (see setup_backup_schedule.ps1):
|
||||
# powershell -ExecutionPolicy Bypass -NoProfile -File "<scriptsRoot>\zbackup_and_sync.ps1"
|
||||
# powershell -ExecutionPolicy Bypass -NoProfile -File "<scriptsRoot>\zbackup_and_sync.ps1" all
|
||||
|
||||
param(
|
||||
[Parameter(Position = 0, ValueFromRemainingArguments = $true)]
|
||||
@ -21,6 +21,18 @@ $ScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
. (Join-Path $ScriptRoot "ZHelpers.ps1")
|
||||
Start-ZTracking
|
||||
|
||||
# Tolerate switch-style args from muscle memory; require an explicit target
|
||||
# ('all' included) — same convention as zbackup/zdeploy.
|
||||
$Projects = @($Projects | ForEach-Object { $_.TrimStart('-') })
|
||||
if ($Projects.Count -eq 0) {
|
||||
$keys = (Get-ZProjectKeys) -join ', '
|
||||
Write-Host ""
|
||||
Write-Host "Usage: zbackup_and_sync <project> [<project> ...] | all" -ForegroundColor Yellow
|
||||
Write-Host " Projects in zconfig.json: $keys" -ForegroundColor Gray
|
||||
Write-Host " 'all' backs up every project plus the scripts folder, then syncs offsite." -ForegroundColor Gray
|
||||
Stop-ZTracking; exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "============================================" -ForegroundColor Cyan
|
||||
Write-Host " Backup & Sync - $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Cyan
|
||||
@ -29,11 +41,7 @@ Write-Host ""
|
||||
|
||||
Write-Host "[1/2] Running backups..." -ForegroundColor Yellow
|
||||
$backupPath = Join-Path $ScriptRoot "zbackup.ps1"
|
||||
if ($Projects.Count -gt 0) {
|
||||
& powershell -NoProfile -File $backupPath @Projects
|
||||
} else {
|
||||
& powershell -NoProfile -File $backupPath
|
||||
}
|
||||
$backupExitCode = $LASTEXITCODE
|
||||
|
||||
if ($backupExitCode -ne 0) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user