Skip to content

1.14 Step | SetupSystemd

Omisen edited this page Aug 15, 2026 · 4 revisions

Installs and activates Odoo's systemd service: renders the unit from the template, installs it into /etc/systemd/system/, then daemon-reload, enable, start. It lives in src/steps/setup_systemd.rs. A port of lib/systemd.sh + templates/odoo.service.tpl.

A consolidation step: it reuses two already-proven patterns — the three independent PreStates of 1.6 SetupPostgres and the embedded template of 1.12 GenerateConfig.


Three independent PreStates (D4)

struct SystemdSnapshot {
    unit_file: PreState,  // does /etc/systemd/system/odoo<N>.service already exist?
                          // (odoo-<instance>.service for a named instance: the
                          // name is what tells two installations apart, so the
                          // version stops being part of it)
    enabled:   PreState,  // was the service already enabled?
    active:    PreState,  // was the service already running?
}
Phase Behaviour
snapshot for each axis: already true before us → Preexisting; otherwise Untracked
run installs the unit (idempotent), daemon-reload, enable (if not already), start/restart, checks is-active
undo restores each CreatedByUs axis, in the order stop → disable → rm → reload

Rule D4: a service that was already running or enabled before us is left as it was; a pre-existing unit file is not removed.


The undo's ordering (the specific care here)

stop      (if active == CreatedByUs)
  └─ disable   (if enabled == CreatedByUs)
       └─ rm the unit file   (if unit_file == CreatedByUs)
            └─ daemon-reload      ← systemd forgets the removed unit

Stopping and disabling before removing the file keeps systemd from holding references to a unit that has vanished. The final daemon-reload closes the loop. Every step is best-effort and idempotent.


Rendering + preserved hardening

The unit is rendered from the embedded template by substituting {{VAR}} placeholders (version, user, install_dir, repo/venv). Validation: no {{...}} placeholder left.

The template's hardening is preserved and must not be watered down:

Under the “Security hardening” heading there was, for a long time, a single directive: PermissionsStartOnly=truedeprecated since systemd 231 (2016) and ignored with a warning. Its job was to run ExecStartPre commands as root, and there are no ExecStartPre commands. It was a heading with no content: removed, and replaced with the directives that actually move the needle for a network-facing process.

Directive Value
User / Group odoo (the service is not root)
NoNewPrivileges true
PrivateTmp true
RuntimeDirectory / RuntimeDirectoryMode odoo / 0750
ProtectSystem full (/usr and /boot read-only; /opt stays writable)
ProtectHome true
PrivateDevices true
ProtectKernelTunables / ProtectKernelModules / ProtectControlGroups true
RestrictSUIDSGID / LockPersonality true
RestrictAddressFamilies AF_UNIX AF_INET AF_INET6
Requires postgresql.service
Restart on-failure
StartLimitIntervalSec / StartLimitBurst burst protection

AF_UNIX is not optional in RestrictAddressFamilies: it is PostgreSQL's socket. Omitting it would give a service that starts and cannot reach the database — a failure that would look like something else entirely.

ProtectSystem=strict is deliberately excluded: it would require an exact list of ReadWritePaths (install dir, filestore, cache, sessions), and getting one wrong breaks the service on a customer machine. full covers /usr and /boot, which is the real gain for a process that already runs unprivileged.

Installing the file: private temp → movechmod 644chown root:root. Final is-active check, with a journalctl -u odoo<N> hint if it did not come up.


Architectural notes

  • No shared state: the three PreStates are local to the step. Unlike 1.13 InitializeOdooDatabase, no step-to-step channel is needed here — no new Arc<Atomic…> in the Context. Keeping shared state under control is what keeps the rollback traceable.
  • render_unit and validate_unit are pure functions, tested separately.
  • Tests: all-absent (installs; the undo order stop→disable→rm→reload is verified), D4 on active (already running → restart in run, no stop in undo), D4 on enabled (already enabled → no disable), failed start → error, rendering (no leftovers, hardening present).

Clone this wiki locally