citadel

My dotfiles, scripts and nix configs
git clone git://jb55.com/citadel
Log | Files | Refs | README | LICENSE

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 }