-
Notifications
You must be signed in to change notification settings - Fork 0
1.1 Step | PrepareOptRoot
The installer's first real step: it creates
/opt/odoo(thectx.odoo_homevalue) when missing, reversibly. It lives insrc/steps/prepare_opt_root.rs. It is the simplest possible mutation — a directory — chosen deliberately as the reference model for the steps that follow (user, packages, postgres…): all of them are richer variants of the samesnapshot → run → undoshape.It comes out of correction C4: the
mkdirthat Bash hid insidecheck_diskbecomes a step with anundohere. See 1. Checks and 0. Engine.
Since instances exist the step owns two levels, snapshotted separately because ownership is per
level: the shared root /opt/odoo, which every instance lives under and is created once, and —
for a named instance — that instance's own home, which is also the home of its system user. For the
unnamed instance the two are the same directory and everything below behaves exactly as it always
did. One PreState for both would have made a second instance's rollback either destroy the shared
root or spare its own home.
| Phase | Behaviour |
|---|---|
| snapshot | for each level: it exists → PreState::Preexisting; otherwise Untracked (run has not created it yet). Plus, for a named instance, whether the shared root has to be widened — see below |
| run |
Preexisting → no-op (not ours: neither owner nor permissions); Untracked → mkdir + 0755, then PreState::CreatedByUs before anything else. If the user already exists, the directory is handed over at once (chown + 0750). A named instance leaves the shared root root-owned 0755: handing it to one instance's user would give that user the ground the others stand on. In dry_run it only logs |
| undo | deepest first — the instance home, then the shared root. Acts only on CreatedByUs and removes a directory only if empty (rmdir, never rm -rf). With another instance installed the shared root is left alone whoever created it |
All three PreStates are handled and tested. The post-run PreState is serialised
(snapshot_value) and persisted, so the rollback knows whether the directory is ours — including a
rollback run days later from the manifest, which rebuilds this step through rehydrate.
At this point the odoo user does not exist yet — it is created by the next step. So the directory
is created owned by root with 0755 permissions, and the chown odoo:odoo happens in the
user-creation step, which is the correct order. The dependency is documented in the code.
Only the missing level is created (create_dir, not create_dir_all). The rollback must restore
exactly what we added: if we also created /opt we would not remove it again, leaving a residue. The
parent (/opt) is assumed to exist on the system.
The unnamed instance's home is /opt/odoo, so it is handed over as odoo:odoo 0750 — right
while it is one instance's private home, wrong the moment a second one moves in. Adding
--instance cliente-x to that machine creates the user odoo-cliente-x, which is neither the owner
nor in the group: with 0750 it cannot traverse /opt/odoo, so it never reaches its own home.
That is the migration path of every existing customer, and in the field it failed three steps later
with mkdir: cannot create directory '/opt/odoo': Permission denied — a directory that exists,
which sends whoever reads it looking for the wrong problem.
So a named instance that finds the shared root not traversable widens it by exactly one bit
(o+x: walk through, still not list) and records the mode it found. The widening is an artifact
like any other — the same treatment the nginx default site gets: we touch what the customer owns
only by writing down what it was. The undo puts the mode back, and only if it is still the one we
left; if no traversal is needed any more, which is the case once the only instance left is the one
that owns the root.
If the mode cannot even be read, the installation stops there, before touching anything, with a message that names the permission. An unreadable mode is “I do not know”, never “it is fine”: widening without having read what was there is a mutation with no undo.
- Idempotent: if the directory is already gone → no-op.
- It removes only if empty. If it contains artifacts from later steps, those steps have their own
undos which run first (reverse order), so by the timePrepareOptRoot's undo runs the directory should be empty. If it is not → a warning, and it is not forced. Neverrm -rf. - A
Preexistingdirectory is never removed: it is not ours to destroy.
parse config
└─▶ preflight checks (non-mutating)
└─▶ Installer::execute([ PrepareOptRoot, … ]) ← first mutation, reversible
It is the first of the 25 steps, which means its undo is the last one to run in a rollback — the
reason its directory should be empty by then.
- An exemplary, deliberately minimal implementation: if the pattern is clean here, the complex steps copy it.
- No
.unwrap()/.expect(): every failure is aResult. - Round-trip tests:
CreatedByUs(creates → removes),Preexisting(survives the undo), non-empty directory (the undo does not force),dry_run(creates nothing,undois a no-op).
owned root is not the right state for the home: it is a waiting state, and it makes sense only
while the user does not exist. When the user is already there — a distro package, the leftover of a
manual setup, a company convention — there is nothing to wait for, and leaving it to root broke the
installation three steps later: CreateOdooUser sees a Preexisting user and returns immediately
without the chown (a deliberate choice: we do not touch what is not ours), and SetupCacheDir
then runs sudo -u odoo mkdir -p /opt/odoo/.cache on a root-owned directory → Permission denied,
with an error that says nothing about the cause.
The handover belongs here and not there because here is where the information is: this step knows
it created the directory itself. CreateOdooUser cannot know that — at its snapshot the home always
exists, because we just created it — so any attempt to infer it from there would be a check that in
production always answers the same way.
The opposite case — a pre-existing root-owned home with an already-existing user — stays out: that directory is not ours and does not get chowned. There the installation stops with an explicit precondition in CreateOdooUser.
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: