From 84523c8e4deb076f8774ddbe4e61b854c41249bc Mon Sep 17 00:00:00 2001 From: KellyMichels Date: Thu, 23 Jul 2026 00:15:19 -0500 Subject: [PATCH] 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. --- CHANGELOG.md | 10 ++++++++++ ZHelpers.ps1 | 11 ++++++++++- setup_backup_schedule.ps1 | 2 +- zbackup.ps1 | 29 +++++++++++++++++++++++------ zbackup_and_sync.ps1 | 22 +++++++++++++++------- 5 files changed, 59 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebc3d52..adbac75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,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 diff --git a/ZHelpers.ps1 b/ZHelpers.ps1 index 7bd7467..dd2bfde 100644 --- a/ZHelpers.ps1 +++ b/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" } diff --git a/setup_backup_schedule.ps1 b/setup_backup_schedule.ps1 index c6a15b6..90e904f 100644 --- a/setup_backup_schedule.ps1 +++ b/setup_backup_schedule.ps1 @@ -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 ` diff --git a/zbackup.ps1 b/zbackup.ps1 index 09c9bfb..037d28f 100644 --- a/zbackup.ps1 +++ b/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 [ ...] +# 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 [ ...] | 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') { @@ -61,11 +75,14 @@ function Invoke-LocalPgDump { $dbLine = @(Get-Content -LiteralPath $envFile | Where-Object { $_ -match "^DATABASE_URL=" } | Select-Object -First 1) if ($dbLine.Count -eq 0) { 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://(?[^:]+):(?[^@]+)@(?[^:]+):(?\d+)/(?[^?]+)' + '^postgresql://(?[^:@/]+):(?[^@]+)@(?[^:/?]+)(:(?\d+))?/(?[^?\s]+)' ) if (-not $rx.Success) { Write-Host ' Could not parse DATABASE_URL - skipping PG backup' -ForegroundColor Red @@ -74,7 +91,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" diff --git a/zbackup_and_sync.ps1 b/zbackup_and_sync.ps1 index cb0576b..19d9af1 100644 --- a/zbackup_and_sync.ps1 +++ b/zbackup_and_sync.ps1 @@ -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 [ ...] +# zbackup_and_sync.ps1 all # backup everything + sync # # Scheduled Task example (see setup_backup_schedule.ps1): -# powershell -ExecutionPolicy Bypass -NoProfile -File "\zbackup_and_sync.ps1" +# powershell -ExecutionPolicy Bypass -NoProfile -File "\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 [ ...] | 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 -} +& powershell -NoProfile -File $backupPath @Projects $backupExitCode = $LASTEXITCODE if ($backupExitCode -ne 0) {