-
Notifications
You must be signed in to change notification settings - Fork 0
1.2 Step | CreateOdooUser
Creates the
odoosystem user (useradd --system), its dedicated group, and makesodoo:odoothe owner of the/opt/odoohome. Reversible. It lives insrc/steps/create_odoo_user.rs. It follows the model of 1.1 PrepareOptRoot on a richer resource (user + group + ownership).A port of
create_odoo_user/_verify_odoo_user_homedirfromlib/system.sh.
| Phase | Behaviour |
|---|---|
| snapshot | detects two independent things: (1) does the user already exist? → Preexisting/Untracked; (2) the current owner of /opt/odoo before our chown (saved for the undo). Then it applies the precondition described below |
| run |
Preexisting → skip useradd, no aggressive chown; absent → useradd + an explicit chown odoo:odoo + chmod 0750, then CreatedByUs. dry_run → log only |
| undo |
CreatedByUs only: userdel without -r + groupdel (best-effort) + restore the home's original owner. Preexisting/Untracked → no-op |
useradd arguments (least privilege, as in Bash):
useradd --system --create-home --home-dir /opt/odoo --user-group --shell /bin/false odoo
--shell /bin/false → no interactive shell; --system → UID < 1000 with no password; --user-group →
a dedicated odoo group.
This is the first case of two steps both touching the same artifact (/opt/odoo). One creates it
(PrepareOptRoot), the other becomes its owner (CreateOdooUser). The rule that unties the knot:
Every step owns the removal of what it created.
Concretely:
-
CreateOdooUser.undorunsuserdelwithout-r: it does NOT remove the home. With-rit would delete/opt/odoo, which isPrepareOptRoot's business. - the home is removed by
PrepareOptRoot.undo, which runs later in the reverse order (SetupLogDir → CreateOdooUser → PrepareOptRoot). - if the home was
Preexisting(not ours) and ourchownchanged its owner,undorestores the original owner saved atsnapshot— so it does not stay owned by a user we are deleting.
Invariant from
CLAUDE.md: neveruserdel -ron aPreexistinguser. Here the undo acts only onCreatedByUsusers, and even then without-r. A pre-existing user is never touched.
This shape — ownership of removal plus reverse order — is the model for future cases where two steps share an artifact (for instance the role that owns the database in PostgreSQL).
The privileged commands (useradd/userdel/groupdel/chown/chmod) are not called directly: they
go through a SystemOps trait held inside the step. That way the
Step trait and the engine do not change.
| Impl | Use |
|---|---|
RealSystemOps |
production: useradd via Command, chown/mkdir via nix + std::fs
|
MockSystemOps (tests) |
records which operation would run and with which arguments, without root |
The tests check the decision logic (the PreState branch, the exact useradd arguments, userdel
without -r, owner restoration) without executing anything and without touching the system.
struct CreateUserSnapshot {
user_prestate: PreState, // Preexisting | Untracked | CreatedByUs
home_original_owner: Option<OwnerId>, // the home's owner before our chown
}Serialised (snapshot_value) and persisted, and read back by rehydrate: the rollback knows whether
the user is ours and whom to restore the home to — even when it runs from disk months later.
-
No aggression towards pre-existing things: if the user was already there, no
useraddand nochown— it is not ours to reconfigure. -
useradddoes not re-chown a pre-existing home: thechown odoo:odoois explicit, after creation (like_verify_odoo_user_homedirin Bash). - Every undo step is best-effort: a failure logs a
warnand carries on, without blocking the other steps' cleanup. - Tests:
CreatedByUs(useradd+chown / userdel without -r),Preexisting(never touched), owner restoration on a pre-existing home,dry_run(no operation).
If the user already exists and /opt/odoo belongs to root without having been created by this
installation, the installer stops here, before mutating.
The reason: this step does not chown (the directory is not ours), and three steps later SetupCacheDir
would run sudo -u odoo mkdir -p /opt/odoo/.cache on a root-owned directory. The error used to be a
Permission denied on a mkdir, naming neither the cause (the home belongs to root) nor the condition
that makes it a problem (the user already exists, so nobody hands it over).
It is a precondition, not an undo: it is not a mutation to reverse, it is a mutation not to begin.
The message names the home, its owner and the two ways out — chown if that directory is meant for
Odoo, or remove it if it is a leftover.
The case where we created the home never reaches here: the handover already happened in PrepareOptRoot, the only place that knows who created it.
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: