-
Notifications
You must be signed in to change notification settings - Fork 0
3. Rollback e2e + hardening
The step from “it works in the tests” to “ready for a real machine”. It adds no dangerous features — it adds guarantees and settles debts. It lives in
tests/rollback_e2e.rs,src/logging.rs,src/lockfile.rs, and in the two TODOs it closes.It closes the circle opened at the beginning: the promise “either the installation succeeds, or the system goes back to exactly what it was” becomes a verified property, not an intention.
Until here each undo was tested in isolation. This tests the rollback's emergent property over the
whole sequence: inject a failure at step N and check that the 0..N-1 chain undoes itself, bringing the
system back to its pre-installation state.
Unlike the mock that records operations, SystemModel models a coherent state (users, packages,
services enabled/active, roles and databases, files, symlinks and their contents, firewall rules). Every
mutation updates the state; every undo restores it; all the steps of a sequence share the same model. So
“back to pristine” is verifiable literally:
final_state == initial_state
| Scenario | What it demonstrates |
|---|---|
| Full chain + a failure at the end | the whole sequence rolls back → identical state; the .bashrc byte for byte |
| Failure halfway (after user/deps/postgres/role/DB) | everything that had been created disappears |
| The customer's pre-existing resources | the init hard stop halts the chain; the rollback touches nothing pre-existing |
The pre-existing resources scenario is the proof of the three protections as a property of the
whole chain, not of a single step: starting from a system with PostgreSQL installed and running and
an odoo database that already exists (the customer's data), after the rollback:
- the customer's database is NOT dropped (anti-drop);
- PostgreSQL stays installed (D3) and the already-running service stays running (D4);
-
a pre-existing
/opt/odoostays.
That is the moment the installer becomes safe to point at a real machine.
tracing with two layers: TTY (with colours, coordinated with the progress bar) and a file without
ANSI (/var/log/invok.log, non-blocking writer). It captures the whole run (INFO and above), errors
and rollback included — the artifact to ask for in a post-mortem. It degrades to TTY-only if the
path is not writable (a dry-run without root), without failing. The password never gets there,
guaranteed by Secret.
Since R7 the log lives in /var/log, outside /opt/odoo: it is opened before the engine, so keeping
it inside would create the very directory the first step is supposed to create — and would keep it
occupied at the last undo.
An exclusive non-blocking flock (through nix) on /run/invok.lock, with an RAII guard: the
release happens on success, on error and on panic (Drop). A second simultaneous run gets a clear
error — “another installation is in progress” — without mutating anything. It is acquired after
the preflight checks and before any mutation, and skipped in dry-run.
acquire deliberately does not create its parent directory: taking a lock is coordination, and it
must not bring artifacts into existence (R7).
The official wkhtmltopdf/packaging release publishes neither checksums nor signatures for its
packages (only the git tag is GPG-signed). There is therefore no upstream checksum to use.
The honest decision: manual TOFU pinning. The fail-closed mechanism of
1.5 is unchanged; the default_checksums() table is populated with
pins generated once from a trusted source (official HTTPS). From then on the installer verifies every
download against that pin — protecting against compromised mirrors, corrupted downloads and later
alterations, even without an upstream signature. They are TOFU pins, not official checksums: the
documentation says so, along with the procedure to generate and update them. A suffix with no pin
refuses the installation — honest behaviour, never bypassed.
The TODO from 1.6 SetupPostgres: even with --aggressive-rollback, before
purging PostgreSQL it is checked (best-effort) that the cluster hosts no other databases besides
ours. If it finds any → purge declined with a warning (stop and disable still applied); if the list
cannot be obtained → fail-safe, nothing is purged. The decision is taken before the stop, because
listing databases requires a running postgres.
- The lock and the logging live in
mainand dedicated modules; theSteptrait and the engine stay untouched. - The end-to-end tests run entirely on
SystemModel— no real system, no root. - What mock tests cannot prove is covered by the integration CI, which really installs and uninstalls: see Development and contributing.
Start here
Key concepts
References
For developers
Technical detail — how it works inside
Steps:
- 1.1 PrepareOptRoot
- 1.2 CreateOdooUser
- 1.3 SetupLogDir
- 1.3b SetupCacheDir
- 1.4 AptPackages (delta)
- 1.5 InstallWkhtmltopdf
- 1.6 SetupPostgres
- 1.7 CreateDbRole
- 1.8 CreateDatabase
- 1.9 CloneOdooRepo
- 1.10 CreateVirtualenv
- 1.11 InstallPythonRequirements
- 1.12 GenerateConfig
- 1.12b SetupDataDir
- 1.13 InitializeOdooDatabase
- 1.14 SetupSystemd
- 1.15 Nginx (6 sub-steps)
- 1.16 WriteControlScript + PatchBashrc
Cross-cutting: