{ config, pkgs, lib, ...}: 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 ]; 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"'';} {record=cfg.host.ip; r_type="PTR"; value="${cfg.sub}.${cfg.domain}.";} ]; # to provide the certs services.nginx.virtualHosts = { "${cfg.sub}.${cfg.domain}" = { forceSSL = true; useACMEHost = "skynet"; serverName = "${cfg.sub}.${cfg.domain}"; }; }; # systemd.services.dovecot2.serviceConfig = { # # restart it daily, override default values # Restart = lib.mkForce "always"; # RestartSec = lib.mkForce "1d"; # }; 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 } ''; */ }; }