{ config, lib, pkgs, ... }: let svc_domain = "mail.${config.mine.shared.settings.domain}"; svc_domain_smtp = "smtp.${config.mine.shared.settings.domain}"; svc_domain_imap = "imap.${config.mine.shared.settings.domain}"; ports = { smtp = 25; submissions = 465; imaptls = 993; http_management = 7377; }; # place data into own zfs dataset stateDir = config.mine.zfsMounts."rpool/safe/svcs/stalwart"; # user/group stalwart_user = config.users.users.stalwart-mail.name; stalwart_group = config.users.groups.stalwart-mail.name; certLocation = config.security.acme.certs."${svc_domain}".directory; in { services.stalwart-mail = { enable = true; openFirewall = true; settings = { lookup.default.hostname = svc_domain; store.db.path = "${stateDir}/db"; directory.ldap = { type = "ldap"; url = config.mine.shared.settings.ldap.url; base-dn = config.mine.shared.settings.ldap.dc; tls.enable = false; bind = { dn = config.mine.shared.settings.ldap.bind_dn; secret = "%{file:${config.age.secrets.lldap-bind-user-pass.path}}%"; auth = { enable = true; dn = "cn=?,${config.mine.shared.settings.ldap.search_base}"; }; }; filter = let _mkFilter = attrs: ph: config.mine.shared.lib.ldap.mkFilter (lconfig: llib: llib.mkAnd [ (llib.mkGroup lconfig.groups.member) (llib.mkOr (lib.forEach attrs (v: llib.mkSearch v ph))) ] ); attrs = config.mine.shared.settings.ldap.attr // { emailAlias = "mailAlias"; emailList = "mailList"; }; in { name = _mkFilter [ attrs.uid ] "?"; email = _mkFilter [ attrs.email attrs.emailAlias attrs.emailList ] "?"; verify = _mkFilter [ attrs.email attrs.emailAlias ] "*?*"; expand = _mkFilter [ attrs.emailList ] "?"; domains = _mkFilter [ attrs.email attrs.emailAlias ] "*@?"; }; attributes = { name = "uid"; class = "objectClass"; description = "givenName"; secret = "uid"; groups = "memberOf"; email = "mail"; # email-alias = "mailAlias"; # quota = "diskQuota"; }; }; storage.directory = "ldap"; # listeners server.listener = { smtp = { bind = [ "[::]:${builtins.toString ports.smtp}"]; protocol = "smtp"; }; submissions = { bind = [ "[::]:${builtins.toString ports.submissions}"]; protocol = "smtp"; tls.implicit = true; }; imaptls = { bind = [ "[::]:${builtins.toString ports.imaptls}"]; protocol = "imap"; tls.implicit = true; }; management = { bind = [ "127.0.0.1:${builtins.toString ports.http_management}" ]; protocol = "http"; }; }; certificate.domain = { cert = "%{file:${certLocation + "/cert.pem"}}%"; private-key = "%{file:${certLocation + "/key.pem"}}%"; }; server.tls = { certificate = "domain"; enable = true; implicit = true; }; # authentication authentication.fallback-admin = { user = "admin"; secret = "%{file:${config.age.secrets.stalwart-admin-fallback-password.path}}%"; }; }; }; # ensure that the configured domain is added to stalwart systemd.services.stalwart-mail.serviceConfig.ExecStartPost = let stalwart-cli = config.services.stalwart-mail.package + "/bin/stalwart-cli"; in pkgs.writeShellScript "stalwart-setup" '' # TODO(eyjhb): at some point this should be changed sleep 10 export URL="http://127.0.0.1:${builtins.toString ports.http_management}" export CREDENTIALS="$(cat ${config.age.secrets.stalwart-admin-fallback-password.path})" if ${stalwart-cli} domain list | grep --quiet -- '${config.mine.shared.settings.domain}'; then echo "Domain is already created, doing nothing." else echo "Domain was not created, creating it now." ${stalwart-cli} domain create '${config.mine.shared.settings.domain}' fi echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!IMPORTANT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" echo "Remember to setup the correct DNS records. Run the following command to get them:" echo "URL=\"$URL\" stalwart-cli domain dns-records ${config.mine.shared.settings.domain}" echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!IMPORTANT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" ''; # setup so that stalwart can access and write to the directory systemd.services.stalwart-mail.serviceConfig.ReadWritePaths = [ stateDir ]; systemd.tmpfiles.rules = [ "Z ${stateDir} 0700 ${stalwart_user} ${stalwart_group} -" ]; # setup certs services.nginx.virtualHosts."${svc_domain}" = { forceSSL = true; enableACME = true; serverAliases = [ svc_domain_smtp svc_domain_imap ]; root = pkgs.writeTextDir "index.html" "Nothing."; }; # need to change group to stalwart-mail for cert + add nginx to stalwart-mail group to do HTTP ACME users.users.nginx.extraGroups = [ stalwart_group ]; security.acme.certs."${svc_domain}".group = stalwart_group; # setup secrets for stalwart # setup access to ldap bind user credential users.groups.secrets-lldap-bind-user-pass.members = [ stalwart_user ]; age.secrets.stalwart-admin-fallback-password.owner = stalwart_user; mine.shared.settings.mail = { domain = svc_domain; domain_smtp = svc_domain_smtp; domain_imap = svc_domain_imap; ports = ports; }; }