From 82db3de30a41093a1dc4d6babce3b789e5d78a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 25 Oct 2026 13:24:49 +0100 Subject: [PATCH] feat(backup): restore a dump straight from the off-host bucket A lost host takes its local dumps with it, and the copy left is the one in the bucket. `docker compose run --rm db-restore latest --from-bucket` now lists `database/` in the bucket the BACKUP_S3_* variables name, takes the newest dump whose manifest is there too (or the one named), downloads both with the S3 client the image already ships, and continues as for any off-host copy: opened with BACKUP_ENCRYPTION_KEY, checked against the manifest, restored into an empty database. `list --from-bucket` shows what is there. The sealed download is removed once it is opened. The image build now proves the S3 client resolves from the shared module's place in the tree, so the restore cannot lose it unnoticed. --- Dockerfile | 6 +- docker-compose.yml | 11 +- scripts/__tests__/db-restore.test.ts | 6 + scripts/db-restore.mjs | 92 +++++++++++- .../__tests__/dump-core.test.ts | 59 ++++++++ src/lib/database-backup/dump-core.mjs | 134 ++++++++++++++++++ 6 files changed, 298 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 033b08005..7c07c594d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -265,9 +265,11 @@ RUN node scripts/reset-password.mjs 2>&1 | grep -q "reset-password: usage:" RUN env -u DATABASE_URL node scripts/pending-migrations.mjs && \ node --input-type=module -e "await import('pg')" && \ node --input-type=module -e "await import('./scripts/migrate-deploy.mjs')" -# The dump and restore scripts load, and the client tools they run are the -# major the bundled database runs. +# The dump and restore scripts load, the client tools they run are the +# major the bundled database runs, and the S3 client `db-restore --from-bucket` +# loads resolves from the shared module's place in the tree. RUN node scripts/db-dump.mjs 2>&1 | grep -q "db-dump: usage:" && \ + node --input-type=module -e "import { createRequire } from 'node:module'; createRequire('/app/src/lib/database-backup/dump-core.mjs').resolve('@aws-sdk/client-s3')" && \ node scripts/db-restore.mjs 2>&1 | grep -q "db-restore: usage:" && \ pg_dump --version | grep -q "(PostgreSQL) 18\." && \ pg_restore --version | grep -q "(PostgreSQL) 18\." diff --git a/docker-compose.yml b/docker-compose.yml index 676522118..19ce9acec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -806,15 +806,18 @@ services: # docker compose run --rm db-restore list # docker compose run --rm db-restore latest # docker compose run --rm db-restore healthlog-