audit 3: a restore is ended by its Resync, and half an archive is refused
test / test (push) Canceled after 0s
test / test (push) Canceled after 0s
The restore manifest was consumed as soon as its version was checked, before store.Open and the Resync; a start that failed in between left a data directory the next start took for an ordinary one (M1). openData now runs CheckRestore, store.Open, the Resync, and only then ConsumeRestore. A truncated archive had nothing to refuse it: the manifest is the first entry, so a download cut short or a tar stopped by a damaged block was a restore on half a database. Every archive now ends with data/contents.json, the size and SHA-256 of each data/ file (deploy files are the operator's to edit), and CheckRestore compares /data with it before the database is opened, then renames it contents.checked.json so a start after a failed one does not compare again unless the manifest changed. Tests: a start whose Resync fails, then a start that resyncs and consumes; an archive cut in the database, before the deploy files and in its last entry (refused, database untouched, no -wal); a damaged key named; the record's names confined to data/; the domain import of a cut plain export, a cut and an altered .spde (refused, nothing created). e2e on the test server: full_backup_restore now also sees contents.json in the archive and gone after the first start, through the real entrypoint and daemons (all pass, 762 s); go vet + go test of 24 packages pass there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+17
-1
@@ -809,7 +809,9 @@ blocked. On that same first boot the panel also runs one **Resync** — OpenDKIM
|
||||
tables and Postfix's sender map are re-derived from SQLite (inbound relay maps
|
||||
when `INBOUND_RELAY_ENABLE=true`; DMARC maps when `DMARC_REPORTS_ENABLE=true`)
|
||||
and both daemons are reloaded, healing any drift between the extracted files and
|
||||
the database. The *Reload configuration* button on Server › Health resyncs
|
||||
the database. The manifest is deleted only after that Resync is done: a first
|
||||
start that fails on the way leaves it, and the next start is a restore again
|
||||
and runs the Resync. The *Reload configuration* button on Server › Health resyncs
|
||||
OpenDKIM, the sender map, and inbound maps only — not DMARC maps. This is why the
|
||||
compose file pins a fixed tag rather than `:latest`: without a known version,
|
||||
there'd be no way to tell which image restoring a given backup actually requires
|
||||
@@ -857,6 +859,20 @@ tag](#fixed-image-tag) if it is not published), then `docker compose up -d`
|
||||
again — the manifest is still there because the failed boot never got to
|
||||
delete it.
|
||||
|
||||
An archive unpacked **in part** — a download cut short, or a `tar` that
|
||||
stopped at a damaged block (it says so and exits non-zero) — is refused at
|
||||
step 5 as well, before the database is opened:
|
||||
|
||||
```
|
||||
backup: the archive was not unpacked whole (data/contents.json, its last entry, is not there) - it was cut short or is damaged; nothing was opened. Unpack it again, from an intact copy, into an empty directory
|
||||
```
|
||||
|
||||
The last entry of every panel and CLI archive, `data/contents.json`, records
|
||||
each file under `data/` with its size and SHA-256, and the first start
|
||||
compares `/data` with it; a file that differs is named in the message.
|
||||
`docker-compose.yml`, `.env` and `certs/` are not compared — they are yours to
|
||||
edit before that start.
|
||||
|
||||
**Moving to a different host** is the same flow: create an empty project
|
||||
directory, unpack the backup there, edit `.env` (and `docker-compose.yml` if
|
||||
needed) for the new hostname or proxy, then `docker compose up -d`. The archive
|
||||
|
||||
Reference in New Issue
Block a user