{ config, pkgs, lib, inputs, ...}: with lib; let cfg = config.services.skynet_email; # create teh new strings create_filter_array = map (x: "(memberOf=cn=${x},ou=groups,${cfg.ldap.base})"); create_filter_join = (x: concatStringsSep "" x); # thought you could escape racket? create_filter = (groups: create_filter_join (create_filter_array groups) ); in { imports = [ ./dns.nix ./acme.nix ./nginx.nix inputs.simple-nixos-mailserver.nixosModule ]; options.services.skynet_email = { # options that need to be passed in to make this work enable = mkEnableOption "Skynet Email"; host = { ip = mkOption { type = types.str; }; name = mkOption { type = types.str; }; }; domain = mkOption { type = types.str; default = "skynet.ie"; description = lib.mdDoc "domaino"; }; sub = mkOption { type = types.str; default = "mail"; description = lib.mdDoc "mailserver subdomain"; }; groups = mkOption { type = types.listOf types.str; default = [ # general skynet users "skynet-users" # C&S folsk get access "skynet-cns" # skynet service accounts "skynet-service" ]; description = lib.mdDoc "Groups we want to allow access to the email"; }; ldap = { hosts = mkOption { type = types.listOf types.str; default = [ "ldaps://sso.skynet.ie" ]; description = lib.mdDoc "ldap domains"; }; base = mkOption { type = types.str; default = "dc=skynet,dc=ie"; description = lib.mdDoc "where to find users"; }; searchBase = mkOption { type = types.str; default = "ou=users,${cfg.ldap.base}"; description = lib.mdDoc "where to find users"; }; bind_dn = mkOption { type = types.str; default = "cn=admin,${cfg.ldap.base}"; description = lib.mdDoc "where to find users"; }; }; }; config = mkIf cfg.enable { services.skynet_backup.normal.backups = [ "/var/vmail" "/var/dkim" ]; age.secrets.ldap_pw.file = ../secrets/ldap/pw.age; # set up dns record for it skynet_dns.records = [ # basic one {record="mail"; r_type="A"; value=cfg.host.ip;} # TXT records, all tehse are inside escaped strings to allow using "" # SPF record {record="${cfg.domain}."; r_type="TXT"; value=''"v=spf1 a:${cfg.sub}.${cfg.domain} -all"'';} # DKIM keys {record="mail._domainkey.skynet.ie."; r_type="TXT"; value=''"v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxju1Ie60BdHwyFVPNQKovL/cX9IFPzBKgjnHZf+WBzDCFKSBpf7NvnfXajtFDQN0poaN/Qfifid+V55ZCNDBn8Y3qZa4Y69iNiLw2DdvYf0HdnxX6+pLpbmj7tikGGLJ62xnhkJhoELnz5gCOhpyoiv0tSQVaJpaGZmoll861/QIDAQAB"'';} {record="mail._domainkey.ulcompsoc.ie."; r_type="TXT"; value=''"v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDl8ptSASx37t5sfmU2d2Y6yi9AVrsNFBZDmJ2uaLa4NuvAjxGQCw4wx+1Jui/HOuKYLpntLsjN851wgPR+3i51g4OblqBDvcHn9NYgWRZfHj9AASANQjdsaAbkXuyKuO46hZqeWlpESAcD6a4Evam4fkm+kiZC0+rccb4cWgsuLwIDAQAB"'';} # DMARC {record="_dmarc.${cfg.domain}."; r_type="TXT"; value=''"v=DMARC1; p=none"'';} # reverse pointer {record=cfg.host.ip; r_type="PTR"; value="${cfg.sub}.${cfg.domain}.";} # SRV records to help gmail on android etc find the correct mail.skynet.ie domain for config rather than just defaulting to skynet.ie # https://serverfault.com/questions/935192/how-to-setup-auto-configure-email-for-android-mail-app-on-your-server/1018406#1018406 # response should be: # _imap._tcp SRV 0 1 143 imap.example.com. {record="_imaps._tcp"; r_type="SRV"; value="0 1 993 ${cfg.sub}.${cfg.domain}";} {record="_imap._tcp"; r_type="SRV"; value="0 1 143 ${cfg.sub}.${cfg.domain}";} {record="_submissions._tcp"; r_type="SRV"; value="0 1 465 ${cfg.sub}.${cfg.domain}";} {record="_submission._tcp"; r_type="SRV"; value="0 1 587 ${cfg.sub}.${cfg.domain}";} ]; # to provide the certs services.nginx.virtualHosts = { "${cfg.sub}.${cfg.domain}" = { forceSSL = true; useACMEHost = "skynet"; # override the inbuilt nginx config enableACME = false; serverName = "${cfg.sub}.${cfg.domain}"; }; }; #https://nixos-mailserver.readthedocs.io/en/latest/add-roundcube.html users.groups.nginx = {}; users.groups.roundcube = {}; services.roundcube = { enable = true; # this is the url of the vhost, not necessarily the same as the fqdn of # the mailserver hostName = "${cfg.sub}.${cfg.domain}"; extraConfig = '' # starttls needed for authentication, so the fqdn required to match # the certificate $config['smtp_server'] = "ssl://${cfg.sub}.${cfg.domain}"; $config['smtp_user'] = "%u"; $config['smtp_pass'] = "%p"; $config['imap_host'] = "ssl://${cfg.sub}.${cfg.domain}"; $config['product_name'] = "Skynet Webmail"; $config['identities_level'] = 4; $config['login_username_filter'] = "email"; $config['ldap_public']['public'] = array( 'name' => 'Public LDAP Addressbook', 'hosts' => array('sso.skynet.ie'), 'port' => 636 , 'user_specific' => false, 'base_dn' => 'ou=users,dc=skynet,dc=ie', 'filter' => '(skMemberOf=cn=skynet-users-linux,ou=groups,dc=skynet,dc=ie)', 'fieldmap' => [ // Roundcube => LDAP:limit 'name' => 'cn', 'surname' => 'sn', 'email' => 'skMail:*', ] ); ''; }; mailserver = { enable = true; fqdn = "${cfg.sub}.${cfg.domain}"; domains = [ cfg.domain ]; # use the letsencrypt certs certificateScheme = "acme"; # 20MB max size messageSizeLimit = 20000000; ldap = { enable = true; uris = cfg.ldap.hosts; bind = { dn = cfg.ldap.bind_dn; passwordFile = config.age.secrets.ldap_pw.path; }; searchBase = cfg.ldap.searchBase; searchScope = "sub"; dovecot = { userFilter = "(skMail=%u)"; # can lock down how much space each user has access to from ldap userAttrs = "quotaEmail=quota_rule=*:bytes=%$,=quota_rule2=Trash:storage=+100M"; # accept emails in, but only allow access to paid up members passFilter = "(&(|${create_filter cfg.groups})(skMail=%u))"; }; postfix = { filter = "(|(skMail=%s)(uid=%s))"; uidAttribute = "skMail"; mailAttribute = "skMail"; }; }; # feckin spammers rejectRecipients = [ ]; }; # tune the spam filter /* services.rspamd.extraConfig = '' actions { reject = null; # Disable rejects, default is 15 add_header = 7; # Add header when reaching this score greylist = 4; # Apply greylisting when reaching this score } ''; */ }; }