Skip to content

History

Omisen edited this page Aug 17, 2026 · 5 revisions

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.

From 2.1.0 to 2.2.0

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 invok destroyed it silently.
  • /opt/odoo really 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 rollback or --force). Instances installed with 2.1.0 remain uninstallable-by-command: the historical manifest path is still read.

From 2.2.0 to 2.3.0: the third family

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 -t and a successful reload: the symptom is a 502 and nothing in the configuration explains it;
  • from Fedora 43 the system python3 is 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.

From 2.3.0 to 3.0.0: the name, and how you get the command

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.

How you get the installer

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.

From 3.0.0 to 3.1.0: more than one Odoo per machine

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 --all

Three 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.

Also in 3.1.0

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 left git alive 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 /tmp and handed to the odoo user, 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/odoo alive through the rollback and made every later run find it “pre-existing”.

From 3.1.0 to 3.2.0: the versions the README had always promised

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.

Also in 3.2.0

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/odoo by 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 gevent build 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.

From 3.2.0 to 3.3.0: the helper stops being the risky part

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 echo failed — the pty is closed — and under set -e that 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 files

start, 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.

Clone this wiki locally