1 { pkgs, lib, config, ... }:
3 inherit (builtins) toString toFile attrNames;
5 inherit (pkgs.lib) unlinesAttrs unlinesValues unwords;
6 inherit (config.services) dovecot2 openldap;
7 inherit (config) networking;
8 stateDir = "/var/lib/dovecot";
9 mailDir = "${stateDir}/mail";
10 sieveDir = "${stateDir}/sieve";
11 authDir = "${stateDir}/auth";
12 escapeGroup = lib.stringAsChars (c: if "a"<=c && c<="z"
16 domainGroup = escapeGroup "${networking.domainBase}";
19 options.services.dovecot2 = {
20 domains = lib.mkOption {
22 type = types.attrsOf (types.submodule ({domain, ...}: {
23 #config.domain = lib.mkDefault domain;
25 accounts = lib.mkOption {
26 type = types.attrsOf (types.submodule ({account, ...}: {
28 password = lib.mkOption {
30 example = "{SSHA512}uyjL1KYx4z7HpfNvnKzuVxpMLD2KVueGGBvOcj7AF1EZCTVhT++IIKUVOC4xpZtWdqVD0OVmZqgYr2qpn/3t3Aj4oU0=";
31 description = ''Password.
32 Use: `doveadm pw -s SSHA512 -p "$password"`
35 aliases = lib.mkOption {
36 type = with types; listOf types.str;
37 example = [ "abuse@${config.networking.domain}" ];
39 description = ''Aliases of this account.'';
41 quota = lib.mkOption {
42 type = with types; nullOr types.str;
46 Per user quota rules. Accepted sizes are `xx k/M/G/T` with the
47 obvious meaning. Leave blank for the standard quota `100G`.
50 sieves = lib.mkOption {
51 type = with types; attrsOf str;
55 #include :personal "roundcube";
56 include :global "spam";
57 include :global "list";
58 include :global "extension";
62 groups = lib.mkOption {
63 type = with types; listOf str;
72 debug = lib.mkOption {
76 Whether to enable verbose logging or not in mail related services.
80 global = lib.mkOption {
81 description = "global scripts.";
82 type = types.attrsOf types.str;
85 before = lib.mkOption {
86 description = "before scripts.";
87 type = types.attrsOf types.str;
90 after = lib.mkOption {
91 description = "after scripts.";
92 type = types.attrsOf types.str;
98 config = lib.mkIf dovecot2.enable {
99 systemd.services.dovecot2 = {
100 preStart = unlinesValues {
102 # SEE: http://wiki2.dovecot.org/SharedMailboxes/Permissions
103 install -D -d -m 0771 \
104 -o ${dovecot2.mailUser} \
105 -g ${dovecot2.mailGroup} \
111 install -D -d -m 0755 -o root -g root \
113 '' + unlinesAttrs (dir: sieves: ''
114 install -D -d -m 0755 -o root -g root \
115 ${sieveDir} ${sieveDir}/${dir}.d
116 '' + unlinesAttrs (name: text: ''
117 src=${pkgs.writeText "${name}.sieve" text}
118 dst="${sieveDir}/${dir}.d/${name}.sieve"
119 ln -fns "$src" "$dst"
120 ${pkgs.dovecot_pigeonhole}/bin/sievec "$dst"
125 lib.optionalString openldap.enable ''
126 # NOTE: make sure nslcd cache is in sync with the LDAP data
127 systemctl restart nslcd
129 install -D -d -m 1770 \
130 -o ${dovecot2.mailUser} \
132 ${mailDir}/${networking.domain} \
133 ${stateDir}/control/${networking.domain} \
134 ${stateDir}/index/${networking.domain}
136 # NOTE: do not set the sticky bit (+t)
137 # on acl/<domain>/, to let dovecot
138 # rename acl.db.lock (own by new user)
139 # to acl.db (own by old user)
140 install -D -d -m 0770 \
141 -o ${dovecot2.mailUser} \
143 ${stateDir}/acl/${networking.domain}
145 # NOTE: domainAliases point to the very same mailboxes as domain's.
146 for domainAlias in ${unwords networking.domainAliases}
148 ln -fns ${networking.domain} ${mailDir}/$domainAlias
149 ln -fns ${networking.domain} ${stateDir}/control/$domainAlias
150 ln -fns ${networking.domain} ${stateDir}/index/$domainAlias
151 ln -fns ${networking.domain} ${stateDir}/acl/$domainAlias