-
Notifications
You must be signed in to change notification settings - Fork 0
History
Historical note. Bash is mentioned here only — as the past, not as a usage guide.
The installer was originally written in Bash (installer.sh plus lib/*.sh modules, with a
check_install.sh verification suite). Those versions are archived at the tags:
The Bash version was idempotent (run it again and carry on) but not reversible (undo and clean up): if a step failed, it could leave the system in an intermediate state.
The current version is a complete rewrite in Rust, born to add what Bash lacked: transactional
rollback — either the installation succeeds, or the system goes back to exactly what it was. The
compiler guarantees that no error branch is ignored; every step has snapshot/run/undo, and the
rollback is proven by end-to-end tests.
The Bash files are no longer in the repository: they were removed when Rust became the source of truth. They remain recoverable from the git history and the tags above.
2.2.0 came out of a cold audit of the whole tree. There was a single theme: the promise “either it succeeds, or the system goes back to what it was” was not being kept in several places — and none of them showed up as an error.
The three things a user notices:
- Ctrl-C rolls back instead of killing the process. Before, the installation was left half-done.
-
An interrupted installation resumes, without losing track of what it had already created. And a
completed one is no longer overwritten: the uninstall manifest was the only thing making an
instance removable, and a second
sudo invokdestroyed it silently. -
/opt/odooreally disappears after a rollback. Before it always stayed: the lock and the log lived inside it and kept it occupied, so the undo meant to remove it could never fire.
Hence the three path moves (manifest to /var/lib, lock to /run, log to /var/log) and the renaming
of --enable-ssl to --open-https-port — a flag that promised TLS without providing it. The legacy
name and its .env key keep working.
If you are upgrading: the log has moved, and re-running the installer on an already-installed instance now fails on purpose (use
rollbackor--force). Instances installed with 2.1.0 remain uninstallable-by-command: the historical manifest path is still read.
Until then the installer spoke one language only. Fedora support was not added with an if next to
every command, but by extracting two boundaries — “which commands install a package, and what is it
called here” and “where do the files live, which concepts exist, who governs the firewall” — so that
the steps stopped knowing which distribution they run on.
Three things that only showed up by actually running it, and that are worth more than the list of translated package names:
- on Fedora the PostgreSQL cluster does not exist until you create it, so “install the package” is not enough: initialising is a mutation with an artifact of its own, and as such it has to be recorded and undoable;
-
SELinux denies the proxy to Odoo with a valid
nginx -tand a successful reload: the symptom is a 502 and nothing in the configuration explains it; - from Fedora 43 the system
python3is newer than Odoo's pins, and no build flag works around a wheel that does not exist. Hence the rule that the installer chooses the interpreter instead of taking whatever is there, installing one the pins cover and removing it on rollback.
The full account: Multi-distribution support.
If you are upgrading from 2.2.0: no flag changes and no existing installation needs redoing. On Ubuntu and Debian the behaviour is identical; the difference only shows on Fedora, where the installer used to stop and now goes all the way through.
The project used to be called odoo-installer. From 3.0.0 it is called Invok, with the short
alias vok — a /usr/bin/vok → /usr/bin/invok symlink created by the packages, not a second
program.
The rule that decided every single occurrence, and that is worth knowing because it explains what
changed and what did not: “Odoo” leaves the identifiers and stays in descriptive use. Out of the
name of the crate, the binary, the packages and the bookkeeping paths (/var/lib/invok/state.json,
/var/log/invok.log, /run/invok.lock). In, unchanged, all the artifacts that are Odoo's:
/opt/odoo, the odoo user and database, odoo.conf, odoo18.service, the odoo helper command. A
trademark is defended on identity, not on mention: taking “Odoo” out of the prose would cost
discoverability without reducing exposure.
If you are upgrading from a 2.x: your instances stay uninstallable-by-command. The manifest of an installation made with 2.4.0 lives in
/var/lib/odoo-installer/state.json, and that path is still read — together with the even older/opt/odoo/.installer-state.json. Dropping it would not leave “an instance to remove by hand”: it would leave an instance nobody can uninstall without guessing the names of its artifacts.
Up to 2.3.0 there was one route: clone and compile. From 3.0.0 there are four wrappers, and three of
them need no Rust — .tar.gz (static musl, any distro), .deb, .rpm, each with its own sha256,
plus cargo install invok for those who already have the toolchain.
Updates come from the Releases page, where every
version stays with its checksums: you update by downloading again. There is no apt/dnf repository
to add to the machine's sources.
Until 3.0.0 the installer assumed it was alone: every artifact it created was named after the Odoo version, so two installations of the same version collided on the user, the role, the database, the unit and the config file. From 3.1.0 an installation can be named, and the name qualifies everything it owns.
sudo invok --instance cliente-x --port 8169
sudo invok list
sudo invok rollback --instance cliente-x
sudo invok rollback --allThree things are worth knowing beyond the commands.
The unnamed instance did not move. Whoever does not ask for an instance must not notice that instances exist: every path, unit, user and manifest keeps the name it has had since the first release. That is not politeness towards old installations — it is what keeps them findable, resumable and removable.
A second instance adds no package, and removing it purges none. The system dependencies, the
PostgreSQL cluster, wkhtmltopdf, nginx and /opt/odoo itself belong to whichever instance created
them. Removing one while another is installed leaves them in place and says so; that instance's
manifest is kept as the record of who owns them, so the last removal can still take them away.
--all does the two passes in the right order.
Ports come in pairs. Odoo's longpolling worker needs a second port, and it used to be the literal
8072 in the template — so two instances wrote the same number and fought over one socket the moment
both ran with more than one worker. It is now derived from --port, persisted, and checked against
the other manifests: a port an instance has recorded is refused even when nothing is listening on
it, which with the default configuration is exactly the case.
Three defects that predate this work and were found by running it, not by reading:
-
the network timeout killed the shell, not the worker. These commands run through
sudo, so killing the child leftgitalive holding the pipes, and the timeout error — already decided — was never reported: an installation stuck for seven minutes with not one line of log, indistinguishable from having no timeout at all; -
the tarball fallback could never succeed. It was downloaded into
/tmpand handed to theodoouser, and creating somebody else's file in a sticky world-writable directory is refused by the kernel, root included. Nobody had noticed because a fallback only runs when the clone has already failed; -
a step that failed halfway was not undone. It had usually created something already — the clone
makes its directories before going to the network — and that stayed on disk, which kept
/opt/odooalive through the rollback and made every later run find it “pre-existing”.
Nothing new to type. This release is about a promise made on the first page since the beginning — Odoo 16, 17, 18 and 19 — that nothing had ever checked: every CI configuration installed 18.0, so three of the four were accepted by the command line and run by nobody. It came back from a customer's machine: Odoo 16 did not install.
The cause was ours, not Odoo's. The installer put setuptools into the virtualenv without an upper
bound, which means whatever exists today — and setuptools 82 removed pkg_resources, which Odoo
16 imports on its first line. On Ubuntu 22.04 the virtualenv already ships a setuptools that has it:
the installer was replacing a working one with a broken one. The bound now sits below the release
that started warning about the module, not merely below the one that dropped it — that warning
would otherwise appear twice at every Odoo 16 start, in a version too old to silence it.
The other three already worked. Running all four end to end showed 17, 18 and 19 installing, serving over HTTP and rolling back cleanly on a first attempt. They were not broken; they were unverified — which is a different problem with the same cure. Each of the four now has a CI job that installs it and removes it again, and a test refuses to let the command line accept a version no job covers.
One combination was declared instead of promised. Odoo 16 did not install on Fedora ≥ 41: its
newest gevent pin predates Python 3.13, which is where every current Fedora lands. That is
upstream's shape, and rather than fail obscurely the installer names the interpreter and prints
the pins that version declares.
Superseded in 3.3.0, which made the ceiling depend on the Odoo version instead of being one number for all of them: Odoo 16 on Fedora now builds its venv on
python3.12. The line is kept here because it was true of 3.2.0, and this page records what each release was. Details in Multi-distribution support.
Repairs found by running the thing, none of which changes how you use it:
-
an interrupted installation no longer takes its neighbours down with it. The rollback that runs
when an installation fails halfway did not know the shared-artifact rule the uninstall command
has known since 3.1.0. It matters in one narrow case, and only there: an instance that created
/opt/odoo, the packages and the cluster, was interrupted, had a second instance installed beside it, and then failed on being resumed. It would have removed them from under a running instance; -
a permission kept on purpose now says so. Adding a named instance to a machine widens
/opt/odooby one traversal bit, and that bit legitimately stays while somebody still needs to walk through it. It stayed silently, so an administrator who removed an instance found a changed permission with nothing anywhere to attribute it to; -
the diagnosis that existed for a failure was silent on that very failure. The message
explaining a
geventbuild failure only appeared for a Python newer than the ones tested — and the case it was written for happens on exactly the newest tested one. Three hundred lines of compiler output and no explanation.
Two releases had made more than one Odoo per machine possible; this one makes it comfortable, and fixes the one thing that could bite a customer without anybody noticing.
Leaving dev no longer leaves an instance down. dev stops the service, opens a shell as the
instance's user, and used to leave it stopped — saying so, in a line read only by whoever was still
watching the screen. Now it puts the service back the way it found it. You are asked first, and the
answer you get by pressing Enter is always "as it was": an instance that was serving comes back up,
one you had deliberately switched off stays off. Close the window or kill the session and the same
rule is applied without asking, which is the case the behaviour exists for.
Proving that on a real machine found a defect none of the tests could see: with the terminal gone, the restore's own first
echofailed — the pty is closed — and underset -ethat ended the shell before it reached the service. The recovery path was being defeated by its own report, in precisely the scenario it was written for. It is an undo, and an undo carries on.
Three verbs can name another instance. status, logs and dev take an instance, so you can go
from list to what you meant without first remembering somebody else's helper name:
odoo list # what is here, what is up, what drives each
odoo status cliente-x
odoo logs cliente-x 500
odoo dev cliente-x # a shell as its user, to reach its filesstart, stop and restart do not, and that is a choice rather than an omission: looking at
somebody else's instance is not the hazard, starting it is. To act on a named instance you use its
own helper, which list tells you. For the same reason dev cliente-x opens a shell without
stopping that service — and says so, since its port stays busy.
The interactive form asks which instance this is, first, with an empty default that means the
historical one. Until now an instance could only be created with --instance or ODOO_INSTANCE, so
whoever sat at the terminal learned that a second Odoo was possible from a refusal, after the fact.
It is the first question because everything below it is named after it: asked at the end, a named
second instance would have inherited the first one's user, database and port.
Odoo 16 installs on Fedora. It did not, and the reason was upstream: its newest gevent pin is
written for Python >= 3.12 with nothing above it, so past 3.12 pip kept selecting a release whose
newest wheel is cp312 and had to compile. The ceiling the installer applies is now per Odoo
version — 3.12 for 16, 3.13 for the rest — so on Fedora it installs python3.12 for the occasion,
builds the venv on it, and the rollback takes it back with everything else. Ubuntu and Debian are
unaffected: their system Python was already below both.
This removes a declared limitation: the README, the package's own page and two wiki pages said the combination did not work. A CI job now installs and rolls it back on
fedora:41, and a test refuses to let a version with a ceiling of its own exist without such a job.
Looking for how to use the current version: Home, Quick start, The rollback model.
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: