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;
73 global = lib.mkOption {
74 description = "global scripts.";
75 type = types.attrsOf types.str;
78 before = lib.mkOption {
79 description = "before scripts.";
80 type = types.attrsOf types.str;
83 after = lib.mkOption {
84 description = "after scripts.";
85 type = types.attrsOf types.str;
91 config = lib.mkIf dovecot2.enable {
92 systemd.services.dovecot2 = {
93 preStart = unlinesValues {
95 # SEE: http://wiki2.dovecot.org/SharedMailboxes/Permissions
96 install -D -d -m 0771 \
97 -o ${dovecot2.mailUser} \
98 -g ${dovecot2.mailGroup} \
104 install -D -d -m 0755 -o root -g root \
106 '' + unlinesAttrs (dir: sieves: ''
107 install -D -d -m 0755 -o root -g root \
108 ${sieveDir} ${sieveDir}/${dir}.d
109 '' + unlinesAttrs (name: text: ''
110 src=${pkgs.writeText "${name}.sieve" text}
111 dst="${sieveDir}/${dir}.d/${name}.sieve"
112 ln -fns "$src" "$dst"
113 ${pkgs.dovecot_pigeonhole}/bin/sievec "$dst"
118 lib.optionalString openldap.enable ''
119 # NOTE: make sure nslcd cache is in sync with the LDAP data
120 systemctl restart nslcd
122 install -D -d -m 1770 \
123 -o ${dovecot2.mailUser} \
125 ${mailDir}/${networking.domain} \
126 ${stateDir}/control/${networking.domain} \
127 ${stateDir}/index/${networking.domain}
129 # NOTE: do not set the sticky bit (+t)
130 # on acl/<domain>/, to let dovecot
131 # rename acl.db.lock (own by new user)
132 # to acl.db (own by old user)
133 install -D -d -m 0770 \
134 -o ${dovecot2.mailUser} \
136 ${stateDir}/acl/${networking.domain}
138 # NOTE: domainAliases point to the very same mailboxes as domain's.
139 for domainAlias in ${unwords networking.domainAliases}
141 ln -fns ${networking.domain} ${mailDir}/$domainAlias
142 ln -fns ${networking.domain} ${stateDir}/control/$domainAlias
143 ln -fns ${networking.domain} ${stateDir}/index/$domainAlias
144 ln -fns ${networking.domain} ${stateDir}/acl/$domainAlias