diff options
| author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-07-30 14:05:39 +0200 |
|---|---|---|
| committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-07-30 14:05:39 +0200 |
| commit | c153a351659e4596acfc31f00bf594343cbfcd68 (patch) | |
| tree | a9ed83de07711d6424fbea09aad28891bae5bfe4 /modules/by-name/st/stalwart-mail | |
| parent | hosts/server3: Setup prometheus server in agent mode (diff) | |
| download | nixos-server-c153a351659e4596acfc31f00bf594343cbfcd68.zip | |
modules: Use namespaces
That might make it easier in the future to merge different server
configs together (and thusly facilitate code-reuse.).
Diffstat (limited to 'modules/by-name/st/stalwart-mail')
| -rw-r--r-- | modules/by-name/st/stalwart-mail/module.nix | 428 | ||||
| -rw-r--r-- | modules/by-name/st/stalwart-mail/settings.nix | 552 |
2 files changed, 0 insertions, 980 deletions
diff --git a/modules/by-name/st/stalwart-mail/module.nix b/modules/by-name/st/stalwart-mail/module.nix deleted file mode 100644 index f7e44d8..0000000 --- a/modules/by-name/st/stalwart-mail/module.nix +++ /dev/null @@ -1,428 +0,0 @@ -{ - lib, - config, - pkgs, - vhackPackages, - ... -}: let - cfg = config.vhack.stalwart-mail; - topCfg = config.services.stalwart-mail; - - configFormat = pkgs.formats.toml {}; - configFile = configFormat.generate "stalwart-mail.toml" topCfg.settings; -in { - imports = [ - ./settings.nix - ]; - - options.vhack.stalwart-mail = { - enable = lib.mkEnableOption "starwart-mail"; - - package = lib.mkPackageOption pkgs "stalwart-mail" {}; - - admin = lib.mkOption { - description = '' - Email address to advertise as administrator. This is the address, where dkim, spv - etc. refusal reports are sent to. - - The format should be: `mailto:<name>@<domain>` - ''; - type = lib.types.str; - example = "mailto:dmarc+rua@example.com"; - default = ""; - }; - - fqdn = lib.mkOption { - type = lib.types.str; - example = "mail.foss-syndicate.org"; - description = '' - The fully qualified domain name for this mail server. - ''; - }; - - principals = lib.mkOption { - default = null; - type = lib.types.nullOr (lib.types.listOf (lib.types.submodule { - options = { - name = lib.mkOption { - type = lib.types.str; - description = "Specifies the username of the account"; - }; - - class = lib.mkOption { - type = lib.types.enum ["individual" "admin"]; - description = "Specifies the account type"; - }; - - description = lib.mkOption { - type = lib.types.str; - description = "Provides a description or full name for the user"; - default = ""; - }; - - secret = lib.mkOption { - type = let - prefix = pre: lib.types.strMatching "^${lib.strings.escapeRegex pre}.*"; - in - lib.types.oneOf [ - (prefix "$argon2") - (prefix "$pbkdf2") - (prefix "$scrypt") - (prefix "$2") # bcrypt - (prefix "$6$") # sha-512 - (prefix "$5$") # sha-256 - (prefix "$sha1") - (prefix "$1") # md5 - (prefix "_") # BSDi crypt - (prefix "{SHA}") # base64 sha - (prefix "{SSHA}") # base64 salted sha - - # unix crypt - (prefix "{CRYPT}") - (prefix "{crypt}") - - # Plain text - (prefix "{PLAIN}") - (prefix "{plain}") - (prefix "{CLEAR}") - (prefix "{clear}") - ]; - description = '' - Sets the password for the user account. - Passwords can be stored hashed or in plain text (not recommended). - See <https://stalw.art/docs/auth/authentication/password/> for a description - of password encoding. - ''; - }; - email = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = '' - A list of email addresses associated with the user. - The first address in the list is considered the primary address. - ''; - }; - }; - })); - }; - - dataDirectory = lib.mkOption { - description = '' - The directory in which to store all storage things. - ''; - default = "/var/lib/stalwart-mail"; - type = lib.types.path; - readOnly = true; - }; - - openFirewall = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - Whether to open TCP firewall ports, which are specified in - {option}`services.stalwart-mail.settings.listener` on all interfaces. - ''; - }; - - security = lib.mkOption { - type = lib.types.nullOr (lib.types.submodule { - options = { - verificationMode = lib.mkOption { - type = lib.types.enum ["relaxed" "strict"]; - description = '' - Whether to allow invalid signatures/checks or not. - ''; - default = "relaxed"; - }; - - dkimKeys = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule { - options = { - dkimPublicKey = lib.mkOption { - type = lib.types.str; - description = '' - The base 64 encoded representation of the public dkim key. - ''; - }; - dkimPrivateKeyPath = lib.mkOption { - type = lib.types.path; - description = '' - The path to the dkim private key agenix file. - Generate it via the `./gen_key` script: - ''; - }; - keyAlgorithm = lib.mkOption { - type = lib.types.enum ["ed25519-sha256" "rsa-sha-256" "rsa-sha-1"]; - description = "The algorithm of the used key"; - }; - }; - }); - description = '' - Which key to use for which domain. The attr keys are the domains - ''; - default = {}; - }; - }; - }); - description = '' - Security options. This should only be set to `null` when testing. - ''; - }; - }; - - config = lib.mkIf cfg.enable { - assertions = [ - { - assertion = cfg.admin != ""; - message = "You need to specify an admin address."; - } - ]; - - vhack.nginx.enable = true; - services = { - stalwart-mail = { - # NOTE(@bpeetz): We do not use the NixOS service, as it comes with too much - # bothersome default configuration and not really any useful configuration. - # However, this decision could obviously be reversed in the future. <2025-02-08> - enable = false; - inherit (cfg) package; - }; - - nginx.virtualHosts."${cfg.fqdn}" = { - locations."/" = { - proxyPass = "http://${builtins.elemAt config.services.stalwart-mail.settings.server.listener.http.bind 0}"; - recommendedProxySettings = true; - }; - - useACMEHost = "${cfg.fqdn}"; - forceSSL = true; - }; - - redis = { - servers = { - "stalwart-mail" = { - enable = true; - - user = "stalwart-mail"; - - # Disable TCP listening. (We have a UNIX socket) - port = 0; - bind = null; - - settings = { - protected-mode = true; - enable-protected-configs = false; - enable-debug-command = false; - enable-module-command = false; - - supervised = "systemd"; - stop-writes-on-bgsave-error = true; - sanitize-dump-payload = "clients"; - }; - }; - }; - }; - }; - security.acme.certs = { - "${cfg.fqdn}" = { - domain = cfg.fqdn; - group = "stalwart-mail-certificates"; - }; - }; - - age.secrets = let - keys = - lib.mapAttrs' ( - keyDomain: keyConfig: - lib.nameValuePair "stalwartMail${keyDomain}" - { - file = keyConfig.dkimPrivateKeyPath; - mode = "600"; - owner = "stalwart-mail"; - group = "stalwart-mail"; - } - ) - cfg.security.dkimKeys; - in - lib.mkIf (cfg.security != null) keys; - - vhack.persist.directories = [ - { - directory = "${cfg.dataDirectory}"; - user = "stalwart-mail"; - group = "stalwart-mail"; - mode = "0700"; - } - { - directory = "${config.services.redis.servers."stalwart-mail".settings.dir}"; - user = "stalwart-mail"; - group = "redis-stalwart-mail"; - mode = "0770"; - } - ]; - - # This service stores a potentially large amount of data. - # Running it as a dynamic user would force chown to be run every time the - # service is restarted on a potentially large number of files. - # That would cause unnecessary and unwanted delays. - users = { - groups = { - stalwart-mail = { - gid = config.vhack.constants.ids.gids.stalwart-mail; - }; - stalwart-mail-certificates = { - gid = config.vhack.constants.ids.gids.stalwart-mail-certificates; - }; - redis-stalwart-mail = { - gid = config.vhack.constants.ids.gids.redis-stalwart-mail; - }; - }; - users = { - nginx = { - extraGroups = ["stalwart-mail-certificates"]; - }; - stalwart-mail = { - isSystemUser = true; - group = "stalwart-mail"; - uid = config.vhack.constants.ids.uids.stalwart-mail; - extraGroups = ["stalwart-mail-certificates"]; - }; - redis-stalwart-mail = { - group = "redis-stalwart-mail"; - uid = config.vhack.constants.ids.uids.redis-stalwart-mail; - }; - }; - }; - - systemd.tmpfiles.rules = [ - "d '${cfg.dataDirectory}' - stalwart-mail stalwart-mail - -" - ]; - - systemd = { - services.stalwart-mail = { - wantedBy = ["multi-user.target"]; - requires = - [ - "redis-stalwart-mail.service" - "network-online.target" - ] - ++ (lib.optional (cfg.security != null) "acme-${cfg.fqdn}.service"); - after = [ - "local-fs.target" - "network.target" - "network-online.target" - "redis-stalwart-mail.service" - "acme-${cfg.fqdn}.service" - ]; - conflicts = [ - "postfix.service" - "sendmail.service" - "exim4.service" - ]; - description = "Stalwart Mail Server"; - - environment = { - SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt"; - NIX_SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt"; - }; - - preStart = let - esa = lib.strings.escapeShellArg; - mkTmpFile = path: "[ -d ${esa path} ] || mkdir --parents ${esa path}"; - - # Create the directories for stalwart - storageDirectories = lib.lists.filter (v: v != null) (lib.attrsets.mapAttrsToList (_: {path ? null, ...}: - if (path != null) - then mkTmpFile path - else null) - topCfg.settings.store); - in - '' - # Stalwart actually wants to store _data_ (e.g., blocked ips) in it's own config file. - # Thus we need to make it writable. - cat ${esa configFile} >$CACHE_DIRECTORY/mutable_config_file.toml - '' - + (builtins.concatStringsSep "\n" storageDirectories); - - serviceConfig = { - ExecStart = pkgs.writers.writeDash "start-stalwart-mail" '' - ${lib.getExe cfg.package} --config="$CACHE_DIRECTORY/mutable_config_file.toml" - ''; - - Restart = "no"; - - KillMode = "process"; - KillSignal = "SIGINT"; - - Type = "simple"; - LimitNOFILE = 65536; - - StandardOutput = "journal"; - StandardError = "journal"; - - ReadWritePaths = [ - cfg.dataDirectory - ]; - CacheDirectory = "stalwart-mail"; - StateDirectory = "stalwart-mail"; - - User = "stalwart-mail"; - Group = "stalwart-mail"; - - SyslogIdentifier = "stalwart-mail"; - - # Bind standard privileged ports - AmbientCapabilities = ["CAP_NET_BIND_SERVICE"]; - CapabilityBoundingSet = ["CAP_NET_BIND_SERVICE"]; - - # Hardening - DeviceAllow = [""]; - LockPersonality = true; - MemoryDenyWriteExecute = true; - PrivateDevices = true; - PrivateUsers = false; # incompatible with CAP_NET_BIND_SERVICE - ProcSubset = "pid"; - PrivateTmp = true; - ProtectClock = true; - ProtectControlGroups = true; - ProtectHome = true; - ProtectHostname = true; - ProtectKernelLogs = true; - ProtectKernelModules = true; - ProtectKernelTunables = true; - ProtectProc = "invisible"; - ProtectSystem = "strict"; - RestrictAddressFamilies = [ - "AF_INET" - "AF_INET6" - "AF_UNIX" - ]; - RestrictNamespaces = true; - RestrictRealtime = true; - RestrictSUIDSGID = true; - SystemCallArchitectures = "native"; - SystemCallFilter = [ - "@system-service" - "~@privileged" - ]; - UMask = "0077"; - }; - }; - }; - - # Make admin commands available in the shell - environment.systemPackages = [cfg.package]; - - networking.firewall = let - parsePorts = listeners: let - parseAddresses = listeners: lib.flatten (lib.mapAttrsToList (name: value: value.bind) listeners); - splitAddress = addr: lib.splitString ":" addr; - extractPort = addr: lib.toInt (builtins.foldl' (a: b: b) "" (splitAddress addr)); - in - builtins.map extractPort (parseAddresses listeners); - in - lib.mkIf (cfg.openFirewall && (builtins.hasAttr "listener" topCfg.settings.server)) - { - allowedTCPPorts = parsePorts topCfg.settings.server.listener; - }; - }; -} diff --git a/modules/by-name/st/stalwart-mail/settings.nix b/modules/by-name/st/stalwart-mail/settings.nix deleted file mode 100644 index dfaf63d..0000000 --- a/modules/by-name/st/stalwart-mail/settings.nix +++ /dev/null @@ -1,552 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: let - cfg = config.vhack.stalwart-mail; - - signaturesByDomain = - (builtins.map ({name, ...}: { - "if" = "sender_domain = '${name}'"; - "then" = "'${name}'"; - }) - (lib.attrsToList cfg.security.dkimKeys)) - ++ [{"else" = false;}]; - - maybeVerificationMode = - if cfg.security != null - then cfg.security.verificationMode - else "disable"; - - directory = - if cfg.principals == null - then "internal" - else "in-memory"; -in { - config.services.stalwart.settings = lib.mkIf cfg.enable { - # https://www.rfc-editor.org/rfc/rfc6376.html#section-3.3 - signature = let - signatures = - lib.mapAttrs (keyDomain: keyConfig: { - private-key = "%{file:${config.age.secrets."stalwartMail${keyDomain}".path}}%"; - - domain = keyDomain; - - selector = "mail"; - headers = ["From" "To" "Cc" "Date" "Subject" "Message-ID" "Organization" "MIME-Version" "Content-Type" "In-Reply-To" "References" "List-Id" "User-Agent" "Thread-Topic" "Thread-Index"]; - algorithm = keyConfig.keyAlgorithm; - - # How do we canonicalize the headers/body? - # https://www.rfc-editor.org/rfc/rfc6376.html#section-3.4 - canonicalization = "simple/simple"; - - expire = "50d"; - report = true; - }) - cfg.security.dkimKeys; - in - lib.mkIf (cfg.security != null) signatures; - - auth = let - # NOTE(@bpeetz): We disable all the checks if the `listener` is submissions, because the - # user's email client will obviously not have the right IP address to pass SPF or - # IPREV. It will also not be able to sign the message with DKIM (as we has to key). <2025-02-25> - ifNotSmpt = valueTrue: valueFalse: [ - { - "if" = "listener != 'submissions'"; - "then" = valueTrue; - } - {"else" = valueFalse;} - ]; - in { - iprev = { - verify = ifNotSmpt maybeVerificationMode "disable"; - }; - spf = { - verify = { - ehlo = ifNotSmpt maybeVerificationMode "disable"; - - mail-from = ifNotSmpt maybeVerificationMode "disable"; - }; - }; - dmarc = { - verify = ifNotSmpt maybeVerificationMode "disable"; - }; - arc = { - seal = lib.mkIf (cfg.security != null) signaturesByDomain; - verify = ifNotSmpt maybeVerificationMode "disable"; - }; - dkim = { - verify = ifNotSmpt maybeVerificationMode "disable"; - - # Ignore insecure dkim signed messages (i.e., messages containing both - # signed and appended not-signed content.) - strict = true; - - sign = - lib.mkIf (cfg.security != null) signaturesByDomain; - }; - }; - report = { - domain = "${cfg.fqdn}"; - submitter = "'${cfg.fqdn}'"; - analysis = { - addresses = ["dmarc@*" "abuse@*"]; - forward = true; - store = "30d"; - }; - tls.aggregate = { - from-name = "'TLS Report'"; - from-address = "'noreply-tls@${cfg.fqdn}'"; - org-name = "'Foss Syndicate Mail Handling'"; - contact-info = "'${cfg.admin}'"; - send = "daily"; - max-size = 26214400; # 25 MiB - sign = lib.mkIf (cfg.security != null) "'${cfg.fqdn}'"; - }; - dmarc = { - aggregate = { - from-name = "'DMARC Report'"; - from-address = "'noreply-dmarc@${cfg.fqdn}'"; - org-name = "'Foss Syndicate Mail Handling'"; - contact-info = "'${cfg.admin}'"; - send = "weekly"; - max-size = 26214400; # 25MiB - sign = lib.mkIf (cfg.security != null) "'${cfg.fqdn}'"; - }; - from-name = "'Report Subsystem'"; - from-address = "'noreply-dmarc@${cfg.fqdn}'"; - subject = "'DMARC Authentication Failure Report'"; - send = "1/1d"; - sign = lib.mkIf (cfg.security != null) signaturesByDomain; - }; - spf = { - from-name = "'Report Subsystem'"; - from-address = "'noreply-spf@${cfg.fqdn}'"; - subject = "'SPF Authentication Failure Report'"; - send = "1/1d"; - sign = lib.mkIf (cfg.security != null) signaturesByDomain; - }; - dkim = { - from-name = "'Report Subsystem'"; - from-address = "'noreply-dkim@${cfg.fqdn}'"; - subject = "'DKIM Authentication Failure Report'"; - send = "1/1d"; - sign = lib.mkIf (cfg.security != null) signaturesByDomain; - }; - dsn = { - from-name = "'Mail Delivery Subsystem'"; - from-address = "'MAILER-DAEMON@${cfg.fqdn}'"; - sign = lib.mkIf (cfg.security != null) signaturesByDomain; - }; - }; - queue = { - schedule = { - retry = "[2m, 5m, 10m, 15m, 30m, 1h, 2h]"; - notify = "[2h, 7h, 1d, 3d]"; - expire = "5d"; - }; - outbound = { - tls = { - starttls = - if maybeVerificationMode == "strict" - then "require" - else "optional"; - allow-invalid-certs = false; - ip-strategy = "ipv6_then_ipv4"; - mta-sts = - if maybeVerificationMode == "strict" - then "require" - else "optional"; - }; - }; - }; - resolver = { - type = "system"; - preserve-intermediates = true; - concurrency = 2; - timeout = "5s"; - attempts = 2; - try-tcp-on-error = true; - public-suffix = [ - "file://${pkgs.publicsuffix-list}/share/publicsuffix/public_suffix_list.dat" - ]; - }; - - spam-filter = { - enable = true; - header = { - status = { - enable = true; - name = "X-Spam-Status"; - }; - result = { - enable = true; - name = "X-Spam-Result"; - }; - }; - bayes = { - enable = true; - - # Learn from users putting mail into JUNK or taking mail out of it. - account = { - enable = true; - }; - }; - - # Fetch the newest spam-filter rules not from github, but from the nix - # package. - resource = "file://${cfg.package.passthru.spam-filter}/spam-filter.toml"; - auto-update = false; - }; - - webadmin = { - # Fetch the newest webadmin bundle not from github, but from the nix - # package. - resource = "file://${cfg.package.passthru.webadmin}/webadmin.zip"; - auto-update = false; - path = "/var/cache/stalwart-mail"; - }; - - session = { - milter = { - # TODO: Add this <2025-02-07> - # "clamav" = { - # enable = true; - # hostname = "127.0.0.1"; - # port = 15112; - # tls = false; - # allow-invalid-certs = false; - # }; - }; - ehlo = { - require = true; - }; - rcpt = { - directory = "'${directory}'"; - catch-all = true; - subaddressing = true; - }; - data = { - spam-filter = true; - add-headers = { - received = true; - received-spf = true; - auth-results = true; - message-id = true; - date = true; - return-path = true; - delivered-to = true; - }; - auth = { - mechanisms = ["LOGIN" "PLAIN"]; - directory = "'${directory}'"; - require = true; - must-match-sender = true; - errors = { - total = 3; - wait = "5s"; - }; - }; - }; - extensions = { - pipelining = true; - chunking = true; - requiretls = true; - no-soliciting = ""; - dsn = [ - { - "if" = "!is_empty(authenticated_as)"; - "then" = true; - } - {"else" = false;} - ]; - future-release = [ - { - "if" = "!is_empty(authenticated_as)"; - "then" = "7d"; - } - {"else" = false;} - ]; - deliver-by = [ - { - "if" = "!is_empty(authenticated_as)"; - "then" = "15d"; - } - {"else" = false;} - ]; - mt-priority = [ - { - "if" = "!is_empty(authenticated_as)"; - "then" = "mixer"; - } - {"else" = false;} - ]; - vrfy = [ - { - "if" = "!is_empty(authenticated_as)"; - "then" = true; - } - {"else" = false;} - ]; - expn = [ - { - "if" = "!is_empty(authenticated_as)"; - "then" = true; - } - {"else" = false;} - ]; - }; - }; - - jmap = { - account = { - purge.frequency = "0 0 *"; - }; - protocol = { - changes.max-history = "14d"; - }; - email = { - # NOTE(@bpeetz): We probably want to enable the auto-deletion of emails in - # the "Junk" and "Deleted" items mail folders, but this should be - # communicated to the users. <2025-02-07> - auto-expunge = false; - }; - mailbox = { - max-depth = 50; - max-name-length = 255; - }; - folders = let - mkFolder = name: { - inherit name; - create = true; - subscribe = true; - }; - in { - inbox = mkFolder "INBOX"; - drafts = mkFolder "DRAFTS"; - sent = mkFolder "SENT"; - trash = mkFolder "TRASH"; - archive = mkFolder "ARCHIVE"; - junk = mkFolder "JUNK"; - shared = {name = "SHARED";}; - }; - }; - imap = { - auth = { - # Allow password login over non tls connection - allow-plain-text = false; - }; - }; - - server = { - hostname = cfg.fqdn; - - listener = { - # HTTP (used for jmap) - "http" = { - bind = ["127.0.0.1:8112"]; - protocol = "http"; - # handled by ngnix - tls.implicit = false; - }; - - # IMAP - "imap" = { - bind = ["[::]:993"]; - protocol = "imap"; - tls.implicit = true; - }; - - # SMTP - "submissions" = { - bind = ["[::]:465"]; - protocol = "smtp"; - tls.implicit = true; - }; - "input" = { - bind = ["[::]:25"]; - protocol = "smtp"; - tls = { - enable = true; - # Require an explicit `STARTTLS` - implicit = false; - }; - }; - - # # POP3 (should be disabled, unless there is a real reason to use it) - # "pop3" = { - # bind = ["[::]:995"]; - # protocol = "pop3"; - # tls.implicit = true; - # }; - - # # LMTP - # "lmtp" = { - # bind = ["[::]:24"]; - # protocol = "lmtp"; - # }; - - # ManageSieve - "managesieve" = { - bind = ["[::]:4190"]; - protocol = "managesieve"; - tls.implicit = true; - }; - }; - - tls = { - enable = true; - - # Expect the client connection to be encrypted from the start (i.e., - # without STARTTLS) - implicit = true; - - certificate = "default"; - }; - - http = { - url = "protocol + '://' + config_get('server.hostname') + ':' + local_port"; - - # We are behind a nginx proxy, and can thus trust this header. - use-x-forwarded = true; - }; - - auto-ban = { - # Ban if the same IP fails to login 10 times in a day - rate = "10/1d"; - - # Ban the login for an user account, if different IP-Addresses tried and - # failed to login 100 times in single day - auth.rate = "100/1d"; - - abuse.rate = "35/1d"; - - loiter.rate = "150/1d"; - - scan.rate = "150/1d"; - }; - - cache = let - MiB = 1024 * 1024; - in { - access-token.size = 10 * MiB; - http-auth.size = 1 * MiB; - permission.size = 5 * MiB; - account.size = 10 * MiB; - mailbox.size = 10 * MiB; - thread.size = 10 * MiB; - bayes.size = 10 * MiB; - dns = { - txt.size = 5 * MiB; - mx.size = 5 * MiB; - ptr.size = 1 * MiB; - ipv4.size = 5 * MiB; - ipv6.size = 5 * MiB; - tlsa.size = 1 * MiB; - mta-sts.size = 1 * MiB; - rbl.size = 5 * MiB; - }; - }; - }; - - tracer = { - # NOTE(@bpeetz): - # We are using the console logger, because that has nice color output. - # Simply using the console should be fine, as systemd pipes that to the journal - # either way. <2025-02-08> - console = { - enable = true; - ansi = true; - level = "info"; - type = "console"; - }; - }; - - store = { - "rocksdb-data" = { - type = "rocksdb"; - path = "${cfg.dataDirectory}/storage/data"; - compression = "lz4"; - - # Perform “maintenance” every day at 3 am local time. - purge.frequency = "0 3 *"; - }; - "rocksdb-directory" = lib.mkIf (cfg.principals == null) { - type = "rocksdb"; - path = "${cfg.dataDirectory}/storage/directory"; - compression = "lz4"; - - # Perform “maintenance” every day at 1 am local time. - purge.frequency = "0 1 *"; - }; - "rocksdb-full-text-search" = { - type = "rocksdb"; - path = "${cfg.dataDirectory}/storage/full-text-search"; - compression = "lz4"; - - # Perform “maintenance” every day at 2 am local time. - purge.frequency = "0 2 *"; - }; - "file-system" = { - type = "fs"; - path = "${cfg.dataDirectory}/storage/blobs"; - depth = 2; - compression = "lz4"; - - # Perform “maintenance” every day at 5:30 am local time. - purge.frequency = "30 5 *"; - }; - "redis" = { - type = "redis"; - redis-type = "single"; - urls = "unix://${config.services.redis.servers."stalwart-mail".unixSocket}"; - timeout = "10s"; - - # Perform “maintenance” every day at 2:30 am local time. - purge.frequency = "30 2 *"; - }; - }; - storage = { - # PostgreSQL is an option, but this is recommended for single node - # configurations. - data = "rocksdb-data"; - - # We could also re-use the data storage backend for that. - blob = "file-system"; - - full-text.default-language = "en"; - fts = "rocksdb-full-text-search"; - - directory = "${directory}"; - - lookup = "redis"; - - # NOTE(@bpeetz): This will encrypt all emails with the users pgp key (if it - # can be determined.) This is a wonderful feature, but quite tiresome, if - # the user intends to read their email without a their pgp key present (for - # example via their smartphone.) <2025-02-07> - encryption.enable = false; - }; - - directory = { - "in-memory" = lib.mkIf (cfg.principals != null) { - type = "memory"; - inherit (cfg) principals; - }; - "internal" = lib.mkIf (cfg.principals == null) { - type = "internal"; - store = "rocksdb-directory"; - }; - }; - - certificate = { - "default" = { - cert = "%{file:${config.security.acme.certs.${cfg.fqdn}.directory}/fullchain.pem}%"; - private-key = "%{file:${config.security.acme.certs.${cfg.fqdn}.directory}/key.pem}%"; - default = true; - }; - }; - }; -} |
