-
Notifications
You must be signed in to change notification settings - Fork 0
Multi distribution support
How the same installer works on Ubuntu, Debian and Fedora without the steps knowing which distribution they are running on. If you are looking for “what do I install”, you want the Quick start; this page explains the how, and it is the page to read before adding a family or touching a package name.
| Family | Minimum | Exercised in CI, full cycle |
|---|---|---|
| Ubuntu | ≥ 22.04 | 22.04, 24.04 (native runners) |
| Debian | ≥ 11 | 11 and 12 (containers — a probe: without systemd as PID 1 it stops at setup-postgres) |
| Fedora | ≥ 40 | 41 and 44 (privileged container with systemd as PID 1) |
“Exercised” has a precise meaning here: the integration CI really installs, checks that Odoo answers
over HTTP, then runs invok rollback and checks that nothing is left. The thresholds are open
upwards: a newer release is accepted with a warning, never refused — a refusal without evidence
blocks the good case, and a blocked installation is a certain harm while the avoided one was
hypothetical.
The divergence between families is not one thing but two, and keeping them apart is what prevents an abstraction that abstracts two different things.
| Boundary | Answers | Lives in |
|---|---|---|
PackageManager |
which commands install and remove, and what the packages are called here |
src/packaging/ (apt.rs, dnf.rs) |
Distro |
where the files live, which concepts exist, which tool governs the firewall |
src/distro/ (debian.rs, fedora.rs) |
Neither is a second door onto the system: both are obtained from SystemOps (ops.packages(),
ops.distro()), so what touches the machine stays in one place and the tests still mock only one
thing.
Steps never match on the distribution. They ask a boundary, and the boundary answers for the family
being run on.
OsFamily is persisted in the manifest and re-read by the rollback command. It is not re-detected
when the rollback starts, and it is not inferred from “which package manager is installed”: the
manifest might describe an installation made elsewhere, and a machine with both managers would answer
wrongly in silence. It is the same rule that governs artifact ownership — who created what is
re-read from the manifest, not re-derived from the system.
| Aspect | Debian / Ubuntu | Fedora |
|---|---|---|
| Manager |
apt-get / dpkg
|
dnf / rpm
|
| PostgreSQL marker | postgresql |
postgresql-server (the client is not enough to say the server is there) |
| PostgreSQL cluster | created by the package's postinst |
does not exist until you create it: needs postgresql-setup --initdb
|
| wkhtmltopdf |
.deb per codename (jammy, bullseye, bookworm) |
the fedora37 .rpm, the only one built for this family in the pinned release |
| Nginx vhost |
sites-available + symlink in sites-enabled
|
conf.d/*.conf, no symlink
|
| Nginx default site | the sites-enabled/default file/symlink, removed and restored by the rollback |
not a file: a block inside nginx.conf, and it is left alone |
| SELinux | not applicable |
httpd_can_network_connect must be turned on, or the proxy answers 502 with a valid nginx -t
|
| Firewall | ufw |
firewalld |
Two consequences worth reading in full, because they are choices and not details:
-
The Nginx default site on Fedora is not removed. There the default server is a
server { listen 80 default_server; … }block inside/etc/nginx/nginx.conf: removing it would mean rewriting the main configuration of a customer's service. The practical effect, declared: on a Fedora with a freshly installed Nginx, a request to a hostname that does not matchNGINX_SERVER_NAMEstill gets the welcome page instead of Odoo. -
The PostgreSQL cluster is a state axis of its own (the fourth in
setup-postgres): if we initialised it, the rollback can remove it — but only with--aggressive-rollbackand only if it hosts no other databases. An empty data directory is an inert leftover; somebody else's data is not.
One need (“we want the libjpeg headers”) has different names on different distributions, and sometimes different names on different releases of the same distribution. So each family's catalogue is not a list of names but a list of alternative groups, resolved at runtime in this order:
- an alternative that is already installed wins (we do not add
libtiff5-devto a machine that haslibtiff-dev, and the delta stays honest); - otherwise the first one with a real candidate;
- otherwise the first one that is installable anyway — a purely virtual name, used only as a fallback, because a virtual name cannot be removed and a delta containing one would lie to the rollback.
If no alternative is available the error arrives in the snapshot, before mutating, with the unresolved group in the message. And if the package index cannot be queried, the message says so rather than sending you off to look for the rename of a package that is sitting perfectly where it always was: blindness is not absence.
A test ties the two catalogues together: every declared need must be covered by both families. Without it, a dependency gets added to one only and the gap surfaces when a VM stops compiling — the most expensive place possible.
Odoo pins gevent and greenlet per Python version. On an interpreter newer than its pins there is
no prebuilt wheel: pip tries to build from source and the generated C does not survive a newer CPython's
headers. That is the case on Fedora ≥ 43, where the system python3 is 3.14.
The installer's rule, in one line:
the system
python3if Odoo's pins cover it, otherwise the newest interpreter packaged by the distribution that they do cover.
On Fedora 44 that means python3.13 (with its headers), installed for the occasion and removed by
the rollback like any other package in the delta — it is brought in by
install-system-dependencies, whose undo purges, and not by bootstrap-prerequisites, which leaves
what it adds installed.
On Debian and Ubuntu nothing changes: the base repositories have one Python and it is inside the pins. If one day it were not, the installer does not refuse: it warns at preflight, and if pip fails to build it says the cause is the Python version — not the build environment, not a missing package.
Every current Fedora would otherwise build the venv on Python 3.13 — as the system interpreter
(41, 42) or as the newest one within the ceiling (>= 43). Odoo 16 has no pin for 3.13: its newest
gevent line is written for python_version >= '3.12' and stops there, with nothing above it,
so it keeps selecting gevent==24.2.1 — released before 3.13 existed, and with no wheel for it. pip
would compile from source, and the C an older Cython generates does not survive 3.13's headers: the
build dies on _PyLong_AsByteArray, whose signature changed in that release.
Odoo 17, 18 and 19 pin gevent==24.11.1 for >= '3.13', which ships a wheel.
So the ceiling is per Odoo version, not one number for all of them: newest_tested_python reads
3.12 for 16 and 3.13 for the rest, and choose_python takes it from there. On Fedora that means
python3.12 — packaged by 41 through 44 — is installed for the occasion, the venv is built on it,
and the rollback takes it back with the rest of the delta. On Ubuntu and Debian nothing changes:
their system Python is already below both ceilings, so the exception never fires.
The number is a measurement, not a judgement about age: 16 is the only branch whose newest
gevent bracket has no upper bound, which is why it is the only one with an exception. It is read
from Odoo's own file, and the command to redo it is in the doc-comment:
curl -fsSL https://raw.githubusercontent.com/odoo/odoo/16.0/requirements.txt | grep -E '^gevent'Proven on a real Fedora 44 before the entry was added to CI: venv on 3.12.13, gevent 24.2.1
installed from a wheel, the service up and answering HTTP 200, and a rollback that removed
python3.12 and python3.12-devel along with everything else, leaving the system interpreter at
3.14.6 untouched. A CI job then installs and rolls back Odoo 16 on fedora:41, and a test refuses to
let a version with its own ceiling exist without such a job — a ceiling nobody executes is a promise.
If a combination ever does fall outside the pins, the installer still does not refuse: it warns
at preflight, and if pip fails it names the interpreter and prints the gevent/greenlet lines that
Odoo version declares, so the cause is readable instead of buried in the compiler's output.
Everything downstream of the virtualenv knows nothing about this choice: the systemd unit, the control
script and the database initialisation all go through <venv>/bin/python3, which the venv creates under
that name whatever the base interpreter was.
The integration CI matrix is designed so that every diverging branch is executed by someone:
| Job | What it exercises that the others do not |
|---|---|
native (Ubuntu 22.04 / 24.04) |
full life cycle on a non-pristine machine (preinstalled packages that must survive) |
container (Debian 11 / 12) |
package names and the wkhtmltopdf pin on genuinely minimal images |
fedora:41 |
the rpm family with a system Python covered by the pins |
fedora:44 |
the rpm family with the alternative interpreter (the other branch of the choice) |
fedora:41 / nginx |
firewalld and SELinux — the only place they really run |
nginx (Ubuntu) |
the two natures of the default site, restored byte for byte |
preexisting-user |
an odoo user already present, with and without a home |
interrupt |
a real SIGINT during the installation |
What is not covered by a real run is declared where it lives rather than hidden: currently the precondition on a divergent PGDATA (a machine with a drop-in that moves the cluster) lives only in the mock tests.
-
OsFamily(a new variant) and the threshold inchecks::validate_os; - a
PackageManagerbackend with its catalogue — every need, or the parity test fails; - a
Distrobackend with the Nginx layout, firewall, SELinux and PostgreSQL cluster; - the TOFU wkhtmltopdf pins for that family's packages (fail-closed: no pin, no install);
- an entry in the
integration.ymlmatrix, because a family nobody runs is a promise, not a feature.
The Step trait is not touched: if it seems necessary, that is the signal that the divergence has
ended up in the wrong place.
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: