feat(db): bring ad-hoc dump/restore on-box as rig db (Closes #15)
#18
No reviewers
Labels
No labels
attention
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-ruling
needs-triage
offsite
post-merge
ready
release
scope:bootstrap
scope:coolify
scope:db
scope:docs
scope:drill
scope:installer
scope:labels
scope:platform
scope:runner
scope:users
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/rig#18
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/rig-db"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #15.
Plan
Bring ad-hoc PostgreSQL dump/restore on-box as
rig dbsubcommands — the imperative, interactive counterpart torig coolify backup install(which is the scheduled, declarative, forensics-only path). This is the "give me a copy of that database right now" / "put this artifact back" verb an operator reaches for by hand, and it conceptually retires cast's off-boxrestore-db.sh: the restore logic now lives with the box that runs the containers, connecting as the container's own superuser instead of assuming a role name from off-box.Key design decisions:
--no-owner --no-aclon every dump (mandatory). A cross-instance restore runs as the target's superuser, and Coolify randomizes that role per database. A plain dump carriesALTER OWNER/GRANTstatements naming the source's role, which does not exist on the target — underON_ERROR_STOP=1that aborts the entire restore on the first such statement. Stripping ownership/ACLs makes the dump describe data and schema, portable onto any instance. Dumps also use--clean --if-existsso a re-restore is idempotent.postgres.$POSTGRES_USER/$POSTGRES_DBare read inside the container via a single-quotedsh -c '...'— the container's own environment, not the host's. On the next container a hardcodedpostgresis simply the wrong role.y/N;--yes(or--force) is the automation bypass. Artifact existence/non-emptiness is validated before the prompt and before anything touches the DB — and, being a pure filesystem check, before the root/docker guards too, so a fat-fingered path fails cheaply and is unit-testable without root.[db]arg for shared containers. The optional third arg targets a named database in a shared Postgres (e.g. aumamidatabase alongside others). It is passed into the container as an env var (RIG_TARGET_DB) rather than spliced into the command string; empty falls back to the container's own$POSTGRES_DB.pipefail(a failingpg_dumpotherwise exits 0 through the pipe andgzipfaithfully compresses the truncated output into a valid-looking.gz), writes to a sibling temp promoted only on success, and refuses to keep an empty artifact.restoreruns withON_ERROR_STOP=1so a bad restore fails loudly instead of half-applying and reporting success.House style: args validated before the root check;
log/warn/diehelpers (usage errors exit 2, refusals/runtime exit 1); Debian-family warn sourcing/etc/os-releaseonly in a subshell; guards are root, docker, and gzip/gunzip.What changed
commands/db.sh(new) —dumpandrestoresubcommands with a shared guard block.docker exec(dump) /docker exec -i(restore, for the stdin pipe).bin/rig— newdbcommand dispatchingdump/restoretocommands/db.sh; bad/missing subcommand → usage on stderr, exit 2. Added adbstanza to theusage()heredoc.test/cli.sh— assertions in the house style: baredb→ usage exit 2;db --helpexit 0; bad subcommand exit 2; missing/too manyargs exit 2; unknown flag exit 2; non-root refusals; a nonexistent-artifact restore failing before the docker/root path; and regression greps pinning the embedded command strings (--no-owner --no-acl, container-own$POSTGRES_USER/$POSTGRES_DB,ON_ERROR_STOP=1).README.md— a### rig dbsection covering the imperative-vs-declarative contrast, the--no-owner --no-aclrationale, container-own-superuser, the confirm gate, and the[db]shared-container arg.Testing
bash test/cli.sh→ 82 passed, 0 failed.shellcheck -xoverbin/*+**/*.sh→ clean (exit 0).The runtime
docker execdump/restore paths (a live pg_dump/psql against a real container) are covered by the end-to-end rehearsal, not unit tests — the dependency-free harness cannot fabricate a running Postgres container or root. What the unit tests do cover is that arg validation, the confirm gate's bypass, and the artifact pre-check are all reachable and correct before that runtime path, and that the mandatory dump/restore flags stay embedded.Added executable verification that dump/restore actually round-trips (not just arg parsing).
test/db-integration.sh— a real Docker-backed probe. It stands up two throwaway Postgres containers whose superusers differ by construction (src_supervsdst_super), seeds a known checksummable table, runs the realrig db dumpandrig db restore, then reads the rows back out and compares an ordered md5 fingerprint + row count. That proves both invariants the design rests on:$POSTGRES_USER/$POSTGRES_DB— a hardcodedpostgreswould fail immediately against the non-default source superuser;--no-owner --no-aclmakes the dump portable across instances whose superusers differ — a plain dump would abort underON_ERROR_STOP=1on the missing role.It also asserts default-outfile naming, restore idempotency (
--clean --if-exists→ a second restore yields the same 3 rows, no duplication), and the named-[db]scratch-database path. It skips cleanly (exit 0) when Docker is absent/unreachable or root is unobtainable, and always cleans up containers + artifact via atrapwith a unique$$-based name prefix.CI — new
db-integrationjob onubuntu-latest(Docker preinstalled + passwordless sudo, so it executes for real, not skip), kept separate from the fastcheckjob so an image pull can’t slow lint feedback.README — a "Verifying a dump/restore actually works" subsection: the safe manual round-trip against a real Coolify container via a fresh scratch db (
createdb … rig_verify→ restore → spot-check →dropdb), echoing "a backup you have never read back is not yet a backup."Ran it locally against real Postgres containers — 12 passed, 0 failed (green round-trip, fingerprint
a8ddf579…survived src→dst and both re-restores).test/cli.shstill 82 passed / 0 failed; shellcheck clean including the new file.