-
Notifications
You must be signed in to change notification settings - Fork 0
1.15 Step | Nginx (6 sub steps)
The Nginx reverse proxy, optional (
--with-nginx). It lives insrc/steps/nginx_*.rs. A port oflib/nginx.sh+templates/nginx.conf.tpl.Two things make it important despite being optional: it is the second monolith split (after the sources in 1.9–1.11), and the snapshot goes back to being a protection — two mutations touch Nginx and firewall configuration that is not ours.
setup_nginx becomes six steps. Without --with-nginx the whole phase is inert (Untracked
snapshot, no-op run and undo), like 1.3 SetupLogDir with the log file
disabled: adding them to the sequence is always safe.
| # | Step | Role | Undo |
|---|---|---|---|
| 1 | NginxInstall | install + enable | stop+disable; purge only with --aggressive-rollback (D3) |
| 2 | NginxWriteConfig | vhost from the template | remove if ours / restore the backup if pre-existing |
| 3 | NginxEnableSite | enable the site + free port 80 | remove our link + restore the default site |
| 4 | NginxSelinux | turn on httpd_can_network_connect where SELinux exists |
turn it off only if we turned it on |
| 5 | NginxFirewall | open the ports with the family's firewall | remove only the delta |
| 6 | NginxReload |
nginx -t + reload |
stop Nginx if we started it (D4) |
Reverse rollback: reload → firewall → selinux → enable-site → write-config → install.
Two treacherous mutations touch the customer's system configuration. Without a correct snapshot, a
rollback would leave the system worse than it found it. The “record the pre-existing state, restore
it in the undo” pattern — already seen with the odoo.conf backup in
1.12 — here protects Nginx and the firewall.
Nginx's conventions change with the family, and the step does not know it: it asks the
Distroboundary where the vhost goes and whether there is a default site to move out of the way. On Debian/Ubuntu it issites-available+ a symlink insites-enabled; on Fedora it isconf.d/*.conf, with no symlink and no default site to remove — there the default server is a block insidenginx.conf, and rewriting the main configuration of a customer's service is not something we do. Declared consequence: on Fedora a request to a hostname that does not matchNGINX_SERVER_NAMEstill gets the welcome page. Detail in Multi-distribution support.
To free port 80, sites-enabled/default has to be moved out of the way — and that is the customer's
pre-existing configuration.
snapshot: record WHAT is there (path_kind): absent | symlink{target} | regular file | other
run: symlink → remove it (it has no content of its own)
regular file → MOVE it to a backup, never delete it
other → do not touch it, and say so
undo: symlink → recreate it towards the RECORDED TARGET
regular file → put the backup back where it was
Recording whether it was there is not enough. While the snapshot was a bool, two defects grew
out of it:
-
restoration was not faithful: the undo always recreated a symlink towards
/etc/nginx/sites-available/default, the distribution-standard target. If the customer's default pointed somewhere else, their config did not come back as it was — it came back as it usually is. For a project that restores.bashrcbyte for byte, that was a double standard; -
a file could be lost:
symlink_existsusessymlink_metadata, which answerstruefor a regular file too, andremove_symlinkisfs::remove_file. An administrator who had writtensites-enabled/defaultas a real file would find it destroyed, and the undo would hand back a symlink to the distro default. Not a leftover: a loss of configuration.
The backup of a regular file lives in
/etc/nginx/, not insites-enabled/:nginx.confincludessites-enabled/*— every file, not only.confones — so a backup left there would be loaded and port 80 would stay occupied. That would be the same defect under another name.
If the default site did not exist → we do not invent one.
Where SELinux is enforcing — Fedora — a proxy towards 127.0.0.1:8069 is denied even with a valid
nginx -t and a successful reload: the symptom is a 502, and nothing in Nginx's configuration
explains it. The httpd_can_network_connect boolean has to be turned on with setsebool -P, that is
persistently — so it is an artifact to record and to turn back off in the undo, but only if it was
off before us.
If getsebool is not executable (no SELinux, or the tools are not installed) the answer is not “off”
but “I do not know”, and nothing is concluded from it: the step does not touch the policy of a system it
cannot question.
Firewall rules are the customer's configuration. The delta pattern applies (as with packages in 1.4):
delta = the desired rules (80/tcp, + 443/tcp with --open-https-port) NOT already present
run: open only the delta
undo: remove ONLY the delta — never a rule the customer already had
If the firewall is not installed or not active → no-op (we do not force a firewall on).
“Already present” is decided by token, not by substring. The comparison used to run
containsover the output ofufw status, and"80/tcp"is contained in"8080/tcp": on a machine with another web app, the rule for port 80 did not enter the delta, was never opened, and nginx stayed unreachable from outside with nothing to signal it. Now the first token of each line — theTocolumn — is compared, with the headings excluded.
The preflight used to demand port 80 be free whenever --with-nginx was requested. But the
supported scenario — and the one these steps handle explicitly, with NginxInstall marking an
already-running nginx as Preexisting and not touching it — is precisely adding a vhost to an nginx
that is already running. On such a machine port 80 is held by nginx, the very program we are about
to configure.
A port held by us is not a conflict. If nginx is not serving and 80 is taken (Apache, another proxy), the conflict is real and the refusal stands: nginx would not even manage to bind.
nginx keeps one namespace for every vhost it loads. The template used to declare
upstream odoo and upstream odoo-longpolling — global names — so the second instance made
nginx -t fail with a duplicate upstream, which failed the reload step, which rolled back the whole
installation. Loud rather than silent, but a hard blocker on the only reason to put two instances
behind one proxy.
The upstreams now carry the instance name, together with the proxy_pass that uses them. The
longpolling upstream had the same problem more quietly: it pointed at a hardwired 127.0.0.1:8072,
so both vhosts sent every websocket to whichever instance had managed to bind that port. It now
carries this instance's gevent port.
What still has to differ between two vhosts is --server-name: with the catch-all _ on both, nginx
answers for the same hostname and serves whichever it loaded first. The log files are already per
instance.
The generated vhost listens on port 80 only, deliberately. The supported way to get HTTPS is
certbot --nginx, which obtains the certificates and rewrites the vhost itself, adding the 443
block and the redirect. Generating one of our own would mean competing with it — and, if it pointed at
certificates that do not exist, nginx -t would fail and take the whole installation with it.
The --open-https-port flag (formerly --enable-ssl) only opens 443 on the firewall ahead of that
step: it does not touch the vhost.
-
NginxInstall — policy consistent with PostgreSQL (D3): stop+disable if we enabled it, but purge
only with
--aggressive-rollback. -
NginxWriteConfig — the vhost is rendered from the embedded template (
include_str!); if a vhost with the same name pre-existed, it is backed up before overwriting and restored in the undo. -
NginxReload — runs
nginx -tand reloads only if the config is valid (no reloading a broken config → error). Its undo stops Nginx if we started it (D4).
The realignment reload lives in
NginxInstall::undo, not inNginxReload::undo, and must not be moved (bug A1.4, found by the end-to-end test). Undos run in reverse:NginxReload's is the first of the phase, when the configurations have not been restored yet;NginxInstall's is the last. Reloading too early leaves the customer's nginx serving our config after the files behind it have gone.
-
No new shared state: the gating is a local
ctx.with_nginxcheck in each step; noArcadded to the Context. -
render_vhost/validate_vhostare pure functions, tested separately. - Tests: gating (all six steps inert without
--with-nginx), default site removed → restored, an absent default not invented, the firewall delta (only 443 removed, never the customer's 80), an inactive firewall no-op, failednginx -t→ error, D3 on install, and the complete rollback through the engine (default site restored + only the firewall delta removed).
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: