feat(mail): channel → nixos-26.05 [upgrade 4/6 · bump] #107

Closed
opened 2026-06-06 12:07:03 +02:00 by dominik.polakovics · 2 comments

Hold until [3/6 · verify] is closed. Then arm: relabel ready-for-agent.

Task: bump hosts/mail/channel to nixos-26.05; open PR. Heaviest agent work in the rollout.

26.05 specifics for mail:

  • Dovecot (top risk): the services.dovecot2 module is converted to RFC-42-style structured settings. hosts/mail/modules/dovecot.nix carries extensive raw config + LDAP passdb/userdb. Fix all eval breakage; preserve the LDAP auth wiring.
  • Postfix: services.postfix.sslCert/sslKey are removed → migrate to settings.main.smtpd_tls_chain_files only if used. Current config appears to use smtpd_tls_* settings already — verify, migrate only if eval flags it. (Touches recently-hardened TLS, ADR-0016.)
  • rspamd → 4.0: check config compat (sharded-Redis note; mail is single-instance).
  • OpenLDAP: no 26.05 breaking change — the fleet-auth keystone is safe at config level.
  • stateVersion stays 22.11.

Acceptance: pre-commit eval green against 26.05; PR notes every dovecot/postfix/rspamd change made.

▶ **Hold** until [3/6 · verify] is closed. Then arm: relabel `ready-for-agent`. **Task:** bump `hosts/mail/channel` to `nixos-26.05`; open PR. **Heaviest agent work in the rollout.** **26.05 specifics for mail:** - **Dovecot (top risk):** the `services.dovecot2` module is converted to RFC-42-style structured settings. `hosts/mail/modules/dovecot.nix` carries extensive raw config + LDAP passdb/userdb. Fix all eval breakage; **preserve the LDAP auth wiring**. - **Postfix:** `services.postfix.sslCert`/`sslKey` are removed → migrate to `settings.main.smtpd_tls_chain_files` **only if used**. Current config appears to use `smtpd_tls_*` settings already — verify, migrate only if eval flags it. (Touches recently-hardened TLS, ADR-0016.) - **rspamd → 4.0:** check config compat (sharded-Redis note; mail is single-instance). - **OpenLDAP:** no 26.05 breaking change — the fleet-auth keystone is safe at config level. - stateVersion stays `22.11`. **Acceptance:** pre-commit eval green against 26.05; PR notes every dovecot/postfix/rspamd change made.
Author
Owner

This was generated by AI during triage.

Scope addition: drop promtail on the 26.05 bump

26.05 removed services.promtail (promtail is EOL), so this bump must also drop mail's ./utils/modules/promtail import -- otherwise eval fails on an unknown option. Mirror nb/nas: replace the import with a short comment noting central journald->loki shipping is paused pending the SEPARATE promtail->grafana-alloy migration (#118). Local journald is unaffected; alloy setup is NOT in scope here.

mail is a plain promtail client -- no Loki server side to worry about.

Full agent brief will be posted when this issue is armed.

> *This was generated by AI during triage.* ## Scope addition: drop promtail on the 26.05 bump 26.05 **removed `services.promtail`** (promtail is EOL), so this bump must also drop mail's `./utils/modules/promtail` import -- otherwise eval fails on an unknown option. Mirror nb/nas: replace the import with a short comment noting central journald->loki shipping is paused pending the SEPARATE promtail->grafana-alloy migration (#118). Local journald is unaffected; alloy setup is NOT in scope here. mail is a plain promtail client -- no Loki server side to worry about. Full agent brief will be posted when this issue is armed.
Author
Owner

This was generated by AI during triage.

Agent Brief

Category: enhancement (channel upgrade)
Summary: Bump mail to nixos-26.05 — flip the host channel, port services.dovecot2 to its 26.05 module shape while preserving the LDAP passdb/userdb auth, handle any postfix/rspamd 26.05 option changes, and — the part the eval-only convention misses — validate the generated dovecot/postfix/rspamd configs against the 26.05 binaries, not just nix eval. Minimal diff: channel flip + forced fixes only.

Current behavior:
mail tracks nixos-25.11, system.stateVersion = "22.11". It is two keystones at once: the fleet-auth anchor (OpenLDAP — every host authenticates against it) and the production mail server (Postfix MTA, Dovecot IMAP/LMTP/sieve, rspamd).

  • Dovecot: services.dovecot2 with structured options (enable, enableImap, enableLmtp, mailLocation, mailUser/mailGroup, sieve.extensions, protocols) plus a large raw extraConfig blob that carries the real config: ssl_*, mail_plugins, service {} listener definitions (lmtp / doveadm / auth / imap-login / managesieve-login / quota-*), userdb/passdb blocks (driver = ldap, args pointing at runtime-generated /run/dovecot2/ldap.conf + ldap-fallback.conf), and a plugin {} block (sieve, fts lucene, quota). LDAP auth is wired by generating those two ldap.conf files in the dovecot preStart — a sed injects the dovecot-ldap-password sops secret into a writeText template. A second module (the rspamd integration) also appends to services.dovecot2.extraConfig (imapsieve + sieve_extprograms + a callPackage'd sieve-spam-filter).
  • Postfix: services.postfix with enable, enableSubmission, settings.main.*, and a config = { } block holding the main.cf body. TLS uses raw smtpd_tls_cert_file / smtpd_tls_key_file / smtpd_tls_CAfile (ACME certs) — not the services.postfix.sslCert/sslKey options. Outbound is pinned to IPv4 (#97); TLS was hardened per ADR-0016.
  • rspamd: services.rspamd with extraConfig, locals, and a controller worker with hashed passwords. Single-instance (no sharded Redis).
  • Logging: already on grafana-alloy (imports the shared alloy module). The earlier "drop promtail" note on this issue is obsolete — that migration landed in #124/#125.
  • No nixpkgs.config insecure/unfree allowances today.
  • ACME via the shared lego module already uses environmentFile (landed with #128) — no further lego change needed here.

Desired behavior: mail evaluates, builds, and its generated service configs parse on the 26.05 binaries; OpenLDAP auth, IMAP/LMTP, submission, sieve, and spam filtering all keep working. Diff = channel flip + the forced fixes below.

Known 26.05 specifics for mail:

  1. Dovecot — the headline risk, two layers:
    a. Module refactor (eval-catchable). 26.05 restructures the dovecot NixOS module (reported as an RFC-42 settings-style conversion). Port the structured options to their 26.05 shape and preserve the extraConfig escape-hatch contents — or migrate them into the new settings interface if extraConfig is removed. Both the main dovecot module and the rspamd-integration module feed the dovecot config; keep both working.
    b. Possible Dovecot upstream major bump (runtime-only — NOT caught by eval or nix-build). Determine the dovecot version 26.05 ships. If it is a major bump (e.g. 2.3 → 2.4), the raw config syntax (service {}, userdb/passdb, plugin {}, ssl_*) may use removed/renamed directives that only fail when dovecot.service starts. You must validate the generated dovecot config against the 26.05 dovecot binary — build the closure and run a config parse (doveconf -n / dovecot config-check) — and fix any syntax migration. Eval-green is necessary but not sufficient.
    c. Preserve the LDAP auth wiring exactly: the two runtime-generated ldap.conf files (primary + fallback), the preStart that injects the dovecot-ldap-password sops secret, and the userdb/passdb driver = ldap references. Breaking auth = total IMAP lockout for every mailbox.

  2. Postfix (low, eval-catchable): services.postfix.sslCert/sslKey are removed on 26.05, but mail doesn't use them (TLS is via raw smtpd_tls_*_file keys) — that migration is N/A; confirm. The likely chore is migrating the remaining services.postfix.config = { } block to settings.main (the config alias may be gone; the module already uses settings.main for one key). Keep the IPv4 outbound pin (#97) and the ADR-0016 TLS hardening intact. Run postfix check against the built config.

  3. rspamd → 4.0 (medium): confirm config compatibility. mail is single-instance, so the 4.0 sharded-Redis change doesn't apply. Watch for renamed modules/symbols in the local config (dkim_signing, arc, dmarc, neural, phishing, milter_headers) and the controller worker password format. Confirm rspamd loads its config on 4.0.

  4. Insecure packages (low): mail carries no permittedInsecurePackages today, but it will likely hit the same pypy2.7-* makePythonWriter guard nb/nas/fw hit (force-evaluated via fetch-cargo-vendor-util for any Rust pkg in the closure, pulled in by the users-groups shell-program-check). If eval/build flags it, add nixpkgs.config.allowInsecurePredicate permitting lib.hasPrefix "pypy2.7-" using the same predicate shape as the other hosts (read pkg.name with a pname/version fallback). Don't add it speculatively if eval is clean.

  5. system.stateVersion stays "22.11".

Acceptance criteria:

  • mail's channel → nixos-26.05.
  • Pre-commit eval green for mail on 26.05; any shared-module edit leaves the still-25.11 hosts (web-arm, amzebs-01) green too.
  • A real mail system-closure build succeeds (eval-only misses 26.05 build-gates).
  • Generated dovecot config validated against the 26.05 dovecot binary (doveconf / parse check passes); any syntax migration applied and documented in the PR.
  • postfix check passes against the built config; rspamd loads its config on 4.0.
  • LDAP passdb/userdb wiring preserved: both ldap.conf files still generated at preStart with the sops password injected; userdb/passdb still driver = ldap.
  • Any insecure allowance is via allowInsecurePredicate (pypy2.7- prefix), justified in the PR; no leftover permittedInsecurePackages the predicate would silently disable.
  • system.stateVersion stays "22.11"; diff limited to channel flip + required fixes.
  • PR notes every dovecot / postfix / rspamd change made.

Out of scope:

  • The keystone reboot + runtime verification (fleet-wide LDAP auth, real IMAP login, send/receive, spam filtering) — that's the paired verify #108 (ready-for-human).
  • Logging / promtail / alloy (done — #124/#125).
  • OpenLDAP changes (no 26.05 breaking change at config level).
  • The lego/acme module (already on environmentFile via #128).
  • Other hosts' channels.

⚠ Note for the human landing this PR (not the agent): bento auto-switches and restarts dovecot/postfix on deploy. Unlike a build failure (which never deploys), a config-syntax break that slips validation deploys cleanly and then kills the service on restart — a live IMAP/SMTP outage before the #108 verify. Land with a console ready, and consider pausing mail's bento-upgrade until you deploy by hand.

> *This was generated by AI during triage.* ## Agent Brief **Category:** enhancement (channel upgrade) **Summary:** Bump mail to nixos-26.05 — flip the host channel, port `services.dovecot2` to its 26.05 module shape while preserving the LDAP passdb/userdb auth, handle any postfix/rspamd 26.05 option changes, and — the part the eval-only convention misses — **validate the generated dovecot/postfix/rspamd configs against the 26.05 binaries**, not just nix eval. Minimal diff: channel flip + forced fixes only. **Current behavior:** mail tracks nixos-25.11, `system.stateVersion = "22.11"`. It is two keystones at once: the **fleet-auth anchor** (OpenLDAP — every host authenticates against it) and the **production mail server** (Postfix MTA, Dovecot IMAP/LMTP/sieve, rspamd). - **Dovecot:** `services.dovecot2` with structured options (`enable`, `enableImap`, `enableLmtp`, `mailLocation`, `mailUser`/`mailGroup`, `sieve.extensions`, `protocols`) **plus a large raw `extraConfig` blob** that carries the real config: `ssl_*`, `mail_plugins`, `service {}` listener definitions (lmtp / doveadm / auth / imap-login / managesieve-login / quota-*), `userdb`/`passdb` blocks (`driver = ldap`, `args` pointing at runtime-generated `/run/dovecot2/ldap.conf` + `ldap-fallback.conf`), and a `plugin {}` block (sieve, fts lucene, quota). LDAP auth is wired by generating those two ldap.conf files in the dovecot `preStart` — a `sed` injects the `dovecot-ldap-password` sops secret into a `writeText` template. A **second module** (the rspamd integration) *also* appends to `services.dovecot2.extraConfig` (imapsieve + `sieve_extprograms` + a `callPackage`'d sieve-spam-filter). - **Postfix:** `services.postfix` with `enable`, `enableSubmission`, `settings.main.*`, and a `config = { }` block holding the main.cf body. TLS uses raw `smtpd_tls_cert_file` / `smtpd_tls_key_file` / `smtpd_tls_CAfile` (ACME certs) — **not** the `services.postfix.sslCert`/`sslKey` options. Outbound is pinned to IPv4 (#97); TLS was hardened per ADR-0016. - **rspamd:** `services.rspamd` with `extraConfig`, `locals`, and a `controller` worker with hashed passwords. Single-instance (no sharded Redis). - **Logging:** already on grafana-alloy (imports the shared `alloy` module). The earlier "drop promtail" note on this issue is **obsolete** — that migration landed in #124/#125. - **No** `nixpkgs.config` insecure/unfree allowances today. - ACME via the shared lego module already uses `environmentFile` (landed with #128) — no further lego change needed here. **Desired behavior:** mail evaluates, **builds**, and its generated service configs **parse on the 26.05 binaries**; OpenLDAP auth, IMAP/LMTP, submission, sieve, and spam filtering all keep working. Diff = channel flip + the forced fixes below. **Known 26.05 specifics for mail:** 1. **Dovecot — the headline risk, two layers:** a. **Module refactor (eval-catchable).** 26.05 restructures the dovecot NixOS module (reported as an RFC-42 `settings`-style conversion). Port the structured options to their 26.05 shape and preserve the `extraConfig` escape-hatch contents — or migrate them into the new `settings` interface if `extraConfig` is removed. Both the main dovecot module **and** the rspamd-integration module feed the dovecot config; keep both working. b. **Possible Dovecot upstream major bump (runtime-only — NOT caught by eval or nix-build).** Determine the dovecot version 26.05 ships. If it is a major bump (e.g. 2.3 → 2.4), the raw config syntax (`service {}`, `userdb`/`passdb`, `plugin {}`, `ssl_*`) may use removed/renamed directives that only fail when `dovecot.service` starts. **You must validate the generated dovecot config against the 26.05 dovecot binary** — build the closure and run a config parse (`doveconf -n` / `dovecot` config-check) — and fix any syntax migration. **Eval-green is necessary but not sufficient.** c. **Preserve the LDAP auth wiring exactly:** the two runtime-generated ldap.conf files (primary + fallback), the `preStart` that injects the `dovecot-ldap-password` sops secret, and the `userdb`/`passdb` `driver = ldap` references. Breaking auth = total IMAP lockout for every mailbox. 2. **Postfix (low, eval-catchable):** `services.postfix.sslCert`/`sslKey` are removed on 26.05, but mail doesn't use them (TLS is via raw `smtpd_tls_*_file` keys) — that migration is **N/A**; confirm. The likely chore is migrating the remaining `services.postfix.config = { }` block to `settings.main` (the `config` alias may be gone; the module already uses `settings.main` for one key). Keep the IPv4 outbound pin (#97) and the ADR-0016 TLS hardening intact. Run `postfix check` against the built config. 3. **rspamd → 4.0 (medium):** confirm config compatibility. mail is single-instance, so the 4.0 sharded-Redis change doesn't apply. Watch for renamed modules/symbols in the local config (`dkim_signing`, `arc`, `dmarc`, `neural`, `phishing`, `milter_headers`) and the controller worker password format. Confirm rspamd loads its config on 4.0. 4. **Insecure packages (low):** mail carries no `permittedInsecurePackages` today, but it will likely hit the same `pypy2.7-*` `makePythonWriter` guard nb/nas/fw hit (force-evaluated via `fetch-cargo-vendor-util` for any Rust pkg in the closure, pulled in by the users-groups shell-program-check). If eval/build flags it, add `nixpkgs.config.allowInsecurePredicate` permitting `lib.hasPrefix "pypy2.7-"` using the same predicate shape as the other hosts (read `pkg.name` with a `pname`/`version` fallback). Don't add it speculatively if eval is clean. 5. **`system.stateVersion` stays `"22.11"`.** **Acceptance criteria:** - [ ] mail's channel → nixos-26.05. - [ ] Pre-commit eval green for mail on 26.05; any shared-module edit leaves the still-25.11 hosts (web-arm, amzebs-01) green too. - [ ] A real mail system-closure **build** succeeds (eval-only misses 26.05 build-gates). - [ ] **Generated dovecot config validated against the 26.05 dovecot binary** (`doveconf` / parse check passes); any syntax migration applied and documented in the PR. - [ ] `postfix check` passes against the built config; rspamd loads its config on 4.0. - [ ] LDAP passdb/userdb wiring preserved: both ldap.conf files still generated at `preStart` with the sops password injected; `userdb`/`passdb` still `driver = ldap`. - [ ] Any insecure allowance is via `allowInsecurePredicate` (`pypy2.7-` prefix), justified in the PR; no leftover `permittedInsecurePackages` the predicate would silently disable. - [ ] `system.stateVersion` stays `"22.11"`; diff limited to channel flip + required fixes. - [ ] PR notes every dovecot / postfix / rspamd change made. **Out of scope:** - The keystone reboot + runtime verification (fleet-wide LDAP auth, real IMAP login, send/receive, spam filtering) — that's the paired verify **#108** (ready-for-human). - Logging / promtail / alloy (done — #124/#125). - OpenLDAP changes (no 26.05 breaking change at config level). - The lego/acme module (already on `environmentFile` via #128). - Other hosts' channels. --- **⚠ Note for the human landing this PR (not the agent):** `bento` auto-`switch`es and restarts dovecot/postfix on deploy. Unlike a build failure (which never deploys), a config-*syntax* break that slips validation deploys cleanly and then kills the service on restart — a live IMAP/SMTP outage *before* the #108 verify. Land with a console ready, and consider pausing mail's `bento-upgrade` until you deploy by hand.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/nixos#107
No description provided.