quota: expose global quota settings

With the options in the upstream dovecot module gone the quota support
and its option now live in our downstream module.

The only behavior change this introduces is not setting a global per
user default instead of the previous 100G per user.

Diabling quota support and setting per user quotas now raises an
assertion:

````
Failed assertions:
- Without quota support enabled, per-user quotas cannot be applied to the following accounts:

  - lowquota@example.com

  Either remove per user quota settings or re-enable `mailserver.quota.enable`.
````
This commit is contained in:
Martin Weinelt
2026-03-17 02:21:10 +01:00
parent 44149c527e
commit 0da8e2b197
4 changed files with 72 additions and 13 deletions
+34 -2
View File
@@ -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.
'';
};
+35 -11
View File
@@ -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;
+2
View File
@@ -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"
];
+1
View File
@@ -33,6 +33,7 @@ groups = [
"mailserver.dmarcReporting",
"mailserver.tlsrpt",
"mailserver.fullTextSearch",
"mailserver.quota",
"mailserver.redis",
"mailserver.ldap",
"mailserver.monitoring",