From 75742d265e66b37fa9b12b60900f53f0860c09d2 Mon Sep 17 00:00:00 2001 From: Axel Karjalainen Date: Sat, 4 Apr 2026 20:02:21 +0300 Subject: [PATCH 1/3] nixos/stalwart: add module-specific `stateVersion` and clean up --- .../manual/release-notes/rl-2605.section.md | 4 +++ nixos/modules/services/mail/stalwart.nix | 33 ++++++++++++------- nixos/tests/stalwart/stalwart-config.nix | 5 ++- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 9053a81954bce..a60462513ec79 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -162,6 +162,10 @@ of pulling the upstream container image from Docker Hub. If you want the old beh - `services.stalwart-mail` has been renamed to `services.stalwart` to align with upstream re-brand as an e-mail and collaboration server. Other notable breaking changes to module: + - Addition of module-specific `stateVersion` option, which on existing installations of Stalwart must be set to the same as `system.stateVersion`. + + This enables manually and carefully migrating Stalwart to a new `stateVersion` or newly enabling the Stalwart module with a newer `stateVersion` than `system.stateVersion`. + - `systemd.services.stalwart` owned by `stalwart:stalwart`. The `user` and `group` are configurable via `services.stalwart.user` and `services.stalwart.group`, respectively. By default, if `stateVersion` is older than `26.05`, will fallback to legacy value of `stalwart-mail` for both `user` and `group`. - Default value for `services.stalwart.dataDir` has changed to `/var/lib/stalwart`. If `stateVersion` is older than `26.05`, will fallback to legacy value of `/var/lib/stalwart-mail`. diff --git a/nixos/modules/services/mail/stalwart.nix b/nixos/modules/services/mail/stalwart.nix index 4719f9ccb7300..624f37e8892a4 100644 --- a/nixos/modules/services/mail/stalwart.nix +++ b/nixos/modules/services/mail/stalwart.nix @@ -8,9 +8,9 @@ let cfg = config.services.stalwart; configFormat = pkgs.formats.toml { }; configFile = configFormat.generate "stalwart.toml" cfg.settings; - useLegacyStorage = lib.versionOlder config.system.stateVersion "24.11"; - useLegacyDefault = lib.versionOlder config.system.stateVersion "26.05"; - default = if useLegacyDefault then "stalwart-mail" else "stalwart"; + useLegacyStorage = lib.versionOlder cfg.stateVersion "24.11"; + useLegacyIdentifier = lib.versionOlder cfg.stateVersion "26.05"; + stalwartIdentifier = if useLegacyIdentifier then "stalwart-mail" else "stalwart"; parsePorts = listeners: @@ -31,6 +31,15 @@ in options.services.stalwart = { enable = lib.mkEnableOption "the all-in-one collaboration and mail server, Stalwart"; + stateVersion = lib.mkOption { + type = lib.types.str; + description = '' + The version of this module (=version of NixOS) when this module was first enabled on this particular machine, used to maintain compatibility with application data created on older versions of this module. + + See {option}`system.stateVersion` for details on the NixOS-global equivalent to this option. + ''; + }; + package = lib.mkPackageOption pkgs "stalwart" { }; openFirewall = lib.mkOption { @@ -55,7 +64,7 @@ in dataDir = lib.mkOption { type = lib.types.path; - default = if useLegacyDefault then "/var/lib/stalwart-mail" else "/var/lib/stalwart"; + default = "/var/lib/${stalwartIdentifier}"; description = '' Data directory for stalwart ''; @@ -63,7 +72,7 @@ in user = lib.mkOption { type = lib.types.str; - inherit default; + default = stalwartIdentifier; description = '' User ownership of service ''; @@ -71,7 +80,7 @@ in group = lib.mkOption { type = lib.types.str; - inherit default; + default = stalwartIdentifier; description = '' Group ownership of service ''; @@ -155,7 +164,7 @@ in ); in { - path = "/var/cache/${default}"; + path = "/var/cache/${stalwartIdentifier}"; resource = lib.mkIf hasHttpListener (lib.mkDefault "file://${cfg.package.webadmin}/webadmin.zip"); }; }; @@ -165,10 +174,10 @@ in # service is restarted on a potentially large number of files. # That would cause unnecessary and unwanted delays. users = { - groups = lib.mkIf (cfg.group == default) { + groups = lib.mkIf (cfg.group == stalwartIdentifier) { ${cfg.group} = { }; }; - users = lib.mkIf (cfg.user == default) { + users = lib.mkIf (cfg.user == stalwartIdentifier) { ${cfg.user} = { isSystemUser = true; inherit (cfg) group; @@ -197,7 +206,7 @@ in KillSignal = "SIGINT"; Restart = "on-failure"; RestartSec = 5; - SyslogIdentifier = default; + SyslogIdentifier = stalwartIdentifier; ExecStartPre = if useLegacyStorage then @@ -217,8 +226,8 @@ in ReadWritePaths = [ cfg.dataDir ]; - CacheDirectory = default; - StateDirectory = default; + CacheDirectory = stalwartIdentifier; + StateDirectory = stalwartIdentifier; # Upstream uses "stalwart" as the username since 0.12.0 User = cfg.user; diff --git a/nixos/tests/stalwart/stalwart-config.nix b/nixos/tests/stalwart/stalwart-config.nix index 7fcbf8aae41c5..399b527ea1d4b 100644 --- a/nixos/tests/stalwart/stalwart-config.nix +++ b/nixos/tests/stalwart/stalwart-config.nix @@ -1,4 +1,4 @@ -{ pkgs, lib, ... }: +{ lib, ... }: let certs = import ../common/acme/server/snakeoil-certs.nix; @@ -9,6 +9,9 @@ in services.stalwart = { enable = true; + + stateVersion = lib.trivial.release; # Only for the test; please don't do in production + settings = { server.hostname = domain; From bfd7ffcf087fc3f24dec05e988550a33e7f09340 Mon Sep 17 00:00:00 2001 From: Axel Karjalainen Date: Sat, 4 Apr 2026 20:04:10 +0300 Subject: [PATCH 2/3] nixos/stalwart: switch logging from stdout to journal journald's centralized logging has many advantages: - External centralized logging can simply source from journald - journald's auto-vacuum options (e.g. SystemMaxUse) can make sure logs don't take up too much space - Logs are in a single timeline, which is useful for analysis --- .../manual/release-notes/rl-2605.section.md | 1 + nixos/modules/services/mail/stalwart.nix | 28 +++++++++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index a60462513ec79..7ce066cd0bf3b 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -168,6 +168,7 @@ of pulling the upstream container image from Docker Hub. If you want the old beh - `systemd.services.stalwart` owned by `stalwart:stalwart`. The `user` and `group` are configurable via `services.stalwart.user` and `services.stalwart.group`, respectively. By default, if `stateVersion` is older than `26.05`, will fallback to legacy value of `stalwart-mail` for both `user` and `group`. - Default value for `services.stalwart.dataDir` has changed to `/var/lib/stalwart`. If `stateVersion` is older than `26.05`, will fallback to legacy value of `/var/lib/stalwart-mail`. + - Default tracer name and type have changed to `journal`. If `stateVersion` is older than `26.05`, will fallback to legacy value of `stdout`. - `services.eintopf` has been renamed to `services.lauti` to align with upstream re-brand as a community online calendar. diff --git a/nixos/modules/services/mail/stalwart.nix b/nixos/modules/services/mail/stalwart.nix index 624f37e8892a4..b2c9cf141f8e1 100644 --- a/nixos/modules/services/mail/stalwart.nix +++ b/nixos/modules/services/mail/stalwart.nix @@ -9,8 +9,8 @@ let configFormat = pkgs.formats.toml { }; configFile = configFormat.generate "stalwart.toml" cfg.settings; useLegacyStorage = lib.versionOlder cfg.stateVersion "24.11"; - useLegacyIdentifier = lib.versionOlder cfg.stateVersion "26.05"; - stalwartIdentifier = if useLegacyIdentifier then "stalwart-mail" else "stalwart"; + pre2605 = lib.versionOlder cfg.stateVersion "26.05"; + stalwartIdentifier = if pre2605 then "stalwart-mail" else "stalwart"; parsePorts = listeners: @@ -123,12 +123,24 @@ in # Default config: all local services.stalwart.settings = { - tracer.stdout = { - type = lib.mkDefault "stdout"; - level = lib.mkDefault "info"; - ansi = lib.mkDefault false; # no colour markers to journald - enable = lib.mkDefault true; - }; + tracer = + if pre2605 then + { + stdout = { + type = lib.mkDefault "stdout"; + level = lib.mkDefault "info"; + ansi = lib.mkDefault false; # no colour markers to journald + enable = lib.mkDefault true; + }; + } + else + { + journal = { + type = lib.mkDefault "journal"; + level = lib.mkDefault "info"; + enable = lib.mkDefault true; + }; + }; store = if useLegacyStorage then { From 28a9274f560b33bd05365c4c47c6c0248b68a4bc Mon Sep 17 00:00:00 2001 From: Axel Karjalainen Date: Sat, 4 Apr 2026 20:04:50 +0300 Subject: [PATCH 3/3] nixos/stalwart: document changing defaults to fix doc build --- nixos/modules/services/mail/stalwart.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nixos/modules/services/mail/stalwart.nix b/nixos/modules/services/mail/stalwart.nix index b2c9cf141f8e1..9dfd5f2e480f5 100644 --- a/nixos/modules/services/mail/stalwart.nix +++ b/nixos/modules/services/mail/stalwart.nix @@ -11,6 +11,7 @@ let useLegacyStorage = lib.versionOlder cfg.stateVersion "24.11"; pre2605 = lib.versionOlder cfg.stateVersion "26.05"; stalwartIdentifier = if pre2605 then "stalwart-mail" else "stalwart"; + stalwartIdentifierText = ''if lib.versionOlder config.services.stalwart.stateVersion "26.05" then "stalwart-mail" else "stalwart"''; parsePorts = listeners: @@ -65,6 +66,7 @@ in dataDir = lib.mkOption { type = lib.types.path; default = "/var/lib/${stalwartIdentifier}"; + defaultText = lib.literalExpression "/var/lib/\${${stalwartIdentifierText}}"; description = '' Data directory for stalwart ''; @@ -73,6 +75,7 @@ in user = lib.mkOption { type = lib.types.str; default = stalwartIdentifier; + defaultText = lib.literalExpression stalwartIdentifierText; description = '' User ownership of service ''; @@ -81,6 +84,7 @@ in group = lib.mkOption { type = lib.types.str; default = stalwartIdentifier; + defaultText = lib.literalExpression stalwartIdentifierText; description = '' Group ownership of service '';