From 6e4f3fb4c4eb6dba51818e8cddd5ae4f092abc34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 10 Sep 2026 05:26:56 +0200 Subject: [PATCH] docs(ops): what a restore replaces, and the console path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two gaps in `backup-restore.md`. It said the restore "replaces the account's data tables" only in a paragraph about grants, so nothing stated the consequence for the account's own record: the rows it gained after the snapshot was taken are deleted with the rest of their class. And the console restore was named once in passing — `## Restore` documents only the off-host script — so an operator restoring from the browser had no procedure to read. Both are written down now, including which button does what, that the restore targets the snapshot's owner rather than the admin running it, that instance-wide settings ride along only when the dialog's opt-in is ticked, and what a copy this instance can no longer decrypt answers. --- docs/ops/backup-restore.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/ops/backup-restore.md b/docs/ops/backup-restore.md index 64e7854e9..2b6fe2af9 100644 --- a/docs/ops/backup-restore.md +++ b/docs/ops/backup-restore.md @@ -124,6 +124,24 @@ decompresses to a few hundred megabytes, and `NODE_OPTIONS=--max-old-space-size=2048` in front of the command is enough for a 445 000-measurement account. +### Restoring from the backups console + +An admin can restore a stored snapshot from the browser instead of the shell. +`/admin/backups` lists one row per account and per backup type: **Backup now** +enqueues the `data-backup` pass for every account on the instance, **Download** +decrypts one snapshot and hands back the JSON, and **Restore** opens a dialog +that first shows what the file carries and then requires the word `RESTORE` to +be typed before it will run. The restore targets the account the snapshot was +taken for, never the admin running it, and it refuses a file whose declared +owner does not match the stored row. Instance-wide settings ride along only +when you tick _Also restore instance settings_ in the dialog; leave it clear to +restore the account alone. + +A stored copy this instance can no longer open — the key that wrote it dropped +from `ENCRYPTION_KEYS` one rotation too early, or bytes that are not the ones +written — is refused by all three buttons with `422` and +`meta.errorCode = backup.payload.undecryptable`, before anything is read back. + ### What a backup deliberately does not carry Every credential-shaped row is left out, and this is not an oversight to fix: @@ -264,6 +282,16 @@ shape, and worse: a restore parses the whole document, so the read path needs several times the blob in heap. An operator restoring a very large account should give the container more memory for the duration. +### What a restore replaces + +A restore replaces the account's data tables; it does not merge into them. +Every row the file carries is written back under its original id, and every row +the account gained after the snapshot was taken — readings, doses, mood +entries, documents — is deleted with the rest of the class before the file is +read back. Restoring Monday's copy on Wednesday therefore costs the account +everything it recorded on Tuesday. If the current state is worth keeping, take +a fresh snapshot from the backups console before restoring the old one. + ## Monthly restore drill (automatic) Since v1.16.4 a pg-boss job (`data-restore-drill`, cron `11 4 1 * *` —