nixos/dovecot: add settings option (RFC42)
Co-authored-by: Katalin Rebhan <me@dblsaiko.net>
This commit is contained in:
co-authored by
Katalin Rebhan
parent
b5aeef3116
commit
ad56a1d988
@@ -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 <https://doc.dovecot.org/latest/core/summaries/settings.html#all-dovecot-settings>
|
||||
for all available options.
|
||||
|
||||
For information on the configuration structure, see <https://doc.dovecot.org/latest/core/settings/syntax.html>.
|
||||
|
||||
::: {.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
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user