default.nix (8664B)
1 2 { config, lib, pkgs, ... }: 3 4 with lib; 5 6 let 7 cfg = config.services.mailz; 8 9 mailbox = name: '' 10 mailbox ${name} { 11 auto = subscribe 12 } 13 ''; 14 15 # Convert: 16 # 17 # { 18 # a = { aliases = [ "x", "y" ]; }; 19 # b = { aliases = [ "x" ]; }; 20 # } 21 # 22 # To: 23 # 24 # { 25 # x = [ "a" "b" ]; 26 # y = [ "a" ]; 27 # } 28 aliases = foldAttrs (user: users: [user] ++ users) [ ] 29 (flatten (flip mapAttrsToList cfg.users 30 (user: options: flip map options.aliases 31 (alias: { ${alias} = user; })))); 32 33 files = { 34 credentials = pkgs.writeText "credentials" 35 (concatStringsSep "\n" 36 (flip mapAttrsToList cfg.users 37 (user: options: "${user}@${cfg.domain} ${options.password}"))); 38 39 users = pkgs.writeText "users" 40 (concatStringsSep "\n" 41 (flip mapAttrsToList cfg.users 42 (user: options: "${user}:${options.password}:::::"))); 43 44 recipients = pkgs.writeText "recipients" 45 (concatStringsSep "\n" 46 (map (user: "${user}@${cfg.domain}") 47 (attrNames cfg.users ++ flatten ((flip mapAttrsToList) cfg.users 48 (user: options: options.aliases))))); 49 50 aliases = pkgs.writeText "aliases" 51 (concatStringsSep "\n" 52 (flip mapAttrsToList aliases 53 (alias: users: "${alias} ${concatStringsSep "," users}"))); 54 55 spamassassinSieve = pkgs.writeText "spamassassin.sieve" '' 56 require "fileinto"; 57 if header :contains "X-Spam-Flag" "YES" { 58 fileinto "Spam"; 59 } 60 ''; 61 62 # From <https://github.com/OpenSMTPD/OpenSMTPD-extras/blob/master/extras/wip/filters/filter-regex/filter-regex.conf> 63 regex = pkgs.writeText "filter-regex.conf" '' 64 helo ! ^\[ 65 helo ^\. 66 helo \.$ 67 helo ^[^\.]*$ 68 ''; 69 }; 70 71 in 72 73 { 74 options = { 75 services.mailz = { 76 domain = mkOption { 77 default = cfg.networking.hostName; 78 type = types.str; 79 description = "Domain for this mail server."; 80 }; 81 82 enable = mkEnableOption "enable mailz: self-hosted email"; 83 84 user = mkOption { 85 default = "vmail"; 86 type = types.str; 87 }; 88 89 sieves = mkOption { 90 default = ""; 91 type = types.str; 92 }; 93 94 group = mkOption { 95 default = "vmail"; 96 type = types.str; 97 }; 98 99 uid = mkOption { 100 default = 2000; 101 type = types.int; 102 }; 103 104 gid = mkOption { 105 default = 2000; 106 type = types.int; 107 }; 108 109 dkimDirectory = mkOption { 110 default = "/var/lib/dkim"; 111 type = types.str; 112 description = "Where to store DKIM keys."; 113 }; 114 115 dkimBits = mkOption { 116 type = types.int; 117 default = 2048; 118 description = "Size of the generated DKIM key."; 119 }; 120 121 users = mkOption { 122 default = { }; 123 type = types.loaOf types.optionSet; 124 description = '' 125 Attribute set of users. 126 ''; 127 128 options = { 129 password = mkOption { 130 type = types.str; 131 description = '' 132 The user password, generated with 133 <literal>smtpctl encrypt</literal>. 134 ''; 135 }; 136 137 aliases = mkOption { 138 type = types.listOf types.str; 139 default = [ ]; 140 example = [ "postmaster" ]; 141 description = "A list of aliases for this user."; 142 }; 143 }; 144 145 example = { 146 "foo" = { 147 password = "encrypted"; 148 aliases = [ "postmaster" ]; 149 }; 150 "bar" = { 151 password = "encrypted"; 152 }; 153 }; 154 }; 155 }; 156 }; 157 158 config = mkIf (cfg.enable && cfg.users != { }) 159 { 160 system.activationScripts.mailz = '' 161 # Make sure SpamAssassin database is present 162 #if ! [ -d /etc/spamassassin ]; then 163 #cp -r ${pkgs.spamassassin}/share/spamassassin /etc 164 #fi 165 166 # Make sure a DKIM private key exist 167 if ! [ -d ${cfg.dkimDirectory}/${cfg.domain} ]; then 168 mkdir -p ${cfg.dkimDirectory}/${cfg.domain} 169 chmod 700 ${cfg.dkimDirectory}/${cfg.domain} 170 ${pkgs.opendkim}/bin/opendkim-genkey --bits ${toString cfg.dkimBits} --domain ${cfg.domain} --directory ${cfg.dkimDirectory}/${cfg.domain} 171 fi 172 ''; 173 174 services.spamassassin.enable = false; 175 176 services.opensmtpd = { 177 enable = true; 178 serverConfiguration = '' 179 pki ${cfg.domain} cert "/var/lib/acme/${cfg.domain}/fullchain.pem" 180 pki ${cfg.domain} key "/var/lib/acme/${cfg.domain}/key.pem" 181 182 table credentials file:${files.credentials} 183 table recipients file:${files.recipients} 184 table aliases file:${files.aliases} 185 186 listen on 0.0.0.0 port 25 hostname ${cfg.domain} tls pki ${cfg.domain} 187 listen on 0.0.0.0 port 12566 hostname ${cfg.domain} tls-require pki ${cfg.domain} auth <credentials> 188 listen on 2600:3c01::f03c:91ff:fe08:5bfb port 12566 hostname ${cfg.domain} tls-require pki ${cfg.domain} auth <credentials> 189 190 action "local_mail" lmtp localhost:24 alias <aliases> 191 action "outbound" relay helo "${cfg.domain}" 192 193 match from any for domain "${cfg.domain}" action "local_mail" 194 match for local action "local_mail" 195 196 match from any auth for any action "outbound" 197 match for any action "outbound" 198 ''; 199 procPackages = [ pkgs.opensmtpd-extras ]; 200 }; 201 202 services.dovecot2 = { 203 enable = true; 204 enablePop3 = false; 205 enableLmtp = true; 206 mailLocation = "maildir:/var/spool/mail/%n"; 207 mailUser = cfg.user; 208 mailGroup = cfg.group; 209 modules = [ pkgs.dovecot_pigeonhole ]; 210 sslServerCert = "/var/lib/acme/${cfg.domain}/fullchain.pem"; 211 sslServerKey = "/var/lib/acme/${cfg.domain}/key.pem"; 212 enablePAM = false; 213 sieveScripts = { 214 before = files.spamassassinSieve; 215 before2 = pkgs.writeText "sieves" cfg.sieves; 216 }; 217 extraConfig = '' 218 disable_plaintext_auth = no 219 postmaster_address = postmaster@${cfg.domain} 220 mail_attribute_dict = file:/var/spool/mail/%n/dovecot-attributes 221 222 service lmtp { 223 inet_listener lmtp { 224 address = 127.0.0.1 ::1 225 port = 24 226 } 227 } 228 229 service imap { 230 vsz_limit = 1024 M 231 } 232 233 service imap-login { 234 inet_listener imaps { 235 address = 10.100.0.7 236 port = 12788 237 ssl = no 238 } 239 } 240 241 userdb { 242 driver = passwd-file 243 args = username_format=%n ${files.users} 244 default_fields = uid=${cfg.user} gid=${cfg.user} home=/var/spool/mail/%n 245 } 246 247 passdb { 248 driver = passwd-file 249 args = username_format=%n ${files.users} 250 } 251 252 namespace inbox { 253 inbox = yes 254 255 mailbox Sent { 256 auto = subscribe 257 special_use = \Sent 258 } 259 260 mailbox Drafts { 261 auto = subscribe 262 special_use = \Drafts 263 } 264 265 mailbox Spam { 266 auto = subscribe 267 special_use = \Junk 268 } 269 270 mailbox Trash { 271 auto = subscribe 272 special_use = \Trash 273 } 274 275 mailbox Archives { 276 auto = subscribe 277 special_use = \Archive 278 } 279 280 ${mailbox "Alerts"} 281 ${mailbox "Bulk"} 282 ${mailbox "RSS"} 283 ${mailbox "GitHub"} 284 ${mailbox "Lists"} 285 ${mailbox "YouTube"} 286 ${mailbox "Lists.ats"} 287 ${mailbox "Arxiv"} 288 ${mailbox "Reddit"} 289 ${mailbox "Lists.lobsters"} 290 ${mailbox "Lists.icn"} 291 ${mailbox "HackerNews"} 292 ${mailbox "Lists.craigslist"} 293 ${mailbox "Lists.bitcoin"} 294 ${mailbox "Lists.elm"} 295 ${mailbox "Lists.emacs"} 296 ${mailbox "Lists.guix"} 297 ${mailbox "Lists.haskell"} 298 ${mailbox "Lists.lkml"} 299 ${mailbox "Lists.nix"} 300 ${mailbox "Lists.nixpkgs"} 301 ${mailbox "Lists.shen"} 302 ${mailbox "Lists.spacemacs"} 303 ${mailbox "Monstercat"} 304 ${mailbox "Updates"} 305 306 } 307 308 protocol lmtp { 309 mail_plugins = $mail_plugins sieve notify push_notification 310 } 311 312 protocol imap { 313 imap_metadata = yes 314 } 315 ''; 316 }; 317 318 # users.extraUsers = optional (cfg.user == "vmail") { 319 # name = "vmail"; 320 # uid = cfg.uid; 321 # group = cfg.group; 322 # }; 323 324 # users.extraGroups = optional (cfg.group == "vmail") { 325 # name = "vmail"; 326 # gid = cfg.gid; 327 # }; 328 329 networking.firewall.allowedTCPPorts = [ 25 ]; 330 }; 331 }