diff --git a/default.nix b/default.nix index cb1f4d5..72317e4 100644 --- a/default.nix +++ b/default.nix @@ -137,6 +137,35 @@ in description = "Message size limit enforced by Postfix."; }; + quota = { + enable = mkOption { + type = types.bool; + default = true; + description = '' + Whether to enable quota support. + + When enabled, incoming mail can be rejected if a mailbox exceeds its + quota. + ''; + }; + + defaults = { + perUser = mkOption { + type = with types; nullOr str; + default = null; + example = "10G"; + description = '' + Default quota applied to all users. + + The value must use a size format like `500M`, `2G`, `10G`. + + If set to `null`, no default per user quota is applied and only + explicit per user quotas apply, if set. + ''; + }; + }; + }; + accounts = mkOption { type = types.attrsOf ( types.submodule ( @@ -260,8 +289,11 @@ in default = null; example = "2G"; description = '' - Per user quota rules. Accepted sizes are `xx k/M/G/T` with the - obvious meaning. Leave blank for the standard quota `100G`. + The quota limit for this user. + + The value is must use a size format like `500M`, `2G`, `10G`. + + If unset, will fall back to {option}`mailserver.quota.defaults.perUser` if set. ''; }; diff --git a/mail-server/dovecot.nix b/mail-server/dovecot.nix index cc4f564..59d9664 100644 --- a/mail-server/dovecot.nix +++ b/mail-server/dovecot.nix @@ -33,6 +33,9 @@ with (import ./common.nix { let inherit (lib) + attrNames + concatMapStringsSep + filterAttrs mapAttrs' mkForce mkIf @@ -170,6 +173,22 @@ in assertion = junkMailboxNumber == 1; message = "nixos-mailserver requires exactly one dovecot mailbox with the 'special_use' flag set to '\\Junk' (${builtins.toString junkMailboxNumber} have been found)"; } + { + assertion = + let + usersWithQuota = attrNames ( + filterAttrs (_: account: account.quota != null) config.mailserver.loginAccounts + ); + in + !cfg.quota.enable -> usersWithQuota == { }; + message = '' + Without quota support enabled, per-user quotas cannot be applied to the following accounts: + + ${concatMapStringsSep "\n" (account: "- ${account}") quotaUsers} + + Either remove per user quota settings or re-enable `mailserver.quota.enable`. + ''; + } ]; warnings = @@ -278,7 +297,7 @@ in }; vsz_limit = "${toString cfg.lmtpMemoryLimit} MB"; }; - "service quota-status" = { + "service quota-status" = mkIf cfg.quota.enable { executable = toString [ "${config.services.dovecot2.package}/libexec/dovecot/quota-status" "-p" @@ -336,8 +355,10 @@ in mail_max_userip_connections = cfg.maxConnectionsPerUser; mail_plugins = [ "$mail_plugins" - "imap_quota" "imap_sieve" + ] + ++ lib.optionals cfg.quota.enable [ + "imap_quota" ]; }; "protocol pop3" = { @@ -347,6 +368,8 @@ in # globally enabled plugins mail_plugins = [ "$mail_plugins" + ] + ++ lib.optionals cfg.quota.enable [ "quota" ] ++ lib.optionals cfg.fullTextSearch.enable [ @@ -437,14 +460,6 @@ in lmtp_save_to_detail_mailbox = cfg.lmtpSaveToDetailMailbox; plugin = { - quota_rule = "*:storage=100G"; # TODO: quota option - quota = "count:User quota"; # per virtual mail user quota - quota_status_success = "DUNNO"; - quota_status_nouser = "DUNNO"; - quota_status_overquota = "552 5.2.2 Mailbox is full"; - quota_grace = "10%%"; - quota_vsizes = true; - sieve = "file:${cfg.sieveDirectory}/%{user}/scripts;active=${cfg.sieveDirectory}/%{user}/active.sieve"; sieve_default = "file:${cfg.sieveDirectory}/%{user}/default.sieve"; sieve_default_name = "default"; @@ -465,7 +480,16 @@ in fts_enforced = cfg.fullTextSearch.enforced; } // (listToMultiAttrs "fts_autoindex_exclude" cfg.fullTextSearch.autoIndexExclude) - ); + ) + // lib.optionalAttrs cfg.quota.enable { + quota_rule = mkIf (cfg.quota.defaults.perUser != null) "*:storage=${cfg.quota.defaults.perUser}"; + quota = "count:User quota"; # per virtual mail user quota + quota_status_success = "DUNNO"; + quota_status_nouser = "DUNNO"; + quota_status_overquota = "552 5.2.2 Mailbox is full"; + quota_grace = "10%%"; + quota_vsizes = true; + }; } // lib.optionalAttrs cfg.debug.dovecot { mail_debug = true; diff --git a/mail-server/postfix.nix b/mail-server/postfix.nix index 52dc45a..0408138 100644 --- a/mail-server/postfix.nix +++ b/mail-server/postfix.nix @@ -373,6 +373,8 @@ in # reject selected recipients "check_recipient_access ${mappedFile "denied_recipients"}" "check_recipient_access ${mappedFile "reject_recipients"}" + ] + ++ lib.optionals cfg.quota.enable [ # quota checking "check_policy_service unix:/run/dovecot2/quota-status" ]; diff --git a/scripts/generate-options.py b/scripts/generate-options.py index dfba41d..eb13425 100644 --- a/scripts/generate-options.py +++ b/scripts/generate-options.py @@ -33,6 +33,7 @@ groups = [ "mailserver.dmarcReporting", "mailserver.tlsrpt", "mailserver.fullTextSearch", + "mailserver.quota", "mailserver.redis", "mailserver.ldap", "mailserver.monitoring",