Skip to content

Spell HTTP/2 for the installed Nginx and roll back a rejected vhost - #67

Merged
shipmill-romamo[bot] merged 1 commit into
mainfrom
fix/66-nginx-http2-and-rollback
Oct 9, 2026
Merged

shipmill-romamo[bot] merged 1 commit into
mainfrom
fix/66-nginx-http2-and-rollback

Conversation

@shipmill-romamo

Copy link
Copy Markdown
Contributor

Summary

Two fixes in engine/ansible/roles/cloudfall_nginx_site, as agreed on the issue.

  • HTTP/2 spelling. The TLS server block wrote http2 on;, which Nginx only parses from 1.25.1; on Debian 12's 1.22.1 nginx -t failed with unknown directive "http2". The role now runs nginx -v right after installing Nginx (changed_when: false, check_mode: false so the template renders in check mode too), asserts the stderr parses as nginx version: nginx/X.Y.Z (failing the converge with the output when it doesn't), and records cloudfall_nginx_site_version. The template uses Ansible's version test: listen 443 ssl http2; / listen [::]:443 ssl http2; below 1.25.1, the current listen 443 ssl; + http2 on; from 1.25.1. The rendered output on >= 1.25.1 is byte-identical to before (checked against the old template for 1.25.1 and 1.27.4, TLS and bootstrap).
  • Rollback of a rejected vhost. Both render + enable + nginx -t sequences (HTTP-only before issuance, TLS after) now run through one included file, tasks/apply_virtual_host.yml. Before rendering it stats the vhost and its sites-enabled link and keeps the current file's content with slurp (in memory, so no backup file is ever written where Nginx reads). On failure the rescue restores the previous file with its owner, group and mode, or removes a new file and a link this run created (a link that existed before stays), then fails with Nginx's stderr. The link handling follows the existing sites-available condition via a cloudfall_nginx_site_link fact (empty when the path is elsewhere).
  • Handlers. The rescue ends in ansible.builtin.fail, so the queued Reload Nginx does not run as long as force_handlers is off; neither domains.yml, engine/ansible/ansible.cfg nor the engine sets it. Even with it on, the reload would load the already restored state.
  • Order. Enable Nginx (start) now runs after the first configuration test instead of before it, so a rejected vhost never reaches a starting server. The final nginx -t now runs before the certbot timer is enabled rather than after.

No change to the Domain schema or the receipt. Check mode keeps skipping nginx -t and the rescue rolls nothing back there.

Test notes

  • engine/tests/test_nginx_site_templates.py: renders with Ansible's own tests registered and trim_blocks (the template module's default); new cases for 1.22.1, 1.24.0, 1.25.0 (ssl http2, no http2 on;) and 1.25.1, 1.26.3, 1.27.10 (unchanged block)
  • engine/tests/test_nginx_site_role.py (new, same harness as test_time_role.py, Ansible's Templar): version parsing from nginx -v stderr (incl. an (Ubuntu) suffix) and rejection of unparseable output or a failed probe; which rescue steps run for new file/new link, existing file/existing link, existing file/new link, a path outside sites-available, and check mode; the failure message carries Nginx's error; the link path fact
  • Passed locally: uv run ruff check ., uv run mypy, uv run pytest (517 passed), uv run cloudfall config validate --project config/examples, uv run ansible-lint engine/ansible (production profile), inventory render and ansible-playbook --syntax-check for all 12 playbooks
  • Not exercised on a real host: the rescue path (restore/remove against a live Nginx) is verified only through the Templar tests, lint and syntax check; there is no molecule or host harness in the repo

Fixes #66

🤖 Generated with Claude Code

The domain vhost wrote `http2 on;`, which Nginx only parses from 1.25.1;
Debian 12 ships 1.22.1, so `nginx -t` failed with `unknown directive
"http2"`. The role now reads the installed version with `nginx -v` (also in
check mode, failing the converge when it cannot be parsed) and writes
`listen 443 ssl http2;` below 1.25.1. Output on 1.25.1 and later is
unchanged.

A rejected render used to stay on disk and enabled, so the next reload (a
certbot hook, a reboot) broke every site on the host. Each render, link and
`nginx -t` now runs in a block whose rescue restores the previous file (kept
in memory with slurp, so no backup file lands where Nginx reads), or removes
a new file and a link this run created, then fails with Nginx's error. The
rescue ends in a failure, so with force_handlers off (neither the playbook,
ansible.cfg nor the engine set it) the queued reload never runs. Nginx is
started only after the first configuration test passes.

Fixes #66

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shipmill-romamo
shipmill-romamo Bot merged commit 9e51dc5 into main Oct 9, 2026
1 check passed
@shipmill-romamo
shipmill-romamo Bot deleted the fix/66-nginx-http2-and-rollback branch October 9, 2026 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

nginx site role writes http2 on; (nginx >= 1.25.1) and leaves an invalid vhost enabled when validation fails

0 participants