docs: first-run restore generates new secrets over a backup holding the old ones

Restoring a real 13-app repository onto a fresh install: 11 came back working.
matrix failed because its install hook writes homeserver.yaml with the new DB
secret and the restore then lays the old one back over it; stoat failed because
rabbit's mnesia dir restores raw and the broker only honours
RABBITMQ_DEFAULT_PASS on an empty data dir.

Apps with a dump descriptor are unaffected — those clear the data dir so the
engine initialises with the current secret, then replay the dump. That is the
tell for what the general fix has to look like.

Not implemented: applying the restored system config on first run changes what a
restore does with credentials (including the WebUI login), which is a decision,
not a defect. Written up as §3.6 with a recommendation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
librelad 2026-08-28 06:50:57 +01:00
parent 95edf64558
commit d0735ea9ba

View File

@ -264,6 +264,54 @@ Anything unexpected — no subuid range, no `newuidmap`, a namespace that will n
start — falls back to running the command plainly, which is what happened before
any of this existed.
### 3.6 — Open: a fresh install generates new secrets, the backup carries the old ones
Found by restoring a real 13-app repository onto a purpose-built install
(storage-locations §12.5, case 4). 11 of 13 apps came back working. The two that
did not failed the same way for two different reasons, and the reason is
structural rather than a bug in either app.
**A first-run restore generates fresh credentials, then restores state that
encodes the old ones.** Install randomises every `CFG_*_PASSWORD`; the backup
contains a machine that used different ones. Wherever a secret is written into
something the restore then lays down, the two disagree:
| app | what disagreed | result |
|---|---|---|
| `matrix` | the install hook writes `homeserver.yaml` from `CFG_MATRIX_DB_PASSWORD_1` at step 3, and step 6 restores the snapshot *over* it | `password authentication failed for user "synapse"` — postgres was initialised with the new secret, the restored config file holds the old one |
| `stoat` | `data/rabbit/mnesia` restores raw, and rabbit only honours `RABBITMQ_DEFAULT_PASS` on an **empty** data dir | `ACCESS_REFUSED` — four dependent services exit 101 |
Apps with a **dump descriptor** are unaffected, and that is the tell: for those,
`restoreDbRehydratePreStart` clears the data dir so the engine initialises with
the current secret, then replays the dump into it. Everything else — a config
file inside the app dir, a broker's own user database — keeps the old value.
Three ways out:
1. **Apply the restored system config on first run** instead of only staging it.
The backup's `configs/` holds the original secrets, so adopting them makes
every restored app consistent by construction. `backupRestoreSystemConfig`
deliberately stages rather than applies — *"recovering creds/settings is a
review-then-copy step, never an automatic blast over a running control
plane"* — and that is right for a **running** box and wrong for a **fresh**
one. The code currently cannot tell those two situations apart. This is §9.1
restated with a concrete failure behind it, and it carries a real
consequence: it also restores the WebUI login, so the user signs in to the
new machine with the old password. Probably expected; must be said out loud.
2. **Re-run app config generation after the data restore.** Fixes matrix. Does
nothing for stoat, where the secret lives inside restored *service state*
rather than a generated file — so it is not sufficient on its own.
3. **Reset the credential in the restored service** (`ALTER ROLE`,
`rabbitmqctl change_password`). Per-engine, fragile, and needs a hook per
backing service.
Recommendation: **(1)**, gated on first run specifically — the flag already
exists as `init_mode=restore`. (2) is a reasonable belt-and-braces addition; (3)
is a last resort for services whose state cannot be re-initialised.
Not implemented here: it changes what a restore does with credentials, which is
a decision rather than a defect.
## 4. The password problem, stated plainly
**An encrypted repository cannot be opened with anything inside itself.** `CFG_BACKUP_LOC_<idx>_PASSWORD` lives in the system config — which is *inside the backup*. So on a fresh machine the user must supply the repository password by hand. There is no way around this and it is not a bug; it is what encryption means.