aboutsummaryrefslogtreecommitdiffstats
path: root/modules/by-name/st/stalwart-mail
diff options
context:
space:
mode:
Diffstat (limited to 'modules/by-name/st/stalwart-mail')
-rw-r--r--modules/by-name/st/stalwart-mail/module.nix428
-rw-r--r--modules/by-name/st/stalwart-mail/settings.nix552
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;
- };
- };
- };
-}