Rename mailserver.loginAccounts to mailserver.accounts

The "login" prefix makes this option more confusing rather than clearer,
because what other account types are there? LDAP ones for example, but
you can login with those too, so the prefix is pointless.
This commit is contained in:
Martin Weinelt
2026-03-20 01:14:43 +01:00
parent 5fdb686c66
commit e9337b346f
16 changed files with 35 additions and 34 deletions
+10 -9
View File
@@ -136,7 +136,7 @@ in
description = "Message size limit enforced by Postfix."; description = "Message size limit enforced by Postfix.";
}; };
loginAccounts = mkOption { accounts = mkOption {
type = types.attrsOf ( type = types.attrsOf (
types.submodule ( types.submodule (
{ name, ... }: { name, ... }:
@@ -170,7 +170,7 @@ in
storing hashed secrets in the world-readable Nix store. storing hashed secrets in the world-readable Nix store.
Passing the hash through Passing the hash through
{option}`mailserver.loginAccounts.<name>.hashedPasswordFile` {option}`mailserver.accounts.<name>.hashedPasswordFile`
allows relying on filesystem discretionary access control as allows relying on filesystem discretionary access control as
another security boundary. another security boundary.
::: :::
@@ -231,7 +231,7 @@ in
example = [ ''/^tom\..*@domain\.com$/'' ]; example = [ ''/^tom\..*@domain\.com$/'' ];
default = [ ]; default = [ ];
description = '' description = ''
Same as {option}`mailserver.loginAccounts.<name>.aliases` but Same as {option}`mailserver.accounts.<name>.aliases` but
using PCRE (Perl compatible regex). using PCRE (Perl compatible regex).
''; '';
}; };
@@ -248,7 +248,7 @@ in
:::{warning} :::{warning}
Does not allow sending from all addresses of these domains. Does not allow sending from all addresses of these domains.
Use {option}`mailserver.loginAccounts.<name>.aliases` if that Use {option}`mailserver.accounts.<name>.aliases` if that
is required. is required.
::: :::
''; '';
@@ -295,7 +295,7 @@ in
Emails sent to send-only accounts will Emails sent to send-only accounts will
be rejected with the reason configured in be rejected with the reason configured in
{option}`mailserver.loginAccounts.<name>.sendOnlyRejectMessage`. {option}`mailserver.accounts.<name>.sendOnlyRejectMessage`.
''; '';
}; };
@@ -305,7 +305,7 @@ in
description = '' description = ''
The message returned to the sender for a send-only account. The message returned to the sender for a send-only account.
See {option}`mailserver.loginAccounts.<name>.sendOnly`. See {option}`mailserver.accounts.<name>.sendOnly`.
''; '';
}; };
}; };
@@ -684,13 +684,13 @@ in
aliases = mkOption { aliases = mkOption {
type = type =
let let
loginAccount = mkOptionType { account = mkOptionType {
name = "Login Account"; name = "Login Account";
check = account: builtins.elem account (builtins.attrNames cfg.loginAccounts); check = account: builtins.elem account (builtins.attrNames cfg.accounts);
}; };
in in
with types; with types;
attrsOf (either loginAccount (nonEmptyListOf loginAccount)); attrsOf (either account (nonEmptyListOf account));
example = { example = {
"postmaster@example.com" = "user1@example.com"; "postmaster@example.com" = "user1@example.com";
"abuse@example.com" = "user1@example.com"; "abuse@example.com" = "user1@example.com";
@@ -1714,5 +1714,6 @@ in
[ "mailserver" "ldap" "attributes" "mail" ] [ "mailserver" "ldap" "attributes" "mail" ]
) )
(mkRenamedOptionModule [ "mailserver" "extraVirtualAliases" ] [ "mailserver" "aliases" ]) (mkRenamedOptionModule [ "mailserver" "extraVirtualAliases" ] [ "mailserver" "aliases" ])
(mkRenamedOptionModule [ "mailserver" "loginAccounts" ] [ "mailserver" "accounts" ])
]; ];
} }
+1 -1
View File
@@ -13,7 +13,7 @@ domain ``example.com`` and send mails with any address of this domain:
.. code:: nix .. code:: nix
mailserver.loginAccounts = { mailserver.accounts = {
"user@example.com" = { "user@example.com" = {
aliases = [ "@example.com" ]; aliases = [ "@example.com" ];
}; };
+1 -1
View File
@@ -12,7 +12,7 @@ let
mapAttrsToList mapAttrsToList
; ;
mailAccounts = config.mailserver.loginAccounts; mailAccounts = config.mailserver.accounts;
htpasswd = pkgs.writeText "radicale.users" ( htpasswd = pkgs.writeText "radicale.users" (
concatStrings (flip mapAttrsToList mailAccounts (mail: user: "${mail}+:${user.hashedPassword}\n")) concatStrings (flip mapAttrsToList mailAccounts (mail: user: "${mail}+:${user.hashedPassword}\n"))
); );
+1 -1
View File
@@ -10,7 +10,7 @@ Limitations
Radicale since the 3.x release (introduced in NixOS 20.09) does not support Radicale since the 3.x release (introduced in NixOS 20.09) does not support
traditional crypt() password hashes any longer. To establish access for traditional crypt() password hashes any longer. To establish access for
existing :option:`mailserver.loginAccounts`, the hashing method used existing :option:`mailserver.accounts`, the hashing method used
for ``hashedPassword`` needs to be compatible with one of the available for ``hashedPassword`` needs to be compatible with one of the available
`htpasswd_encryption`_ methods. Such hashes can for example be created using `htpasswd_encryption`_ methods. Such hashes can for example be created using
+1 -1
View File
@@ -21,7 +21,7 @@ NixOS 26.05
is an alternative to hashed passwords that integrates well with workflows is an alternative to hashed passwords that integrates well with workflows
established by `agenix`_/`sops-nix`_ that instead rely on encryption. This established by `agenix`_/`sops-nix`_ that instead rely on encryption. This
option prevents files from leaking in to the Nix store. option prevents files from leaking in to the Nix store.
See :option:`mailserver.loginAccounts.<name>.passwordFile`. See :option:`mailserver.accounts.<name>.passwordFile`.
- LDAP setups require a migration of Dovecot home directories to - LDAP setups require a migration of Dovecot home directories to
`UUID based home directories`_. The exact UUID attribute can be customized `UUID based home directories`_. The exact UUID attribute can be customized
through :option:`mailserver.ldap.attributes.uuid`. through :option:`mailserver.ldap.attributes.uuid`.
+1 -1
View File
@@ -38,7 +38,7 @@
# A list of all login accounts. To create the password hashes, use # A list of all login accounts. To create the password hashes, use
# nix-shell -p mkpasswd --run 'mkpasswd -s' # nix-shell -p mkpasswd --run 'mkpasswd -s'
loginAccounts = { accounts = {
"user1@example.com" = { "user1@example.com" = {
# Reads the password hash from a file on the server # Reads the password hash from a file on the server
hashedPasswordFile = "/a/file/containing/a/hashed/password"; hashedPasswordFile = "/a/file/containing/a/hashed/password";
+4 -4
View File
@@ -51,12 +51,12 @@ rec {
builtins.toString (mkHashFile name value.hashedPassword) builtins.toString (mkHashFile name value.hashedPassword)
else else
value.passwordFile value.passwordFile
) cfg.loginAccounts; ) cfg.accounts;
# Collect accounts with plain text passwords that require hashing # Collect accounts with plain text passwords that require hashing
accountsWithPlaintextPasswordFiles = lib.filter ( accountsWithPlaintextPasswordFiles = lib.filter (name: cfg.accounts.${name}.passwordFile != null) (
name: cfg.loginAccounts.${name}.passwordFile != null builtins.attrNames cfg.accounts
) (builtins.attrNames cfg.loginAccounts); );
# Appends the LDAP bind password to files to avoid writing this # Appends the LDAP bind password to files to avoid writing this
# password into the Nix store. # password into the Nix store.
+3 -3
View File
@@ -115,7 +115,7 @@ let
umask 077 umask 077
for f in ${ for f in ${
builtins.toString (lib.mapAttrsToList (name: _: passwordFiles."${name}") cfg.loginAccounts) builtins.toString (lib.mapAttrsToList (name: _: passwordFiles."${name}") cfg.accounts)
}; do }; do
if [ ! -f "$f" ]; then if [ ! -f "$f" ]; then
echo "Expected password hash file $f does not exist!" echo "Expected password hash file $f does not exist!"
@@ -131,7 +131,7 @@ let
"${name}:${"$(sed -n '1{p;p;q}' ${passwordFiles."${name}"} | ${lib.getExe' pkgs.dovecot "doveadm"} pw)"}::::::" "${name}:${"$(sed -n '1{p;p;q}' ${passwordFiles."${name}"} | ${lib.getExe' pkgs.dovecot "doveadm"} pw)"}::::::"
else else
"${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}::::::" "${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}::::::"
) cfg.loginAccounts ) cfg.accounts
)} )}
EOF EOF
@@ -141,7 +141,7 @@ let
name: value: name: value:
"${name}:::::::" "${name}:::::::"
+ lib.optionalString (value.quota != null) "userdb_quota_rule=*:storage=${value.quota}" + lib.optionalString (value.quota != null) "userdb_quota_rule=*:storage=${value.quota}"
) cfg.loginAccounts ) cfg.accounts
)} )}
EOF EOF
''; '';
+4 -4
View File
@@ -51,7 +51,7 @@ let
to = name; to = name;
in in
map (from: { "${from}" = to; }) (value.aliases ++ lib.singleton name) map (from: { "${from}" = to; }) (value.aliases ++ lib.singleton name)
) cfg.loginAccounts ) cfg.accounts
) )
); );
regex_valiases_postfix = mergeLookupTables ( regex_valiases_postfix = mergeLookupTables (
@@ -62,7 +62,7 @@ let
to = name; to = name;
in in
map (from: { "${from}" = to; }) value.aliasesRegexp map (from: { "${from}" = to; }) value.aliasesRegexp
) cfg.loginAccounts ) cfg.accounts
) )
); );
@@ -75,7 +75,7 @@ let
to = name; to = name;
in in
map (from: { "@${from}" = to; }) value.catchAll map (from: { "@${from}" = to; }) value.catchAll
) cfg.loginAccounts ) cfg.accounts
) )
); );
@@ -127,7 +127,7 @@ let
# denied_recipients_postfix :: [ String ] # denied_recipients_postfix :: [ String ]
denied_recipients_postfix = map (acct: "${acct.name} REJECT ${acct.sendOnlyRejectMessage}") ( denied_recipients_postfix = map (acct: "${acct.name} REJECT ${acct.sendOnlyRejectMessage}") (
lib.filter (acct: acct.sendOnly) (lib.attrValues cfg.loginAccounts) lib.filter (acct: acct.sendOnly) (lib.attrValues cfg.accounts)
); );
denied_recipients_file = builtins.toFile "denied_recipients" ( denied_recipients_file = builtins.toFile "denied_recipients" (
lib.concatStringsSep "\n" denied_recipients_postfix lib.concatStringsSep "\n" denied_recipients_postfix
+3 -3
View File
@@ -86,7 +86,7 @@ let
rm "${sieveDirectory}/${name}/default.svbin" rm "${sieveDirectory}/${name}/default.svbin"
fi fi
'' ''
) (map (user: { inherit (user) name sieveScript; }) (lib.attrValues loginAccounts))} ) (map (user: { inherit (user) name sieveScript; }) (lib.attrValues accounts))}
''; '';
in in
{ {
@@ -102,14 +102,14 @@ in
] ]
) == 1; ) == 1;
message = "Login account ${acct.name} must provide exactly one of password file, hashed password, or hashed password file"; message = "Login account ${acct.name} must provide exactly one of password file, hashed password, or hashed password file";
}) (lib.attrValues loginAccounts); }) (lib.attrValues accounts);
# warn for accounts that specify both password and file # warn for accounts that specify both password and file
warnings = warnings =
map (acct: "${acct.name} specifies both a password hash and hash file; hash file will be used") map (acct: "${acct.name} specifies both a password hash and hash file; hash file will be used")
( (
lib.filter (acct: (acct.hashedPassword != null && acct.hashedPasswordFile != null)) ( lib.filter (acct: (acct.hashedPassword != null && acct.hashedPasswordFile != null)) (
lib.attrValues loginAccounts lib.attrValues accounts
) )
); );
+1 -1
View File
@@ -25,7 +25,7 @@ f = open(sys.argv[1])
options = json.load(f) options = json.load(f)
groups = [ groups = [
"mailserver.loginAccounts", "mailserver.accounts",
"mailserver.x509", "mailserver.x509",
"mailserver.dkim", "mailserver.dkim",
"mailserver.srs", "mailserver.srs",
+1 -1
View File
@@ -77,7 +77,7 @@
]; ];
virusScanning = true; virusScanning = true;
loginAccounts = { accounts = {
"user1@example.com" = { "user1@example.com" = {
hashedPassword = "$6$/z4n8AQl6K$kiOkBTWlZfBd7PvF5GsJ8PmPgdZsFGN1jPGZufxxr60PoR0oUsrvzm2oQiflyz5ir9fFJ.d/zKm/NgLXNUsNX/"; hashedPassword = "$6$/z4n8AQl6K$kiOkBTWlZfBd7PvF5GsJ8PmPgdZsFGN1jPGZufxxr60PoR0oUsrvzm2oQiflyz5ir9fFJ.d/zKm/NgLXNUsNX/";
aliases = [ "postmaster@example.com" ]; aliases = [ "postmaster@example.com" ];
+1 -1
View File
@@ -66,7 +66,7 @@
}; };
dmarcReporting.enable = true; dmarcReporting.enable = true;
loginAccounts = { accounts = {
"user1@example.com" = { "user1@example.com" = {
hashedPassword = "$6$/z4n8AQl6K$kiOkBTWlZfBd7PvF5GsJ8PmPgdZsFGN1jPGZufxxr60PoR0oUsrvzm2oQiflyz5ir9fFJ.d/zKm/NgLXNUsNX/"; hashedPassword = "$6$/z4n8AQl6K$kiOkBTWlZfBd7PvF5GsJ8PmPgdZsFGN1jPGZufxxr60PoR0oUsrvzm2oQiflyz5ir9fFJ.d/zKm/NgLXNUsNX/";
aliases = [ "postmaster@example.com" ]; aliases = [ "postmaster@example.com" ];
+1 -1
View File
@@ -87,7 +87,7 @@ in
]; ];
localDnsResolver = false; localDnsResolver = false;
loginAccounts = { accounts = {
"user1@example.com" = { "user1@example.com" = {
hashedPasswordFile = hashedPasswordFile; hashedPasswordFile = hashedPasswordFile;
}; };
+1 -1
View File
@@ -133,7 +133,7 @@ in
"frank@example.com" = "mallory@example.com"; "frank@example.com" = "mallory@example.com";
}; };
loginAccounts = { accounts = {
# Colliding local account takes precedence over LDAP account with # Colliding local account takes precedence over LDAP account with
# same address. # same address.
"carol@example.com" = { "carol@example.com" = {
+1 -1
View File
@@ -35,7 +35,7 @@ let
fqdn = "mail.${domain}"; fqdn = "mail.${domain}";
domains = [ domain ]; domains = [ domain ];
localDnsResolver = false; localDnsResolver = false;
loginAccounts = { accounts = {
"user@${domain}" = { "user@${domain}" = {
hashedPasswordFile = hashPassword "password"; hashedPasswordFile = hashPassword "password";
}; };