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