-
Notifications
You must be signed in to change notification settings - Fork 0
1.16 Step | WriteControlScript + PatchBashrc
The last two steps: they install the
odoohelper command and close the project's third critical protection (C3) — care for the user's personal files. They live insrc/steps/write_control_script.rsandsrc/steps/patch_bashrc.rs. A port oflib/control_script.sh.They complete the triad of protections: DB anti-drop (do not destroy) · init hard stop (do not write) · surgical bashrc (do not alter).
These artifacts belong to whoever ran sudo (SUDO_USER), not to the odoo user and not to root.
The command is installed for that user only (~/.local/bin), never globally in
/usr/local/bin — a security choice from the original Bash (reduce exposure), preserved. The home is
determined with getent passwd <user>, not assumed to be /home/<user>. A missing SUDO_USER is an
error.
Our own material, in the user's directories. It follows the pattern of the earlier steps.
| Phase | Behaviour |
|---|---|
| snapshot | do the ~/.scripts/odoo.sh script and the ~/.local/bin/odoo symlink already exist? and did the directories exist? For a named instance both carry the instance name (odoo-cliente-x), so each installation gets a helper of its own instead of overwriting the other's |
| run |
always writes odoo.sh (a wrapper over systemctl {start|stop|restart|status} plus dev), chmod +x, creates the symlink if missing, chowns to SUDO_USER. If the script was already there, it is backed up first |
| undo |
CreatedByUs → remove; Preexisting → put the backup back. The directories only if we created them and they are empty |
Tests: ownership == SUDO_USER (never odoo/root), no path under /usr/, a pre-existing script saved
and restored.
Why the script is always rewritten. Its contents are generated by us and carry the service name inside (
SERVICE_NAME=odoo18). Skipping it because “it already exists” meant that, after reinstalling a different Odoo version, theodoohelper kept driving the old service — and the user only found out whenodoo restartdid not do what it should.The
PreStatehere does not protect somebody else's content: it protects the decision to remove it in the undo. But a file with that name could still be somebody else's, and we have no way to know: hence it is set aside before being rewritten, and the undo puts it back — the same treatment as the nginx vhost andodoo.conf.
The .bashrc is the user's most intimate config file: aliases, functions, their own things. The
“surgical” promise here means that, if the user rolls back, their file comes back byte for byte as
it was — without our line, and without scars.
We add exactly one line, only if it is not already there:
export PATH="$HOME/.local/bin:$PATH"
| Phase | Behaviour |
|---|---|
| snapshot | does the .bashrc exist? is the line already present? (exact line match, grep -Fqx) |
| run | if absent: back up the file, then append the SINGLE line (never rewrite the whole file) |
| undo | if we added it: restore from the backup (primary) → fall back to removing the exact line |
-
Never rewrite or truncate the whole
.bashrc. Only append/remove the single line, or restore the backup. -
Exact match, never fuzzy: if the user had a different handwritten
PATHline, the undo does not touch it. - Line already present (
Preexisting) → it is not ours → run and undo are no-ops (no duplicates, no removals). - If the file did not exist and we created it → the undo removes it.
It brings the file back to its exact pre-modification state, with no risk of removing the wrong
line. The exact match (remove_exact_line) is the robust fallback when the backup is not available.
-
No step-to-step channel added:
sudo_useris read-only input in the Context (from the environment, likeos_info), not mutable shared state. The only shared state remainsdb_created_by_us. -
control_script_contentandremove_exact_lineare pure functions, tested separately. - Tests protecting C3: a round trip of the
.bashrc(after run+undo, identical to the original with aliases and functions intact), line already present → no-op,.bashrccreated → removed, and a different PATH line of the user's survives (exact match).
odoo {start|stop|restart|status|dev}
Every mutating verb acts on ${SERVICE_NAME} — this instance's service — and on nothing else. A
machine can carry several, each with its own service, user, database and port, and a helper that
started or stopped somebody else's would take one customer offline to fix another one's problem. That
is the hazard the shared-artifact rule protects the rollback from, arriving through the front door
instead. There is a test that reads the mutating branches and refuses any systemctl there acting on
a name we did not derive.
status is the exception, because reading is not touching: it shows this instance's service and then
every Odoo service on the machine, marking the one this helper drives.
Odoo services on this machine:
odoo-cliente-x.service inactive
-> odoo18.service active (this one: odoo)
Two commands produce that, and the reason is worth keeping. systemctl list-units was the obvious
one and it is the wrong source: a service that is stopped gets unloaded, so it vanishes from that
listing even with --all — which is precisely the instance somebody running status is looking for.
It was written that way, and a VM showed the missing line within a minute. The unit files are the
record of what is installed; is-active answers, per unit, what is up. A manifest could not answer
either question: what is running now is not recorded anywhere, by design — the same distinction the
preflight makes between a port that is reserved and a port that is held.
dev stops this service and opens a shell as the instance's user, which lands in its home. It
does not stop the others: two instances share no port and no database, so there is nothing to free.
When the shell exits the service is still down, and the helper now says so — an instance quietly left
stopped after a debugging session was a defect of the helper, not of whoever used it.
An unknown verb prints the usage on stderr and exits 2. It used to print it on stdout and exit
0, so a script calling the helper could not tell a typo from a success.
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: