{ config, lib, pkgs, inputs, ... }: let inherit (lib) mkOption mkEnableOption mkPackageOption types ; cfg = config.services.stalwart; configFormat = pkgs.formats.toml { }; configFile = configFormat.generate "stalwart-mail.toml" cfg.settings; listenerOpts = name: defaultPort: tls: { enable = mkOption { type = types.bool; default = true; }; port = mkOption { type = types.port; default = defaultPort; }; firewallOpen = mkOption { type = types.bool; default = true; }; listenAddress = mkOption { type = types.str; default = "[::]"; }; tls = mkOption { type = types.bool; default = tls; }; }; in { options.services.stalwart = { enable = mkEnableOption "Enables Stalwart service"; package = mkOption { type = types.package; default = pkgs.stalwart; }; dataDir = mkOption { type = types.path; default = "/var/lib/stalwart"; }; settings = mkOption { inherit (configFormat) type; default = { }; description = '' Configuration options for the Stalwart email server. See for available options. By default, the module is configured to store everything locally. ''; }; hostname = mkOption { type = types.str; }; smtp = listenerOpts "smtp" 25 false; submissions = listenerOpts "submissions" 465 true; imap = listenerOpts "imap" 993 true; jmap = listenerOpts "jmap" 443 true; sieve = listenerOpts "managesieve" 4190 false; }; config = lib.mkIf cfg.enable { # TODO: open firewall ports networking.firewall.allowedTCPPorts = lib.lists.flatten [ (lib.lists.optional cfg.smtp.enable cfg.smtp.port) (lib.lists.optional cfg.submissions.enable cfg.submissions.port) (lib.lists.optional cfg.imap.enable cfg.imap.port) (lib.lists.optional cfg.jmap.enable cfg.jmap.port) (lib.lists.optional cfg.sieve.enable cfg.sieve.port) ]; services.stalwart.settings = { tracer.journal = { type = "journal"; level = lib.mkDefault "info"; ansi = false; enable = lib.mkDefault true; }; webadmin = { path = "/var/cache/stalwart"; resource = lib.mkDefault "file://${cfg.package.webadmin}/webadmin.zip"; }; lookup.default.hostname = cfg.hostname; resolver = { type = lib.mkDefault "system"; public-suffix = lib.mkDefault [ "file://${pkgs.publicsuffix-list}/share/publicsuffix/public_suffix_list.dat" ]; }; server.listener = let mkListener = listCfg: protocol: lib.mkIf listCfg.enable { inherit protocol; bind = [ "${listCfg.listenAddress}:${toString listCfg.port}" ]; tls.implicit = listCfg.tls; }; in { smtp = mkListener cfg.smtp "smtp"; submissions = mkListener cfg.submissions "smtp"; imap = mkListener cfg.imap "imap"; sieve = mkListener cfg.sieve "managesieve"; jmap = mkListener cfg.jmap "http"; }; store.rocksdb = { type = "rocksdb"; path = lib.mkDefault "${cfg.dataDir}/rocksdb"; purge.frequency = "0 3 *"; }; store.filesystem = { type = "fs"; path = lib.mkDefault "${cfg.dataDir}/store"; purge.frequency = "0 3 *"; }; storage = { data = lib.mkDefault "rocksdb"; fts = lib.mkDefault "rocksdb"; blob = lib.mkDefault "filesystem"; lookup = lib.mkDefault "rocksdb"; # TODO # directory = lib.mkDefault "default"; }; # queue.outbound.tls.starttls = [ # { "if" = "retry_num > 1 && last_error == 'tls'"; "then" = "disable"; } # { "else" = "require"; } # ]; queue.outbound.tls.starttls = "disable"; }; # This service stores a potentially large amount of data. # Running it as a dynamic user would force chown to be run everytime the # service is restarted on a potentially large number of files. # That would cause unnecessary and unwanted delays. users = { groups.stalwart-mail = { }; users.stalwart-mail = { isSystemUser = true; group = "stalwart-mail"; }; }; systemd.services.stalwart = { enable = cfg.enable; wantedBy = [ "multi-user.target" ]; after = [ "local-fs.target" "network.target" ]; harden = { enable = true; ipSockets = true; onlyLocalhost = false; }; serviceConfig = { ExecStart = "${cfg.package}/bin/stalwart --config=${configFile}"; User = "stalwart-mail"; Group = "stalwart-mail"; # Base from template resources/systemd/stalwart-mail.service Type = "simple"; LimitNOFILE = 65536; KillMode = "process"; KillSignal = "SIGINT"; Restart = "on-failure"; RestartSec = 5; StandardOutput = "journal"; StandardError = "journal"; SyslogIdentifier = "stalwart"; StateDirectory = "stalwart"; CacheDirectory = "stalwart"; # Bind standard privileged ports AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; # Hardening PrivateUsers = false; # incompatible with CAP_NET_BIND_SERVICE UMask = "0077"; }; }; # Make admin commands available in the shell environment.systemPackages = [ cfg.package ]; }; }