Repository navigation
Fix backup bugs, harden runtime and add essential features - #34
Merged
Merged
Conversation
itbm
marked this pull request as ready for review
September 28, 2026 23:27
itbm
pushed a commit
that referenced
this pull request
Oct 20, 2026
- new xfail: a run stopped by COMMAND_TIMEOUT must still run cleanup (backup-error script hook and URL, no dump left in /tmp); today go-cron SIGKILLs the shell, so its EXIT trap never runs - '* * * * *' really fires (the only minute-long test) - @monthly, @Yearly and @annually are accepted - COMMAND_TIMEOUT does not affect one-off backups, as the README says - timeout tests match 'timed out after Ns' rather than the full log line, so a reworded message still passes - xfail calls moved to just before the step that exposes each bug Against PR #34 the file has no failures; its four xfails pass there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UdSRPLaq3mTA6Djdzte7C5
itbm
pushed a commit
that referenced
this pull request
Oct 20, 2026
New tests:
- S3_BUCKET with a sub-path (the README calls it a bucket path): backup
key, BACKUP_S3_URI, retention and restore all work
- xfails for S3_PREFIX with a trailing slash ('backups/'): every backup
fails against MinIO (XMinioInvalidObjectName) because the key has
'//', and AWS S3 would store such keys
- xfails for a leading slash ('/backups'): the request path and
BACKUP_S3_URI contain '//', and retention lists a prefix that matches
nothing, so the backup exits 2 after uploading
- the custom-region trace now also covers retention's list and delete
Removes four tests duplicated in validation.sh, retention.sh, smoke.sh
and backup.sh. Against PR #34 the file has no failures; its seven
prefix xfails pass there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UdSRPLaq3mTA6Djdzte7C5
itbm
pushed a commit
that referenced
this pull request
Oct 20, 2026
- an encrypted POSTGRES_DATABASE=all backup is stored only as all_<ts>.sql.gz.enc, decrypts with the README's command, and restores both databases, the role and its ownership into a fresh server - tests no longer pin the temporary file in the 'Encrypting ...' log line; the .enc object, its Salted__ header and the round trips prove encryption - the encryption-failure test uses a stand-in openssl that fails 'enc', instead of blocking a fixed temporary path - the legacy xfail is renamed for the behaviour it expects, and xfail calls sit just before the step that exposes each bug All three tests that failed against PR #34 only because of the old temporary path now pass on both images. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UdSRPLaq3mTA6Djdzte7C5
itbm
pushed a commit
that referenced
this pull request
Oct 20, 2026
- the error-event scrubbing test now also covers backup-error, with ENCRYPTION_PASSWORD, AWS_SESSION_TOKEN and AWS_ROLE_ARN set, and HOOK_EXIT_CODE=2 - new test: a directory at $HOOKS_DIR/<event> (which passes a plain -x check) fails the job closed with nothing uploaded - the scheduled-hooks test judges only events recorded before it stops the container. On PR #34 docker stop forwards SIGTERM to the running backup, which then correctly fires backup-error; the old check failed whenever the stop landed mid-run - the HOOK_INHERIT_ENV xfail moves to just before the step that exposes the bug Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UdSRPLaq3mTA6Djdzte7C5
itbm
pushed a commit
that referenced
this pull request
Oct 20, 2026
- new test: with S3_CA_BUNDLE and verification on, DELETE_OLDER_THAN deletes an old object on minio-tls and keeps a newer one and the new backup (only S3_SSL_VERIFY=no exercised retention over TLS before) - renames the explicit S3_SSL_VERIFY=yes test for what it proves - drops per-test database clean-up helpers now that run.sh does it Passes on both the master and PR #34 images. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UdSRPLaq3mTA6Djdzte7C5
- Treat empty variables as unset. The README's Kubernetes example sets values to "", which made DELETE_OLDER_THAN delete every backup older than midnight, broke S3_ENDPOINT, enabled encryption with an empty password and stopped SCHEDULE from starting. - Move shared validation and AWS/PostgreSQL setup into common.sh. - Normalise S3_PREFIX so an empty or slashed prefix no longer produces keys such as s3://bucket//file. - Validate DELETE_OLDER_THAN and PARALLEL_JOBS before any work starts. - Use a per-run temporary directory so concurrent runs cannot collide. - Stop plain SQL restores at the first error (RESTORE_ON_ERROR_STOP), fail when DROP DATABASE fails, create the database only when it is missing, and reject unsupported formats before dropping anything. - go-cron: run jobs in their own process group so COMMAND_TIMEOUT kills pg_dump and aws too, keep all job output, and shut down gracefully on SIGTERM/SIGINT so cleanup and error hooks run. - Pin the Go dependency with go.mod/go.sum and add Go unit tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CA15ULnH4kuk41vkprNUm1
- Add DELETE_MATCH_DATABASE (default no, keeping current behaviour) so DELETE_OLDER_THAN can be limited to <POSTGRES_DATABASE>_* files. - Never delete the backup uploaded by the current run, fail the backup when a delete fails, and log how many files were deleted and kept. - Check each dump before upload: reject empty dumps, custom-format dumps that pg_restore --list cannot read, and corrupt gzip output. - Add a CI workflow for pushes and pull requests: ShellCheck, shell tests under bash and BusyBox sh in the built image, gofmt, go vet, go test -race and a Docker build. - Extend Dependabot to Go modules and GitHub Actions. - Fix ShellCheck warnings in the tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CA15ULnH4kuk41vkprNUm1
- Read POSTGRES_USER, POSTGRES_PASSWORD, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY and ENCRYPTION_PASSWORD from *_FILE variables for Docker and Kubernetes secrets. - BACKUP_FILE=latest restores the newest <POSTGRES_DATABASE>_<timestamp> backup in S3_PREFIX. Hooks see the resolved file name. - Install tzdata so TZ and CRON_TZ in SCHEDULE work. - BACKUP_ON_START=yes runs a backup when a scheduled container starts. - S3_STORAGE_CLASS, S3_SSE and S3_SSE_KMS_KEY_ID set upload options. - Run one-off backups and restores through go-cron --once, so stopping the container stops pg_dump, psql and aws, runs cleanup and error hooks, and keeps the job's exit code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CA15ULnH4kuk41vkprNUm1
The runner's ShellCheck does not support the busybox dialect, so the Shell scripts job failed with "Unknown shell: busybox". Install shellcheck-py 0.11.0.1 in a venv and run that binary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CA15ULnH4kuk41vkprNUm1
itbm
force-pushed
the
claude/wizardly-lovelace-70vymp
branch
from
October 20, 2026 20:38
62aad10 to
ed92a24
Compare
pg_dump 17 and later write "SET transaction_timeout = 0;" into the header of every plain dump. PostgreSQL 16 and older reject that setting, so with ON_ERROR_STOP now on by default, restoring such a dump into an older server stopped at the header and nothing was restored. For servers older than 17, drop that line from each dump header before psql reads it. Table data and later statements are passed through as they are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UdSRPLaq3mTA6Djdzte7C5
The end-to-end suite from master marks 41 known bugs as xfail. With this branch, 28 of those tests pass, so the run reported them as XPASS. Their markers are removed so the suite now checks these fixes. The other 13 are still open and keep their markers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UdSRPLaq3mTA6Djdzte7C5
An empty COMPRESSION_CMD left a bare pipe, so every plain backup exited 141. An empty DECOMPRESSION_CMD made restore.sh try to run the downloaded file. An empty AWS_CA_BUNDLE made the AWS CLI skip certificate checks. Fall back to gzip and "gunzip -c", and unset an empty AWS_CA_BUNDLE. Remove the end-to-end xfail markers for these three cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CA15ULnH4kuk41vkprNUm1
POSTGRES_EXTRA_OPTS is passed to pg_dump and psql alike, and -x means "no privileges" to pg_dump but expanded output to psql. With -x, psql prints the version as "server_version_num|160015", the comparison failed, and a plain restore into PostgreSQL 16 or older kept the transaction_timeout line and stopped there. Take the value after the last "|", and leave the dump unchanged when the version is not a number. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UdSRPLaq3mTA6Djdzte7C5
Fixes found by reviewing this branch: - The transaction_timeout filter now drops the line only where pg_dump writes it: right after its statement_timeout, lock_timeout and idle_in_transaction_session_timeout settings. The header tracking it replaces could also drop that line from a function body or --inserts data after a line starting with \connect, and it missed sections of pg_dumpall output from pg_dump 17.0-17.5, which has no \restrict line. The unit test now checks the exact output for these cases. - DELETE_MATCH_DATABASE=yes deleted other databases' backups whose names start with "<POSTGRES_DATABASE>_", such as app_archive_... for a database named app. It now requires the timestamp, using the same check as BACKUP_FILE=latest. - An empty *_FILE secret is an error. An empty ENCRYPTION_PASSWORD_FILE turned encryption off and uploaded a plain backup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UdSRPLaq3mTA6Djdzte7C5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR fixes bugs that could lose data or produce silently broken backups, makes the runtime more robust, adds CI, and adds a few essential features. Its commits are listed by phase below.
Phase 1: bug fixes (
424d6ae)"". Before this change:DELETE_OLDER_THAN=""deleted every backup older than midnight.S3_ENDPOINT=""broke everyawscall.ENCRYPTION_PASSWORD=""encrypted backups with an empty password.SCHEDULE=""stopped the scheduler from starting.COMMAND_TIMEOUTnow stops the backup. Previously onlyshwas killed, andpg_dumpandawskept running. Jobs now run in their own process group, which getsSIGTERMand thenSIGKILL.SIGTERMandSIGINT. A running job is terminated cleanly, so temporary files are removed and error hooks fire. Job output is no longer lost.RESTORE_ON_ERROR_STOP, on by default for a single database). Previously a partial restore reported success.DROP DATABASEnow fails the job.CREATE DATABASEruns only when the database is missing. Unsupported formats are rejected before anything is dropped.S3_PREFIXis normalised, so an empty or slashed prefix no longer producess3://bucket//file.DELETE_OLDER_THANandPARALLEL_JOBSare checked before any work starts.go.modandgo.sumcommitted.common.shnow holds the validation and setup code that was duplicated in the backup and restore scripts.Phase 2: stability (
bdb91be)DELETE_MATCH_DATABASE=yes(opt-in) limits retention to this database's backups, named<POSTGRES_DATABASE>_<timestamp>. The default behaviour is unchanged.pg_restore --listcan't read, and corrupt gzip output are all rejected.shin the built image,gofmt,go vet,go test -raceand a Docker build.Phase 3: features (
b9a7780,ed92a24)*_FILEvariables forPOSTGRES_USER,POSTGRES_PASSWORD,S3_ACCESS_KEY_ID,S3_SECRET_ACCESS_KEYandENCRYPTION_PASSWORD, so Docker and Kubernetes secrets work.BACKUP_FILE=latestrestores the newest backup of the database.tzdatais installed, soTZandCRON_TZ=inSCHEDULEwork.BACKUP_ON_START=yesruns a backup when a scheduled container starts.S3_STORAGE_CLASS,S3_SSEandS3_SSE_KMS_KEY_ID.go-cron --once. Stopping the container now stopspg_dump,psqlandaws, runs cleanup and error hooks, and keeps the job's exit code.Phase 4: after rebasing onto the end-to-end suite (
302c240,6e04e9f,be716d8,c5a2f47,8aa9d02)This branch is rebased onto
master, which now has the end-to-end suite from #35.302c240,c5a2f47,8aa9d02).pg_dump17 and later writeSET transaction_timeout = 0;into every plain dump, which those servers reject. With stop-on-error on by default, such a restore stopped at the header and restored nothing. For servers older than 17 that line is now dropped wherepg_dumpwrites it, after its other timeout settings. Table data and function bodies are left alone.COMPRESSION_CMD,DECOMPRESSION_CMDandAWS_CA_BUNDLEare treated as unset (be716d8).DELETE_MATCH_DATABASE=yesrequires the timestamp (8aa9d02), so a database namedappno longer deletesapp_archive_…backups.*_FILEsecret is an error (8aa9d02). An emptyENCRYPTION_PASSWORD_FILEused to turn encryption off.xfailmarkers removed (6e04e9f,be716d8). The suite marks 41 known bugs onmaster; this branch fixes 31 of them, so their tests now pass and check these fixes. The other 10 are fixed in Fix the remaining known bugs from the end-to-end suite #36, which is stacked on this PR.Behaviour changes to note
DROP_DATABASE=yeswithCREATE_DATABASE=yes, or setRESTORE_ON_ERROR_STOP=noto keep the old behaviour.POSTGRES_DATABASE=allstays permissive by default.DROP DATABASEor retention delete now fails the job instead of only printing a warning.Testing
test/e2e, run by CI for each case file): every test passes except the 10 known bugs that Fix the remaining known bugs from the end-to-end suite #36 fixes, which stay markedxfail.sh test/hooks_test.shruns 152 tests (55 before this PR). They pass under bash, and under BusyBoxshinside the built image. I ran the Phase 1 tests against the original scripts: 17 fail there, which confirms they catch these bugs.go test -racepasses. The tests cover the process-group kill on timeout, theTERMtrap running on shutdown, complete output capture and exit-code mapping.postgres:18-alpineand an S3 mock, before the suite existed:*_FILEsecrets;latestwith drop, create and parallel jobs;DELETE_MATCH_DATABASEkept the unrelated file and the just-uploaded backup;COMMAND_TIMEOUT;docker stopduring a scheduled, initial or one-off backup terminatespg_dumpand firesbackup-error;Not included
These would be worth doing later:
🤖 Generated with Claude Code
https://claude.ai/code/session_01CA15ULnH4kuk41vkprNUm1