Skip to content

1.12b Step | SetupDataDir

Omisen edited this page Aug 14, 2026 · 3 revisions

Creates Odoo's filestore (data_dir) reversibly. It lives in src/steps/setup_data_dir.rs. Born in R6 to close A-R5-3, and with it a new rule: ownership of the directory and ownership of the data are two different questions, and removing requires a yes to both.


The problem: an artifact born with no owner

The data_dir written into odoo<N>.conf by 1.12 GenerateConfig is <odoo_home>/.local/share/Odoo — inside /opt/odoo, which is the odoo user's $HOME. That is where Odoo writes the filestore: the actual files of the records' attachments.

While no step created it, that directory came into existence by itself on Odoo's first start. And /opt/odoo, when the installer finds it already there, is Preexisting: never touched, rightly so. Result, measured by integration.yml's Ubuntu job: after a complete rollback, /opt/odoo/.local was still there.

An artifact born with nobody recording it cannot be undone. It is not a hole in an undo: it is an artifact outside the model. Here it is created by a step, with its own PreState, and becomes removable by the only route this project allows — because the snapshot says we created it.


The cycle

Phase Behaviour
snapshot does the data_dir already exist? → Preexisting. Otherwise record the highest level missing on the way down from odoo_home (usually .local) and read from the Context whether the database is ours
run Preexisting → no-op. Otherwise mkdir -p as the odoo user (the service has to be able to write there) → CreatedByUs
undo removes the recorded level — but only if CreatedByUs and the database was ours
struct DataDirSnapshot {
    prestate: PreState,                  // is the directory ours?
    created_root: Option<PathBuf>,       // the highest level we created
    db_was_ours: bool,                   // is the data inside ours?
}

created_root is what makes the undo surgical: if the customer already had /opt/odoo/.local (with other things in it), the highest missing level is .local/share, and their .local is not touched. We do not remove the branch, we remove exactly the piece we grafted on.


The protection: the filestore follows the database's fate

A filestore is not a cache: it is the on-disk half of the application data. If the database was Preexisting — a customer database with the same name, which 1.8 CreateDatabase protects from the dropdb — then its filestore contains real attachments. We created the directory; the data inside is not ours.

So the undo requires two conditions, and one false is enough to stop it:

undo acts  ⟺  prestate == CreatedByUs   ∧   db_was_ours
               (the directory is ours)      (the data is ours)

It is the anti-drop rule applied to the data's other half: same protection, same reason.

Why db_was_ours is persisted and not re-read from the Context

The verdict arrives through the Context::db_created_by_us channel, which CreateDatabase publishes in its own snapshot. This step comes later in the sequence, so by its snapshot the value is there — and it is copied into the persisted snapshot.

That is not redundancy. The invok rollback command rebuilds the Context from the persisted configuration, where that flag defaults to false: without a local copy the undo would never know it may act, and the filestore would stay behind as a leftover. With the flag inverted the worse thing would happen — a customer's data removed by mistake. As with the database, the verdict is re-read, not re-derived.


The safety net on the rm -rf

The undo recursively deletes a path that comes from disk. A corrupted state, or one written by another installation, must not become a disaster elsewhere: before acting, created_root must be a strict descendant of odoo_home. Outside that (or equal to odoo_home) the undo logs and removes nothing. Better a leftover to remove by hand.


Undo ordering, declared

The step comes after create-database, so its undo runs before the dropdb (reverse order). If the dropdb failed — it is best-effort — a database of ours would be left without its filestore.

That is unavoidable: the snapshot must run after CreateDatabase's in order to know whose the database is, and that fixes its position in the sequence. The case is a database we are throwing away anyway, and a failed dropdb already ends up in the leftovers report at the end of the rollback.


Design notes

  • The data_dir path lives in one place, generate_config::data_dir: two identical format!s in two files would be the premise of a rollback cleaning the wrong directory.
  • dry_run: neither mkdir nor rm -rf, only logs.
  • Tests (tests/setup_data_dir.rs): the two ownership axes separately, a pre-existing .local left untouched, a pre-existing filestore ignored entirely, the database verdict surviving the round trip through disk, the perimeter guard with hostile paths, and an inert dry-run. The step is also in tests/rehydrate.rs's CHAIN, so it goes through the equivalence check between the live undo and the rehydrated one.

Clone this wiki locally