Skip to content

Restore database backups safely and quickly from the console - #1886

Merged
stefan-burke merged 15 commits into
mainfrom
restore-backup-task
Jul 22, 2026
Merged

stefan-burke merged 15 commits into
mainfrom
restore-backup-task

Conversation

@stefan-burke

@stefan-burke stefan-burke commented Jul 22, 2026

Copy link
Copy Markdown
Member

What changed

  • Add deno task restore <backup.zip> to restore the database named by DB_URL in .env.
  • Show backup details, require typed confirmation, and report each restore step.
  • Import SQL in bounded batches while keeping unique indexes and validation triggers active.
  • Defer only derived indexes and triggers during import, then rebuild them safely.
  • Remove the web restore form because a realistic restore cannot fit the edge request limit. Backup creation and downloads remain available on the Backups page.
  • Preserve multiline text and make .env database settings override inherited shell values.

Safety

  • Inspect the ZIP before confirmation or database reset. Reject malformed manifests, missing populated tables, empty archives, unsupported tables, non-INSERT SQL, SQL stored under the wrong table filename, and incomplete manifest-less backups.
  • Reject any single SQL statement over the 512 KiB restore request limit before reset.
  • Refuse backups from newer app versions before deleting data.
  • If an import batch fails after reset, rebuild a complete empty current schema instead of leaving a partial restore.
  • Keep restored data when final progress reporting fails after all database work succeeds.
  • Read the restored build commit from the primary database before suggesting a matching app deployment.

Checks

  • deno task precommit
  • deno task mutation --source src/shared/db/backup.ts --test "test/shared/db/backup{.test.ts,/*.test.ts}" --harness (141/141 mutants killed)
  • deno task mutation --source src/shared/update.ts --test test/shared/update.test.ts --harness (68/68 mutants killed; 2 proven equivalents suppressed)

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The pull request moves database restoration from the admin web interface to a Deno CLI task. It adds backup inspection, confirmation, progress reporting, and bounded restore execution while retaining web backup listing, creation, and download features.

Changes

Database restore workflow

Layer / File(s) Summary
Backup inspection and bounded replay
src/shared/db/backup.ts, test/shared/db/backup*, scripts/mutation/equivalent-mutants.txt
Backup manifests and SQL contents are validated before replay, statement splitting preserves quoted semicolons, and restore operations execute with progress stages and bounded batches.
Deno restore task and CLI orchestration
scripts/restore*, src/restore.ts, test/integration/restore-task.test.ts, test/restore.test.ts
The restore task loads .env values, validates targets, requires typed confirmation, invokes restoration, reports failures and commit information, and returns an exit code.
Schema restore coordination
src/shared/db/migrations/schema-sync.ts, src/shared/db/migrations/schema/triggers.ts, test/shared/db/migrations/*
Index metadata and trigger groups expose the schema information needed to defer derived work during restore imports.
Admin backup UI and route transition
src/features/admin/backup.ts, src/ui/templates/admin/backup.tsx, src/locales/en/*, README.md, AGENTS.md, test/features/admin/backup/*, test/ui/templates/admin/backup.test.ts, test/e2e/booking.test.ts
Web restore routes and upload forms are removed; backup listing, creation, and download remain, while the page and documentation describe the console restore command.
Shared script wiring and validation
scripts/script-runner.ts, scripts/deploy-edge*, deno.json, test/lib/code-quality/detectors.ts, test/integration/*
Shared script I/O is adopted by edge deployment, restore configuration is added, production export checks include script entry points, and supporting integration tests are added.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RestoreScript
  participant runRestoreTask
  participant runRestoreCli
  participant restoreFromZip
  participant Database
  RestoreScript->>runRestoreTask: Load dotenv values
  runRestoreTask->>runRestoreCli: Provide CLI I/O and dependencies
  runRestoreCli->>restoreFromZip: Restore after typed confirmation
  restoreFromZip->>Database: Inspect, reset, and import backup
  Database-->>runRestoreCli: Report progress and result
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a safe console-based database backup restore workflow.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch restore-backup-task
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch restore-backup-task

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7f64315df4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/restore.ts Outdated
Comment thread src/restore.ts
Comment thread src/shared/db/backup.ts Outdated
Comment thread src/shared/db/backup.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Line 331: Update the README restore task description to clarify that it
restores the database identified by DB_URL, using DB_TOKEN from .env, rather
than restoring DB_URL itself. Keep the command unchanged and align the wording
with the detailed restore instructions below.

In `@scripts/restore-lib.ts`:
- Around line 95-98: Update the DB_TOKEN validation condition in the restore
flow to recognize both libsql:// and https:// database URLs before checking the
token. Preserve the existing error message and return behavior for either remote
URL scheme, while leaving other DB_URL values unchanged.

In `@src/shared/db/backup.ts`:
- Around line 7-10: Update the backup documentation bullet near splitStatements
to describe its current tokenizer behavior: it recognizes quoted strings and
preserves semicolons, including those in multiline values, within the same
statement. Remove the outdated claim that statements are delimited by ";\n";
leave the other bullets unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0c0271a0-0a65-4602-995a-7c3cddde7388

📥 Commits

Reviewing files that changed from the base of the PR and between 0077485 and 7f64315.

⛔ Files ignored due to path filters (1)
  • deno.lock is excluded by !**/*.lock
📒 Files selected for processing (29)
  • AGENTS.md
  • README.md
  • deno.json
  • scripts/deploy-edge-lib.ts
  • scripts/deploy-edge.ts
  • scripts/restore-lib.ts
  • scripts/restore.ts
  • scripts/script-runner.ts
  • src/features/admin/backup.ts
  • src/locales/en/backup.json
  • src/locales/en/guide-domains.json
  • src/locales/en/guide-operations.json
  • src/restore.ts
  • src/shared/db/backup.ts
  • src/ui/templates/admin/backup.tsx
  • test/e2e/booking.test.ts
  • test/features/admin/backup/routes.test.ts
  • test/features/admin/backup/server.test.ts
  • test/integration/admin-action-cookie.test.ts
  • test/integration/code-quality.test.ts
  • test/integration/confirm-page-options.test.ts
  • test/integration/restore-task.test.ts
  • test/lib/code-quality/detectors.ts
  • test/lib/server-backup.test.ts
  • test/restore.test.ts
  • test/routes/backup.test.ts
  • test/shared/db/backup.test.ts
  • test/shared/db/backup/restore.test.ts
  • test/ui/templates/admin/backup.test.ts
💤 Files with no reviewable changes (3)
  • test/lib/server-backup.test.ts
  • test/routes/backup.test.ts
  • test/shared/db/backup/restore.test.ts

Comment thread README.md Outdated
Comment thread scripts/restore-lib.ts Outdated
Comment thread src/shared/db/backup.ts
@stefan-burke stefan-burke changed the title Restore database backups safely from the console Restore database backups safely and quickly from the console Jul 22, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6763a76ca7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/restore-lib.ts Outdated
Comment thread src/shared/db/backup.ts Outdated
Comment thread src/shared/db/backup.ts
Comment thread src/shared/db/backup.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/shared/db/backup.ts (1)

234-243: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Rename readManifest to readManifestOrNull.

This function legitimately returns null for an expected-absence case (older backups without manifest.json, as covered by the "Backup manifest: not available" test), but doesn't follow the codebase's *OrNull naming convention for that pattern. readRestoreDbUrlOrNull in scripts/restore-lib.ts already follows this convention for the same kind of nullable-on-expected-absence semantics.

As per coding guidelines: "Use nullable results, defaults, optional chaining, and catches only when absence or recovery is genuinely expected and documented; use *OrNull naming for expected absence."

♻️ Proposed rename
-const readManifest = (
+const readManifestOrNull = (
   files: Record<string, Uint8Array>,
 ): BackupManifest | null => {
   const manifestBytes = files["manifest.json"];
   if (!manifestBytes) return null;
   const parsed: unknown = JSON.parse(new TextDecoder().decode(manifestBytes));
   const result = v.safeParse(BackupManifestSchema, parsed);
   if (!result.success) throw new Error("Backup manifest is invalid");
   return result.output;
 };
-  const manifest = readManifest(backup.files);
+  const manifest = readManifestOrNull(backup.files);

Also applies to: 272-272

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/db/backup.ts` around lines 234 - 243, Rename the nullable helper
readManifest to readManifestOrNull and update every reference to it, including
the usage around the indicated later location; preserve its existing null
behavior when manifest.json is absent.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Around line 367-369: Update the README paragraph describing restore behavior
to qualify that table, row, statement, and schema details are shown when
available, particularly for manifest-less archives. Keep the existing claims
about typed deletion confirmation, restore-step reporting, and recorded commit
display unchanged.

---

Outside diff comments:
In `@src/shared/db/backup.ts`:
- Around line 234-243: Rename the nullable helper readManifest to
readManifestOrNull and update every reference to it, including the usage around
the indicated later location; preserve its existing null behavior when
manifest.json is absent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b925f4ed-b73d-47a6-ad0e-b89b4cabbe37

📥 Commits

Reviewing files that changed from the base of the PR and between 6763a76 and 076cc0a.

📒 Files selected for processing (9)
  • AGENTS.md
  • README.md
  • scripts/restore-lib.ts
  • scripts/restore.ts
  • src/restore.ts
  • src/shared/db/backup.ts
  • test/integration/restore-task.test.ts
  • test/restore.test.ts
  • test/shared/db/backup.test.ts

Comment thread README.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
README.md (1)

365-371: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Qualify manifest-dependent backup details.

Manifest-less archives cannot provide table, row, or schema details; only the SQL statement count is guaranteed. Change this to say those details are shown “when available,” matching the restore implementation. This repeats the unresolved prior review finding.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 365 - 371, Update the restore documentation to state
that table, row, and schema details are shown only when available, while keeping
the SQL statement count as the guaranteed detail. Preserve the existing workflow
and commit-reporting description.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@README.md`:
- Around line 365-371: Update the restore documentation to state that table,
row, and schema details are shown only when available, while keeping the SQL
statement count as the guaranteed detail. Preserve the existing workflow and
commit-reporting description.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cfa33fbb-555b-466c-af9d-919993314a85

📥 Commits

Reviewing files that changed from the base of the PR and between 076cc0a and 2a9bde2.

📒 Files selected for processing (3)
  • AGENTS.md
  • README.md
  • deno.json

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bc2a0684f3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/shared/db/backup.ts
Comment thread src/shared/db/backup.ts Outdated
Comment thread src/shared/db/backup.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7288dcaff2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/restore-lib.ts
Comment thread src/shared/db/backup.ts
@stefan-burke

Copy link
Copy Markdown
Member Author

Addressed the remaining CodeRabbit outside-diff naming note in 9e5565b: the nullable manifest helper is now readManifestOrNull, matching its expected legacy-backup absence contract. Focused restore tests and the full pinned precommit pass.

@stefan-burke

Copy link
Copy Markdown
Member Author

Addressed the remaining outside-diff CodeRabbit finding in 9e5565b. The expected-absence helper is now named readManifestOrNull, matching the repository convention; the existing manifest inspection tests still cover both absent and present manifests.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c5f9fa60ce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/shared/db/backup.ts
Comment on lines +374 to 375
report("resetting");
await resetDatabase();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep pre-reset progress failures from wiping data

The final progress report was moved out of the cleanup path, but this earlier report("resetting") still runs before resetDatabase() inside the try. If the progress handler fails here (for example stdout gets a broken pipe), the catch treats it as a post-reset failure and then runs the cleanup reset/rebuild, deleting the target even though no restore step had run. Gate cleanup on a flag set after resetDatabase() starts or succeeds, or keep this report outside the destructive catch.

AGENTS.md reference: AGENTS.md:L98-L100

Useful? React with 👍 / 👎.

Comment thread src/shared/db/backup.ts
Comment on lines +314 to +318
manifest === null
? legacyRequiredTables
: Object.entries(manifest.tables)
.filter(([, rowCount]) => rowCount > 0)
.map(([table]) => table);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require manifest restores to include core tables

With a manifest present, requiredTables is derived only from whatever row counts the manifest says are positive. A partial or corrupt ZIP can include a well-formed manifest such as tables: { settings: 1 } plus only settings.sql, pass inspection, and then restoreFromSql deletes the rebuilt schema_migrations and attendee_statuses rows without importing replacements, leaving the site without migration rows or default statuses after a reported-success restore. Keep the core-table requirement for manifest archives too, or validate the manifest against the required schema before reset.

AGENTS.md reference: AGENTS.md:L101-L105

Useful? React with 👍 / 👎.

srcTsxContents,
testContents,
ALLOWED_TEST_HOOKS,
[scriptsContents, cliContents, e2eContents],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep e2e harness imports out of production-use checks

Passing e2eContents (the e2e-payments harness) as a production importer weakens this guard: a src/ export used only from e2e payment tests is now treated as used in production, so the rule no longer catches dead/test-only exports for that case. Keep production entry maps to real runtime/operator entry points, and leave e2e harness imports in the test side of the check.

AGENTS.md reference: AGENTS.md:L64-L64

Useful? React with 👍 / 👎.

Comment thread src/shared/db/backup.ts
Comment on lines +437 to +439
// Every generated table file ends its statements with semicolons, so no
// extra separator is needed between files.
await restoreFromSql(allSql.join(""), onProgress);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve separators between restored table files

Inspection accepts a final SQL statement without a trailing semicolon because splitStatements adds one, but the restore path concatenates raw table files with no separator. If a ZIP has two known table files and the first ends in INSERT ... without ;, preflight passes, then the import sees that statement glued to the next file's first INSERT and fails only after the target was reset; join files with a newline or restore the inspected statements instead.

Useful? React with 👍 / 👎.

Comment thread src/shared/db/backup.ts
Comment on lines +374 to 375
report("resetting");
await resetDatabase();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep pre-reset progress failures from wiping data

The final progress report was moved out of the cleanup path, but this earlier report("resetting") still runs before resetDatabase() inside the try. If the progress handler fails here (for example stdout gets a broken pipe), the catch treats it as a post-reset failure and then runs the cleanup reset/rebuild, deleting the target even though no restore step had run. Gate cleanup on a flag set after resetDatabase() starts or succeeds, or keep this report outside the destructive catch.

Useful? React with 👍 / 👎.

Comment thread src/shared/db/backup.ts
Comment on lines +314 to +318
manifest === null
? legacyRequiredTables
: Object.entries(manifest.tables)
.filter(([, rowCount]) => rowCount > 0)
.map(([table]) => table);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require manifest restores to include core tables

With a manifest present, requiredTables is derived only from whatever row counts the manifest says are positive. A partial or corrupt ZIP can include a well-formed manifest such as tables: { settings: 1 } plus only settings.sql, pass inspection, and then restoreFromSql deletes the rebuilt schema_migrations and attendee_statuses rows without importing replacements, leaving the site without migration rows or default statuses after a reported-success restore. Keep the core-table requirement for manifest archives too, or validate the manifest against the required schema before reset.

Useful? React with 👍 / 👎.

srcTsxContents,
testContents,
ALLOWED_TEST_HOOKS,
[scriptsContents, cliContents, e2eContents],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep e2e harness imports out of production-use checks

Passing e2eContents (the e2e-payments harness) as a production importer weakens this guard: a src/ export used only from e2e payment tests is now treated as used in production, so the rule no longer catches dead/test-only exports for that case. Keep production entry maps to real runtime/operator entry points, and leave e2e harness imports in the test side of the check.

Useful? React with 👍 / 👎.

@stefan-burke
stefan-burke added this pull request to the merge queue Jul 22, 2026
Merged via the queue into main with commit 8a6be47 Jul 22, 2026
3 checks passed
@stefan-burke
stefan-burke deleted the restore-backup-task branch July 22, 2026 19:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant