From ad56a1d988b448518aaad2bc5577b3059c8dfd12 Mon Sep 17 00:00:00 2001 From: Jappie3 Date: Sat, 20 Dec 2025 16:16:52 +0100 Subject: [PATCH] nixos/dovecot: add settings option (RFC42) Co-authored-by: Katalin Rebhan --- nixos/modules/services/mail/dovecot.nix | 695 +++++++++++------------- nixos/tests/dovecot.nix | 14 +- 2 files changed, 336 insertions(+), 373 deletions(-) diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix index af91440c6a3f..c4e71daff438 100644 --- a/nixos/modules/services/mail/dovecot.nix +++ b/nixos/modules/services/mail/dovecot.nix @@ -7,33 +7,45 @@ let inherit (lib) - attrValues + all + attrsToList concatMapStringsSep - concatStrings concatStringsSep + elemAt + filter flatten imap1 + isAttrs + isBool + isDerivation + isInt + isList + isPath + isString + listToAttrs literalExpression + mapAttrs' mapAttrsToList + mkDefault mkEnableOption mkIf + mkMerge mkOption + mkPackageOption mkRemovedOptionModule + mkRenamedOptionModule + nameValuePair optional optionalAttrs optionalString + optionals singleton + splitString + traceSeq types - mkRenamedOptionModule - nameValuePair - mapAttrs' - listToAttrs - filter ; - inherit (lib.strings) match; cfg = config.services.dovecot2; - dovecotPkg = pkgs.dovecot; baseDir = "/run/dovecot2"; stateDir = "/var/lib/dovecot"; @@ -69,35 +81,6 @@ let ) ); - mkExtraConfigCollisionWarning = term: '' - You referred to ${term} in `services.dovecot2.extraConfig`. - - Due to gradual transition to structured configuration for plugin configuration, it is possible - this will cause your plugin configuration to be ignored. - - Consider setting `services.dovecot2.pluginSettings.${term}` instead. - ''; - - # Those settings are automatically set based on other parts - # of this module. - automaticallySetPluginSettings = [ - "sieve_plugins" - "sieve_extensions" - "sieve_global_extensions" - "sieve_pipe_bin_dir" - ] - ++ (builtins.attrNames sieveScriptSettings) - ++ (builtins.attrNames imapSieveMailboxSettings); - - # The idea is to match everything that looks like `$term =` - # but not `# $term something something` - # or `# $term = some value` because those are comments. - configContainsSetting = lines: term: (match "[[:blank:]]*${term}[[:blank:]]*=.*" lines) != null; - - warnAboutExtraConfigCollisions = map mkExtraConfigCollisionWarning ( - filter (configContainsSetting cfg.extraConfig) automaticallySetPluginSettings - ); - sievePipeBinScriptDirectory = pkgs.linkFarm "sieve-pipe-bins" ( map (el: { name = builtins.unsafeDiscardStringContext (baseNameOf el); @@ -105,200 +88,263 @@ let }) cfg.sieve.pipeBins ); - dovecotConf = concatStrings [ - '' - base_dir = ${baseDir} - protocols = ${concatStringsSep " " cfg.protocols} - sendmail_path = /run/wrappers/bin/sendmail - mail_plugin_dir = /run/current-system/sw/lib/dovecot/modules - # defining mail_plugins must be done before the first protocol {} filter because of https://doc.dovecot.org/configuration_manual/config_file/config_file_syntax/#variable-expansion - mail_plugins = $mail_plugins ${concatStringsSep " " cfg.mailPlugins.globally.enable} - '' - - (concatStringsSep "\n" ( - mapAttrsToList (protocol: plugins: '' - protocol ${protocol} { - mail_plugins = $mail_plugins ${concatStringsSep " " plugins.enable} - } - '') cfg.mailPlugins.perProtocol - )) - - ( - if cfg.sslServerCert == null then - '' - ssl = no - disable_plaintext_auth = no - '' - else - '' - ssl_cert = <${cfg.sslServerCert} - ssl_key = <${cfg.sslServerKey} - ${optionalString (cfg.sslCACert != null) ("ssl_ca = <" + cfg.sslCACert)} - ${optionalString cfg.enableDHE "ssl_dh = <${config.security.dhparams.params.dovecot2.path}"} - disable_plaintext_auth = yes - '' - ) - - '' - default_internal_user = ${cfg.user} - default_internal_group = ${cfg.group} - ${optionalString (cfg.mailUser != null) "mail_uid = ${cfg.mailUser}"} - ${optionalString (cfg.mailGroup != null) "mail_gid = ${cfg.mailGroup}"} - - mail_location = ${cfg.mailLocation} - - maildir_copy_with_hardlinks = yes - pop3_uidl_format = %08Xv%08Xu - - auth_mechanisms = plain login - - service auth { - user = root - } - '' - - (optionalString cfg.enablePAM '' - userdb { - driver = passwd - } - - passdb { - driver = pam - args = ${optionalString cfg.showPAMFailure "failure_show_msg=yes"} dovecot2 - } - '') - - (optionalString (cfg.mailboxes != { }) '' - namespace inbox { - inbox=yes - ${concatStringsSep "\n" (map mailboxConfig (attrValues cfg.mailboxes))} - } - '') - - (optionalString cfg.enableQuota '' - service quota-status { - executable = ${dovecotPkg}/libexec/dovecot/quota-status -p postfix - inet_listener { - port = ${cfg.quotaPort} - } - client_limit = 1 - } - - plugin { - quota_rule = *:storage=${cfg.quotaGlobalPerUser} - quota = count:User quota # per virtual mail user quota - quota_status_success = DUNNO - quota_status_nouser = DUNNO - quota_status_overquota = "552 5.2.2 Mailbox is full" - quota_grace = 10%% - quota_vsizes = yes - } - '') - - # General plugin settings: - # - sieve is mostly generated here, refer to `pluginSettings` to follow - # the control flow. - '' - plugin { - ${concatStringsSep "\n" (mapAttrsToList (key: value: " ${key} = ${value}") cfg.pluginSettings)} - } - '' - - cfg.extraConfig + allSieveSettings = mkMerge [ + imapSieveMailboxSettings + sieveScriptSettings + { + sieve_plugins = + mkIf (cfg.sieve.plugins != [ ]) # . + (concatStringsSep " " cfg.sieve.plugins); + sieve_extensions = + mkIf (cfg.sieve.extensions != [ ]) # . + (concatMapStringsSep " " (el: "+${el}") cfg.sieve.extensions); + sieve_global_extensions = + mkIf (cfg.sieve.globalExtensions != [ ]) # . + (concatMapStringsSep " " (el: "+${el}") cfg.sieve.globalExtensions); + sieve_pipe_bin_dir = + mkIf (cfg.sieve.pipeBins != [ ]) # . + sievePipeBinScriptDirectory; + } ]; - mailboxConfig = - mailbox: - '' - mailbox "${mailbox.name}" { - auto = ${toString mailbox.auto} - '' - + optionalString (mailbox.autoexpunge != null) '' - autoexpunge = ${mailbox.autoexpunge} - '' - + optionalString (mailbox.specialUse != null) '' - special_use = \${toString mailbox.specialUse} - '' - + "}"; + yesOrNo = v: if v then "yes" else "no"; - mailboxes = - { name, ... }: - { - options = { - name = mkOption { - type = types.strMatching ''[^"]+''; - example = "Spam"; - default = name; - readOnly = true; - description = "The name of the mailbox."; - }; - auto = mkOption { - type = types.enum [ - "no" - "create" - "subscribe" - ]; - default = "no"; - example = "subscribe"; - description = "Whether to automatically create or create and subscribe to the mailbox or not."; - }; - specialUse = mkOption { - type = types.nullOr ( - types.enum [ - "All" - "Archive" - "Drafts" - "Flagged" - "Junk" - "Sent" - "Trash" - ] - ); - default = null; - example = "Junk"; - description = "Null if no special use flag is set. Other than that every use flag mentioned in the RFC is valid."; - }; - autoexpunge = mkOption { - type = types.nullOr types.str; - default = null; - example = "60d"; - description = '' - To automatically remove all email from the mailbox which is older than the - specified time. - ''; - }; - }; - }; + toOption = + i: n: v: + "${i}${toString n} = ${v}"; + + formatKeyValue = + indent: n: v: + if (v == null) then + "" + else if isInt v then + toOption indent n (toString v) + else if isBool v then + toOption indent n (yesOrNo v) + else if isString v then + toOption indent n v + else if isList v then + if all isString v then + toOption indent n (concatStringsSep " " v) + else + map (formatKeyValue indent n) v + else if isPath v || isDerivation v then + # paths -> copy to store + # derivations -> just use output path instead of looping over the attrs + toOption indent n "${v}" + else if isAttrs v then + let + sectionType = v._section.type; + sectionName = v._section.name; + sectionTitle = concatStringsSep " " ( + filter (s: s != null) [ + sectionType + sectionName + ] + ); + in + concatStringsSep "\n" ( + [ + "${indent}${sectionTitle} {" + ] + ++ (mapAttrsToList (formatKeyValue "${indent} ") (removeAttrs v [ "_section" ])) + ++ [ "${indent}}" ] + ) + else + throw (traceSeq v "services.dovecot2.settings: unexpected type"); + + doveConf = concatStringsSep "\n" ( + optionals (cfg.includeFiles != [ ]) (map (f: "!include ${f}") cfg.includeFiles) + ++ flatten (mapAttrsToList (formatKeyValue "") cfg.settings) + ); in { imports = [ - (mkRemovedOptionModule [ "services" "dovecot2" "package" ] "") (mkRemovedOptionModule [ "services" "dovecot2" "modules" ] "Now need to use `environment.systemPackages` to load additional Dovecot modules") + (mkRemovedOptionModule [ + "services" + "dovecot2" + "enablePop3" + ] "Set 'services.dovecot2.settings.protocols.pop3 = true/false;' instead.") + (mkRemovedOptionModule [ + "services" + "dovecot2" + "enableImap" + ] "Set 'services.dovecot2.settings.protocols.imap = true/false;' instead.") + (mkRemovedOptionModule [ + "services" + "dovecot2" + "enableLmtp" + ] "Set 'services.dovecot2.settings.protocols.lmtp = true/false;' instead.") (mkRenamedOptionModule [ "services" "dovecot2" "sieveScripts" ] [ "services" "dovecot2" "sieve" "scripts" ] ) - ]; + (mkRenamedOptionModule + [ "services" "dovecot2" "mailUser" ] + [ "services" "dovecot2" "settings" "mail_uid" ] + ) + (mkRenamedOptionModule + [ "services" "dovecot2" "mailGroup" ] + [ "services" "dovecot2" "settings" "mail_gid" ] + ) + (mkRenamedOptionModule + [ "services" "dovecot2" "protocols" ] + [ "services" "dovecot2" "settings" "protocols" ] + ) + ] + ++ ( + let + basePath = [ + "services" + "dovecot2" + ]; + mkRemovedOptions = + list: + map ( + name: mkRemovedOptionModule (basePath ++ name) "Please use services.dovecot2.settings instead." + ) list; + in + mkRemovedOptions [ + [ "extraConfig" ] + [ "mailboxes" ] + [ "pluginSettings" ] + [ "enableQuota" ] + [ "quotaPort" ] + [ "quotaGlobalPerUser" ] + ] + ); options.services.dovecot2 = { enable = mkEnableOption "the dovecot 2.x POP3/IMAP server"; - enablePop3 = mkEnableOption "starting the POP3 listener (when Dovecot is enabled)"; - - enableImap = mkEnableOption "starting the IMAP listener (when Dovecot is enabled)" // { - default = true; + package = mkPackageOption pkgs "dovecot" { } // { }; - enableLmtp = mkEnableOption "starting the LMTP listener (when Dovecot is enabled)"; + settings = mkOption { + default = { }; + type = + let + inherit (lib.types) + attrsOf + path + bool + int + nonEmptyListOf + nonEmptyStr + nullOr + oneOf + str + submodule + ; + inherit (lib.lists) last dropEnd; - protocols = mkOption { - type = types.listOf types.str; - default = [ ]; - description = "Additional listeners to start when Dovecot is enabled."; + section = submodule ( + { name, options, ... }: + let + # if the current name is a list (matches '[definition .*]') -> get + # name' from _module.args.loc & use it for {type,name}Default + name' = + if (builtins.match "[[]definition .*].*" name) == null then + name + else + last (dropEnd 3 options._module.args.loc); + + # split name' on the first space + splits = builtins.match "([^ ]+) (.+)" name'; + typeDefault = if splits == null then name' else builtins.elemAt splits 0; + nameDefault = if splits == null then null else builtins.elemAt splits 1; + in + { + options = { + _section = { + type = mkOption { + description = "Section type, mandatory for every section."; + type = nonEmptyStr; + default = typeDefault; + }; + name = mkOption { + description = "Section name, comes after section type & is optional in some cases."; + type = nullOr nonEmptyStr; + default = nameDefault; + }; + }; + }; + + freeformType = attrsOf valueType; + } + ); + + primitiveType = oneOf [ + int + str + bool + # path must order before section, otherwise the latter will + # interpret path literals as a module path to load + path + section + ]; + + valueType = + nullOr (oneOf [ + primitiveType + (nonEmptyListOf primitiveType) + ]) + // { + description = "Dovecot config value"; + }; + in + attrsOf valueType; + description = '' + Dovecot configuration, see + for all available options. + + For information on the configuration structure, see . + + ::: {.warning} + Explicit settings in [{option}`services.dovecot2.settings`](#opt-services.dovecot2.settings) can silently override values set by other `services.dovecot2.*` options. + ::: + ''; + example = { + protocols = { + imap = true; + submission = true; + lmtp = true; + }; + mail_driver = "maildir"; + "namespace inbox" = { + inbox = true; + separator = "/"; + }; + service = [ + { + _section.name = "imap"; + process_min_avail = 1; + client_limit = 100; + "inet_listener imap".port = 31143; + "inet_listener imaps".port = 31993; + } + { + _section.name = "lmtp"; + user = "dovemail"; + "unix_listener lmtp" = { + mode = "0660"; + user = "postfix"; + }; + } + ]; + "protocol imap".mail_plugins = { + imap_sieve = true; + imap_filter_sieve = true; + }; + mail_attribute."dict file" = { + path = "%{home}/dovecot-attributes"; + }; + }; }; user = mkOption { @@ -313,13 +359,6 @@ in description = "Dovecot group name."; }; - extraConfig = mkOption { - type = types.lines; - default = ""; - example = "mail_debug = yes"; - description = "Additional entries to put verbatim into Dovecot's config file."; - }; - mailPlugins = let plugins = @@ -374,129 +413,31 @@ in type = types.nullOr types.path; default = null; description = "Config file used for the whole dovecot configuration."; - apply = v: if v != null then v else pkgs.writeText "dovecot.conf" dovecotConf; + apply = v: if v != null then v else pkgs.writeText "dovecot.conf" doveConf; }; - mailLocation = mkOption { - type = types.str; - default = "maildir:/var/spool/mail/%u"; # Same as inbox, as postfix - example = "maildir:~/mail:INBOX=/var/spool/mail/%u"; - description = '' - Location that dovecot will use for mail folders. Dovecot mail_location option. - ''; - }; - - mailUser = mkOption { - type = types.nullOr types.str; - default = null; - description = "Default user to store mail for virtual users."; - }; - - mailGroup = mkOption { - type = types.nullOr types.str; - default = null; - description = "Default group to store mail for virtual users."; + includeFiles = mkOption { + type = types.listOf types.path; + default = [ ]; + description = "Files to include in the Dovecot config file using !include directives."; + example = [ "/foo/bar/extraDovecotConfig.conf" ]; }; createMailUser = mkEnableOption '' automatically creating the user - given in {option}`services.dovecot.user` and the group - given in {option}`services.dovecot.group`'' + given in {option}`services.dovecot2.settings.mail_uid` and the group + given in {option}`services.dovecot2.settings.mail_gid`'' // { default = true; }; - sslCACert = mkOption { - type = types.nullOr types.str; - default = null; - description = "Path to the server's CA certificate key."; - }; - - sslServerCert = mkOption { - type = types.nullOr types.str; - default = null; - description = "Path to the server's public key."; - }; - - sslServerKey = mkOption { - type = types.nullOr types.str; - default = null; - description = "Path to the server's private key."; - }; - enablePAM = mkEnableOption "creating a own Dovecot PAM service and configure PAM user logins" // { default = true; }; - enableDHE = mkEnableOption "ssl_dh and generation of primes for the key exchange" // { - default = true; - }; - showPAMFailure = mkEnableOption "showing the PAM failure message on authentication error (useful for OTPW)"; - mailboxes = mkOption { - type = - with types; - coercedTo (listOf unspecified) ( - list: - listToAttrs ( - map (entry: { - name = entry.name; - value = removeAttrs entry [ "name" ]; - }) list - ) - ) (attrsOf (submodule mailboxes)); - default = { }; - example = literalExpression '' - { - Spam = { specialUse = "Junk"; auto = "create"; }; - } - ''; - description = "Configure mailboxes and auto create or subscribe them."; - }; - - enableQuota = mkEnableOption "the dovecot quota service"; - - quotaPort = mkOption { - type = types.str; - default = "12340"; - description = '' - The Port the dovecot quota service binds to. - If using postfix, add check_policy_service inet:localhost:12340 to your smtpd_recipient_restrictions in your postfix config. - ''; - }; - quotaGlobalPerUser = mkOption { - type = types.str; - default = "100G"; - example = "10G"; - description = "Quota limit for the user in bytes. Supports suffixes b, k, M, G, T and %."; - }; - - pluginSettings = mkOption { - # types.str does not coerce from packages, like `sievePipeBinScriptDirectory`. - type = types.attrsOf ( - types.oneOf [ - types.str - types.package - ] - ); - default = { }; - example = literalExpression '' - { - sieve = "file:~/sieve;active=~/.dovecot.sieve"; - } - ''; - description = '' - Plugin settings for dovecot in general, e.g. `sieve`, `sieve_default`, etc. - - Some of the other knobs of this module will influence by default the plugin settings, but you - can still override any plugin settings. - - If you override a plugin setting, its value is cleared and you have to copy over the defaults. - ''; - }; - imapsieve.mailbox = mkOption { default = [ ]; description = "Configure Sieve filtering rules on IMAP actions"; @@ -624,36 +565,56 @@ in config = mkIf cfg.enable { security.pam.services.dovecot2 = mkIf cfg.enablePAM { }; - security.dhparams = mkIf (cfg.sslServerCert != null && cfg.enableDHE) { - enable = true; - params.dovecot2 = { }; - }; - services.dovecot2 = { - protocols = - optional cfg.enableImap "imap" ++ optional cfg.enablePop3 "pop3" ++ optional cfg.enableLmtp "lmtp"; - - mailPlugins = mkIf cfg.enableQuota { - globally.enable = [ "quota" ]; - perProtocol.imap.enable = [ "imap_quota" ]; - }; - sieve.plugins = optional (cfg.imapsieve.mailbox != [ ]) "sieve_imapsieve" ++ optional (cfg.sieve.pipeBins != [ ]) "sieve_extprograms"; sieve.globalExtensions = optional (cfg.sieve.pipeBins != [ ]) "vnd.dovecot.pipe"; - pluginSettings = lib.mapAttrs (n: lib.mkDefault) ( + settings = mkMerge [ { - sieve_plugins = concatStringsSep " " cfg.sieve.plugins; - sieve_extensions = concatStringsSep " " (map (el: "+${el}") cfg.sieve.extensions); - sieve_global_extensions = concatStringsSep " " (map (el: "+${el}") cfg.sieve.globalExtensions); - sieve_pipe_bin_dir = sievePipeBinScriptDirectory; + base_dir = mkDefault baseDir; + sendmail_path = mkDefault "/run/wrappers/bin/sendmail"; + mail_plugin_dir = mkDefault "/run/current-system/sw/lib/dovecot/modules"; + + default_internal_user = mkDefault cfg.user; + default_internal_group = mkDefault cfg.group; + + maildir_copy_with_hardlinks = mkDefault true; + pop3_uidl_format = mkDefault "%08Xv%08Xu"; + + auth_mechanisms = mkDefault "plain login"; + "service auth" = { + user = "root"; + }; + + mail_location = mkDefault "maildir:/var/spool/mail/%u"; + + passdb = mkIf cfg.enablePAM (mkDefault { + driver = "pam"; + args = "${optionalString cfg.showPAMFailure "failure_show_msg=yes"} dovecot2"; + }); + + userdb = mkIf cfg.enablePAM (mkDefault { + driver = "passwd"; + }); + + mail_plugins = mkDefault "$mail_plugins ${concatStringsSep " " cfg.mailPlugins.globally.enable}"; + + plugin = allSieveSettings; } - // sieveScriptSettings - // imapSieveMailboxSettings - ); + (mkIf (cfg.mailPlugins.perProtocol != { } || cfg.mailPlugins.globally.enable != [ ]) ( + listToAttrs ( + map (m: { + name = "protocol ${elemAt (splitString "." m.name) 0}"; + value.mail_plugins = "$mail_plugins ${ + concatStringsSep " " (m.value.enable ++ cfg.mailPlugins.globally.enable) + }"; + }) (attrsToList cfg.mailPlugins.perProtocol) + ) + )) + ]; }; users.users = { @@ -670,12 +631,14 @@ in group = cfg.group; }; } - // optionalAttrs (cfg.createMailUser && cfg.mailUser != null) { - ${cfg.mailUser} = { + // optionalAttrs (cfg.settings.mail_uid or null != null && cfg.createMailUser) { + ${cfg.settings.mail_uid} = { description = "Virtual Mail User"; isSystemUser = true; } - // optionalAttrs (cfg.mailGroup != null) { group = cfg.mailGroup; }; + // optionalAttrs (cfg.settings.mail_gid or null != null) { + group = cfg.settings.mail_gid; + }; }; users.groups = { @@ -684,8 +647,8 @@ in // optionalAttrs (cfg.group == "dovecot2") { dovecot2.gid = config.ids.gids.dovecot2; } - // optionalAttrs (cfg.createMailUser && cfg.mailGroup != null) { - ${cfg.mailGroup} = { }; + // optionalAttrs (cfg.settings.mail_gid or null != null && cfg.createMailUser) { + ${cfg.settings.mail_gid} = { }; }; environment.etc."dovecot/dovecot.conf".source = cfg.configFile; @@ -704,8 +667,8 @@ in startLimitIntervalSec = 60; # 1 min serviceConfig = { Type = "notify"; - ExecStart = "${dovecotPkg}/sbin/dovecot -F"; - ExecReload = "${dovecotPkg}/sbin/doveadm reload"; + ExecStart = "${lib.getExe cfg.package} -F"; + ExecReload = "${lib.getExe' cfg.package "doveadm"} reload"; CapabilityBoundingSet = [ "CAP_CHOWN" @@ -773,7 +736,9 @@ in ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/sieve/${to}' '') cfg.sieve.scripts )} - chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/sieve' + ${optionalString ( + cfg.settings ? mail_uid && cfg.settings.mail_uid != null && cfg.settings.mail_gid != null + ) "chown -R '${cfg.settings.mail_uid}:${cfg.settings.mail_gid}' '${stateDir}/sieve'"} '' + optionalString (cfg.imapsieve.mailbox != [ ]) '' mkdir -p ${stateDir}/imapsieve/{before,after} @@ -791,29 +756,22 @@ in ) cfg.imapsieve.mailbox} ${optionalString ( - cfg.mailUser != null && cfg.mailGroup != null - ) "chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/imapsieve'"} + cfg.settings ? mail_uid && cfg.settings.mail_uid != null && cfg.settings.mail_gid != null + ) "chown -R '${cfg.settings.mail_uid}:${cfg.settings.mail_gid}' '${stateDir}/imapsieve'"} ''; }; - environment.systemPackages = [ dovecotPkg ]; - - warnings = warnAboutExtraConfigCollisions; + environment.systemPackages = [ cfg.package ]; assertions = [ - { - assertion = - (cfg.sslServerCert == null) == (cfg.sslServerKey == null) - && (cfg.sslCACert != null -> !(cfg.sslServerCert == null || cfg.sslServerKey == null)); - message = "dovecot needs both sslServerCert and sslServerKey defined for working crypto"; - } { assertion = cfg.showPAMFailure -> cfg.enablePAM; message = "dovecot is configured with showPAMFailure while enablePAM is disabled"; } { - assertion = cfg.sieve.scripts != { } -> (cfg.mailUser != null && cfg.mailGroup != null); - message = "dovecot requires mailUser and mailGroup to be set when `sieve.scripts` is set"; + assertion = + cfg.sieve.scripts != { } -> (cfg.settings.mail_uid != null && cfg.settings.mail_gid != null); + message = "dovecot requires settings.mail_uid and settings.mail_gid to be set when `sieve.scripts` is set"; } { assertion = config.systemd.services ? dovecot2 == false; @@ -822,8 +780,11 @@ in ''; } ]; - }; - meta.maintainers = [ lib.maintainers.dblsaiko ]; + meta.maintainers = with lib.maintainers; [ + dblsaiko + jappie3 + prince213 + ]; } diff --git a/nixos/tests/dovecot.nix b/nixos/tests/dovecot.nix index 83b3781c773d..b7291114ccee 100644 --- a/nixos/tests/dovecot.nix +++ b/nixos/tests/dovecot.nix @@ -8,12 +8,14 @@ services.postfix.enable = true; services.dovecot2 = { enable = true; - protocols = [ - "imap" - "pop3" - ]; - mailUser = "vmail"; - mailGroup = "vmail"; + settings = { + protocols = [ + "imap" + "pop3" + ]; + mail_uid = "vmail"; + mail_gid = "vmail"; + }; }; environment.systemPackages = let