diff --git a/docs/roadmap/first-run-restore.md b/docs/roadmap/first-run-restore.md index 7ca4a23..1dc4da1 100644 --- a/docs/roadmap/first-run-restore.md +++ b/docs/roadmap/first-run-restore.md @@ -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__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.