From 06cc71c76eb52dc747704a317ac5e175ebdd2ba8 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Thu, 12 Mar 2026 12:58:45 +0100 Subject: [PATCH 1/3] treewide: add language annotations for inline code Instruct editors to correctly highlight and evaluate inline code blocks. --- mail-server/common.nix | 30 +- mail-server/dovecot.nix | 79 ++--- mail-server/users.nix | 79 ++--- tests/clamav.nix | 174 +++++------ tests/external.nix | 619 +++++++++++++++++++++------------------- tests/internal.nix | 1 + tests/ldap.nix | 55 ++-- tests/multiple.nix | 44 +-- 8 files changed, 563 insertions(+), 518 deletions(-) diff --git a/mail-server/common.nix b/mail-server/common.nix index d3de0d9..3ca5c57 100644 --- a/mail-server/common.nix +++ b/mail-server/common.nix @@ -69,21 +69,23 @@ rec { passwordFile, destination, }: - pkgs.writeScript "append-ldap-bind-pwd-in-${name}" '' - #!${pkgs.stdenv.shell} - set -euo pipefail + pkgs.writeScript "append-ldap-bind-pwd-in-${name}" + # bash + '' + #!${pkgs.stdenv.shell} + set -euo pipefail - baseDir=$(dirname ${destination}) - if (! test -d "$baseDir"); then - mkdir -p $baseDir - chmod 755 $baseDir - fi + baseDir=$(dirname ${destination}) + if (! test -d "$baseDir"); then + mkdir -p $baseDir + chmod 755 $baseDir + fi - cat ${file} > ${destination} - echo -n '${prefix}' >> ${destination} - cat ${passwordFile} | tr -d '\n' >> ${destination} - echo -n '${suffix}' >> ${destination} - chmod 600 ${destination} - ''; + cat ${file} > ${destination} + echo -n '${prefix}' >> ${destination} + cat ${passwordFile} | tr -d '\n' >> ${destination} + echo -n '${suffix}' >> ${destination} + chmod 600 ${destination} + ''; } diff --git a/mail-server/dovecot.nix b/mail-server/dovecot.nix index c757005..2780e66 100644 --- a/mail-server/dovecot.nix +++ b/mail-server/dovecot.nix @@ -96,50 +96,53 @@ let destination = ldapConfFile; }; - genPasswdScript = pkgs.writeScript "generate-password-file" '' - #!${pkgs.stdenv.shell} + genPasswdScript = + pkgs.writeScript "generate-password-file" + # bash + '' + #!${pkgs.stdenv.shell} - set -euo pipefail + set -euo pipefail - if (! test -d "${passwdDir}"); then - mkdir "${passwdDir}" - chmod 755 "${passwdDir}" - fi + if (! test -d "${passwdDir}"); then + mkdir "${passwdDir}" + chmod 755 "${passwdDir}" + fi - # Prevent world-readable password files, even temporarily. - umask 077 + # Prevent world-readable password files, even temporarily. + umask 077 - for f in ${ - builtins.toString (lib.mapAttrsToList (name: _: passwordFiles."${name}") cfg.loginAccounts) - }; do - if [ ! -f "$f" ]; then - echo "Expected password hash file $f does not exist!" - exit 1 - fi - done + for f in ${ + builtins.toString (lib.mapAttrsToList (name: _: passwordFiles."${name}") cfg.loginAccounts) + }; do + if [ ! -f "$f" ]; then + echo "Expected password hash file $f does not exist!" + exit 1 + fi + done - cat < ${passwdFile} - ${lib.concatStringsSep "\n" ( - lib.mapAttrsToList ( - name: _: - if lib.elem name accountsWithPlaintextPasswordFiles then - "${name}:${"$(sed -n '1{p;p;q}' ${passwordFiles."${name}"} | ${lib.getExe' pkgs.dovecot "doveadm"} pw)"}::::::" - else - "${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}::::::" - ) cfg.loginAccounts - )} - EOF + cat < ${passwdFile} + ${lib.concatStringsSep "\n" ( + lib.mapAttrsToList ( + name: _: + if lib.elem name accountsWithPlaintextPasswordFiles then + "${name}:${"$(sed -n '1{p;p;q}' ${passwordFiles."${name}"} | ${lib.getExe' pkgs.dovecot "doveadm"} pw)"}::::::" + else + "${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}::::::" + ) cfg.loginAccounts + )} + EOF - cat < ${userdbFile} - ${lib.concatStringsSep "\n" ( - lib.mapAttrsToList ( - name: value: - "${name}:::::::" - + lib.optionalString (value.quota != null) "userdb_quota_rule=*:storage=${value.quota}" - ) cfg.loginAccounts - )} - EOF - ''; + cat < ${userdbFile} + ${lib.concatStringsSep "\n" ( + lib.mapAttrsToList ( + name: value: + "${name}:::::::" + + lib.optionalString (value.quota != null) "userdb_quota_rule=*:storage=${value.quota}" + ) cfg.loginAccounts + )} + EOF + ''; junkMailboxes = builtins.attrNames ( lib.filterAttrs (_: v: v ? "specialUse" && v.specialUse == "Junk") cfg.mailboxes diff --git a/mail-server/users.nix b/mail-server/users.nix index 93a543d..9863066 100644 --- a/mail-server/users.nix +++ b/mail-server/users.nix @@ -43,48 +43,51 @@ let group = vmailGroupName; }; - virtualMailUsersActivationScript = pkgs.writeScript "activate-virtual-mail-users" '' - #!${pkgs.stdenv.shell} + virtualMailUsersActivationScript = + pkgs.writeScript "activate-virtual-mail-users" + # bash + '' + #!${pkgs.stdenv.shell} - set -euo pipefail + set -euo pipefail - # Prevent world-readable paths, even temporarily. - umask 007 + # Prevent world-readable paths, even temporarily. + umask 007 - # Create directory to store user sieve scripts if it doesn't exist - if (! test -d "${sieveDirectory}"); then - mkdir "${sieveDirectory}" - chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}" - chmod 770 "${sieveDirectory}" - fi + # Create directory to store user sieve scripts if it doesn't exist + if (! test -d "${sieveDirectory}"); then + mkdir "${sieveDirectory}" + chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}" + chmod 770 "${sieveDirectory}" + fi - # Copy user's sieve script to the correct location (if it exists). If it - # is null, remove the file. - ${lib.concatMapStringsSep "\n" ( - { name, sieveScript }: - if lib.isString sieveScript then - '' - if (! test -d "${sieveDirectory}/${name}"); then - mkdir -p "${sieveDirectory}/${name}" - chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}" - chmod 770 "${sieveDirectory}/${name}" - fi - cat << 'EOF' > "${sieveDirectory}/${name}/default.sieve" - ${sieveScript} - EOF - chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}/default.sieve" - '' - else - '' - if (test -f "${sieveDirectory}/${name}/default.sieve"); then - rm "${sieveDirectory}/${name}/default.sieve" - fi - if (test -f "${sieveDirectory}/${name}.svbin"); then - rm "${sieveDirectory}/${name}/default.svbin" - fi - '' - ) (map (user: { inherit (user) name sieveScript; }) (lib.attrValues loginAccounts))} - ''; + # Copy user's sieve script to the correct location (if it exists). If it + # is null, remove the file. + ${lib.concatMapStringsSep "\n" ( + { name, sieveScript }: + if lib.isString sieveScript then + '' + if (! test -d "${sieveDirectory}/${name}"); then + mkdir -p "${sieveDirectory}/${name}" + chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}" + chmod 770 "${sieveDirectory}/${name}" + fi + cat << 'EOF' > "${sieveDirectory}/${name}/default.sieve" + ${sieveScript} + EOF + chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}/default.sieve" + '' + else + '' + if (test -f "${sieveDirectory}/${name}/default.sieve"); then + rm "${sieveDirectory}/${name}/default.sieve" + fi + if (test -f "${sieveDirectory}/${name}.svbin"); then + rm "${sieveDirectory}/${name}/default.svbin" + fi + '' + ) (map (user: { inherit (user) name sieveScript; }) (lib.attrValues loginAccounts))} + ''; in { config = lib.mkIf enable { diff --git a/tests/clamav.nix b/tests/clamav.nix index e0cfb43..900dd32 100644 --- a/tests/clamav.nix +++ b/tests/clamav.nix @@ -144,111 +144,117 @@ password user2 ''; }; - "root/virus-email".text = '' - From: User2 - Content-Type: multipart/mixed; - boundary="Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607" - Mime-Version: 1.0 (Mac OS X Mail 11.3 \(3445.6.18\)) - Subject: Testy McTest - Message-Id: <94550DD9-1FF1-4ED1-9F09-8812FF2E59AA@example.com> - Date: Sat, 12 May 2018 14:15:44 +0200 - To: User1 - X-Mailer: Apple Mail (2.3445.6.18) + "root/virus-email".text = + # mail + '' + From: User2 + Content-Type: multipart/mixed; + boundary="Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607" + Mime-Version: 1.0 (Mac OS X Mail 11.3 \(3445.6.18\)) + Subject: Testy McTest + Message-Id: <94550DD9-1FF1-4ED1-9F09-8812FF2E59AA@example.com> + Date: Sat, 12 May 2018 14:15:44 +0200 + To: User1 + X-Mailer: Apple Mail (2.3445.6.18) - --Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607 - Content-Transfer-Encoding: 7bit - Content-Type: text/plain; - charset=us-ascii + --Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607 + Content-Transfer-Encoding: 7bit + Content-Type: text/plain; + charset=us-ascii - Hello + Hello - I have attached a dangerous virus. + I have attached a dangerous virus. - Mfg. - User2 + Mfg. + User2 - --Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607 - Content-Disposition: attachment; - filename=eicar.com.txt - Content-Type: text/plain; - x-unix-mode=0644; - name="eicar.com.txt" - Content-Transfer-Encoding: 7bit + --Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607 + Content-Disposition: attachment; + filename=eicar.com.txt + Content-Type: text/plain; + x-unix-mode=0644; + name="eicar.com.txt" + Content-Transfer-Encoding: 7bit - X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H* - --Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607-- - ''; - "root/safe-email".text = '' - From: User - To: User1 - Cc: - Bcc: - Subject: This is a test Email from user@example2.com to user1 - Reply-To: + X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H* + --Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607-- + ''; + "root/safe-email".text = + # mail + '' + From: User + To: User1 + Cc: + Bcc: + Subject: This is a test Email from user@example2.com to user1 + Reply-To: - Hello User1, + Hello User1, - how are you doing today? + how are you doing today? - XOXO User1 - ''; + XOXO User1 + ''; }; }; }; - testScript = '' - start_all() + testScript = + # python + '' + start_all() - server.wait_for_unit("multi-user.target") - client.wait_for_unit("multi-user.target") + server.wait_for_unit("multi-user.target") + client.wait_for_unit("multi-user.target") - # TODO put this blocking into the systemd units? I am not sure if rspamd already waits for the clamd socket. - server.wait_until_succeeds( - "set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" - ) - server.wait_until_succeeds( - "set +e; timeout 1 nc -U /run/clamav/clamd.ctl < /dev/null; [ $? -eq 124 ]" - ) + # TODO put this blocking into the systemd units? I am not sure if rspamd already waits for the clamd socket. + server.wait_until_succeeds( + "set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" + ) + server.wait_until_succeeds( + "set +e; timeout 1 nc -U /run/clamav/clamd.ctl < /dev/null; [ $? -eq 124 ]" + ) - client.execute("cp -p /etc/root/.* ~/") - client.succeed("mkdir -p ~/mail") - client.succeed("ls -la ~/ >&2") - client.succeed("cat ~/.fetchmailrc >&2") - client.succeed("cat ~/.procmailrc >&2") - client.succeed("cat ~/.msmtprc >&2") + client.execute("cp -p /etc/root/.* ~/") + client.succeed("mkdir -p ~/mail") + client.succeed("ls -la ~/ >&2") + client.succeed("cat ~/.fetchmailrc >&2") + client.succeed("cat ~/.procmailrc >&2") + client.succeed("cat ~/.msmtprc >&2") - # fetchmail returns EXIT_CODE 1 when no new mail - client.succeed("fetchmail --nosslcertck -v || [ $? -eq 1 ] >&2") + # fetchmail returns EXIT_CODE 1 when no new mail + client.succeed("fetchmail --nosslcertck -v || [ $? -eq 1 ] >&2") - # Verify that mail can be sent and received before testing virus scanner - client.execute("rm ~/mail/*") - client.succeed("msmtp -a user2 user1@example.com < /etc/root/safe-email >&2") - # give the mail server some time to process the mail - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - client.execute("rm ~/mail/*") - # fetchmail returns EXIT_CODE 0 when it retrieves mail - client.succeed("fetchmail --nosslcertck -v >&2") - client.execute("rm ~/mail/*") + # Verify that mail can be sent and received before testing virus scanner + client.execute("rm ~/mail/*") + client.succeed("msmtp -a user2 user1@example.com < /etc/root/safe-email >&2") + # give the mail server some time to process the mail + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + client.execute("rm ~/mail/*") + # fetchmail returns EXIT_CODE 0 when it retrieves mail + client.succeed("fetchmail --nosslcertck -v >&2") + client.execute("rm ~/mail/*") - with subtest("virus scan file"): - server.succeed( - 'set +o pipefail; clamdscan $(readlink -f /etc/root/eicar.com.txt) | grep "Txt\\.Malware\\.Agent-1787597 FOUND" >&2' - ) + with subtest("virus scan file"): + server.succeed( + 'set +o pipefail; clamdscan $(readlink -f /etc/root/eicar.com.txt) | grep "Txt\\.Malware\\.Agent-1787597 FOUND" >&2' + ) - with subtest("virus scan email"): - client.succeed( - 'set +o pipefail; msmtp -a user2 user1@example.com < /etc/root/virus-email 2>&1 | tee /dev/stderr | grep "server message: 554 5\\.7\\.1" >&2' - ) - server.succeed("journalctl -u rspamd | grep -i eicar") - # give the mail server some time to process the mail - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + with subtest("virus scan email"): + client.succeed( + 'set +o pipefail; msmtp -a user2 user1@example.com < /etc/root/virus-email 2>&1 | tee /dev/stderr | grep "server message: 554 5\\.7\\.1" >&2' + ) + server.succeed("journalctl -u rspamd | grep -i eicar") + # give the mail server some time to process the mail + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - with subtest("no warnings or errors"): - server.fail("journalctl -u postfix | grep -i error >&2") - server.fail("journalctl -u postfix | grep -i warning >&2") - server.fail("journalctl -u dovecot | grep -i error >&2") - server.fail("journalctl -u dovecot | grep -i warning >&2") - ''; + with subtest("no warnings or errors"): + server.fail("journalctl -u postfix | grep -i error >&2") + server.fail("journalctl -u postfix | grep -i warning >&2") + server.fail("journalctl -u dovecot | grep -i error >&2") + server.fail("journalctl -u dovecot | grep -i warning >&2") + ''; } diff --git a/tests/external.nix b/tests/external.nix index e4ff571..fd2c267 100644 --- a/tests/external.nix +++ b/tests/external.nix @@ -121,80 +121,89 @@ echo grep '^Message-ID:.*@mail.example.com>$' "$@" >&2 exec grep '^Message-ID:.*@mail.example.com>$' "$@" ''; - test-imap-spam = pkgs.writeScriptBin "imap-mark-spam" '' - #!${pkgs.python3.interpreter} - import imaplib + test-imap-spam = + pkgs.writeScriptBin "imap-mark-spam" + # python + '' + #!${pkgs.python3.interpreter} + import imaplib - with imaplib.IMAP4_SSL('${serverIP}') as imap: - imap.login('user1@example.com', 'user1') - imap.select() - status, [response] = imap.search(None, 'ALL') - msg_ids = response.decode("utf-8").split(' ') - print(msg_ids) - assert status == 'OK' - assert len(msg_ids) == 1 + with imaplib.IMAP4_SSL('${serverIP}') as imap: + imap.login('user1@example.com', 'user1') + imap.select() + status, [response] = imap.search(None, 'ALL') + msg_ids = response.decode("utf-8").split(' ') + print(msg_ids) + assert status == 'OK' + assert len(msg_ids) == 1 - imap.copy(','.join(msg_ids), 'Junk') - for num in msg_ids: - imap.store(num, '+FLAGS', '\\Deleted') - imap.expunge() + imap.copy(','.join(msg_ids), 'Junk') + for num in msg_ids: + imap.store(num, '+FLAGS', '\\Deleted') + imap.expunge() - imap.select('Junk') - status, [response] = imap.search(None, 'ALL') - msg_ids = response.decode("utf-8").split(' ') - print(msg_ids) - assert status == 'OK' - assert len(msg_ids) == 1 + imap.select('Junk') + status, [response] = imap.search(None, 'ALL') + msg_ids = response.decode("utf-8").split(' ') + print(msg_ids) + assert status == 'OK' + assert len(msg_ids) == 1 - imap.close() - ''; - test-imap-ham = pkgs.writeScriptBin "imap-mark-ham" '' - #!${pkgs.python3.interpreter} - import imaplib + imap.close() + ''; + test-imap-ham = + pkgs.writeScriptBin "imap-mark-ham" + # python + '' + #!${pkgs.python3.interpreter} + import imaplib - with imaplib.IMAP4_SSL('${serverIP}') as imap: - imap.login('user1@example.com', 'user1') - imap.select('Junk') - status, [response] = imap.search(None, 'ALL') - msg_ids = response.decode("utf-8").split(' ') - print(msg_ids) - assert status == 'OK' - assert len(msg_ids) == 1 + with imaplib.IMAP4_SSL('${serverIP}') as imap: + imap.login('user1@example.com', 'user1') + imap.select('Junk') + status, [response] = imap.search(None, 'ALL') + msg_ids = response.decode("utf-8").split(' ') + print(msg_ids) + assert status == 'OK' + assert len(msg_ids) == 1 - imap.copy(','.join(msg_ids), 'INBOX') - for num in msg_ids: - imap.store(num, '+FLAGS', '\\Deleted') - imap.expunge() + imap.copy(','.join(msg_ids), 'INBOX') + for num in msg_ids: + imap.store(num, '+FLAGS', '\\Deleted') + imap.expunge() - imap.select('INBOX') - status, [response] = imap.search(None, 'ALL') - msg_ids = response.decode("utf-8").split(' ') - print(msg_ids) - assert status == 'OK' - assert len(msg_ids) == 1 + imap.select('INBOX') + status, [response] = imap.search(None, 'ALL') + msg_ids = response.decode("utf-8").split(' ') + print(msg_ids) + assert status == 'OK' + assert len(msg_ids) == 1 - imap.close() - ''; - search = pkgs.writeScriptBin "search" '' - #!${pkgs.python3.interpreter} - import imaplib - import sys + imap.close() + ''; + search = + pkgs.writeScriptBin "search" + # python + '' + #!${pkgs.python3.interpreter} + import imaplib + import sys - [_, mailbox, needle] = sys.argv + [_, mailbox, needle] = sys.argv - with imaplib.IMAP4_SSL('${serverIP}') as imap: - imap.login('user1@example.com', 'user1') - imap.select(mailbox) - status, [response] = imap.search(None, 'BODY', repr(needle)) - msg_ids = [ i for i in response.decode("utf-8").split(' ') if i ] - print(msg_ids) - assert status == 'OK' - assert len(msg_ids) == 1 - status, response = imap.fetch(msg_ids[0], '(RFC822)') - assert status == "OK" - assert needle in repr(response) - imap.close() - ''; + with imaplib.IMAP4_SSL('${serverIP}') as imap: + imap.login('user1@example.com', 'user1') + imap.select(mailbox) + status, [response] = imap.search(None, 'BODY', repr(needle)) + msg_ids = [ i for i in response.decode("utf-8").split(' ') if i ] + print(msg_ids) + assert status == 'OK' + assert len(msg_ids) == 1 + status, response = imap.fetch(msg_ids[0], '(RFC822)') + assert status == "OK" + assert needle in repr(response) + imap.close() + ''; in { imports = [ @@ -269,282 +278,298 @@ password user1 ''; }; - "root/email1".text = '' - Message-ID: <12345qwerty@host.local.network> - From: User2 - To: User1 - Cc: - Bcc: - Subject: This is a test Email from user2 to user1 - Reply-To: + "root/email1".text = + # mail + '' + Message-ID: <12345qwerty@host.local.network> + From: User2 + To: User1 + Cc: + Bcc: + Subject: This is a test Email from user2 to user1 + Reply-To: - Hello User1, + Hello User1, - how are you doing today? - ''; - "root/email2".text = '' - Message-ID: <232323abc@host.local.network> - From: User - To: User1 - Cc: - Bcc: - Subject: This is a test Email from user@example2.com to user1 - Reply-To: + how are you doing today? + ''; + "root/email2".text = + # mail + '' + Message-ID: <232323abc@host.local.network> + From: User + To: User1 + Cc: + Bcc: + Subject: This is a test Email from user@example2.com to user1 + Reply-To: - Hello User1, + Hello User1, - how are you doing today? + how are you doing today? - XOXO User1 - ''; - "root/email3".text = '' - Message-ID: - From: Postmaster - To: Chuck - Cc: - Bcc: - Subject: This is a test Email from postmaster@example.com to chuck - Reply-To: + XOXO User1 + ''; + "root/email3".text = + # mail + '' + Message-ID: + From: Postmaster + To: Chuck + Cc: + Bcc: + Subject: This is a test Email from postmaster@example.com to chuck + Reply-To: - Hello Chuck, + Hello Chuck, - I think I may have misconfigured the mail server - XOXO Postmaster - ''; - "root/email4".text = '' - Message-ID: - From: Single Alias - To: User1 - Cc: - Bcc: - Subject: This is a test Email from single-alias@example.com to user1 - Reply-To: + I think I may have misconfigured the mail server + XOXO Postmaster + ''; + "root/email4".text = + # mail + '' + Message-ID: + From: Single Alias + To: User1 + Cc: + Bcc: + Subject: This is a test Email from single-alias@example.com to user1 + Reply-To: - Hello User1, + Hello User1, - how are you doing today? + how are you doing today? - XOXO User1 aka Single Alias - ''; - "root/email5".text = '' - Message-ID: <789asdf@host.local.network> - From: User2 - To: Multi Alias - Cc: - Bcc: - Subject: This is a test Email from user2@example.com to multi-alias - Reply-To: + XOXO User1 aka Single Alias + ''; + "root/email5".text = + # mail + '' + Message-ID: <789asdf@host.local.network> + From: User2 + To: Multi Alias + Cc: + Bcc: + Subject: This is a test Email from user2@example.com to multi-alias + Reply-To: - Hello Multi Alias, + Hello Multi Alias, - how are we doing today? + how are we doing today? - XOXO User1 - ''; - "root/email6".text = '' - Message-ID: <123457qwerty@host.local.network> - From: User2 - To: User1 - Cc: - Bcc: - Subject: This is a test Email from user2 to user1 - Reply-To: + XOXO User1 + ''; + "root/email6".text = + # mail + '' + Message-ID: <123457qwerty@host.local.network> + From: User2 + To: User1 + Cc: + Bcc: + Subject: This is a test Email from user2 to user1 + Reply-To: - Hello User1, + Hello User1, - this email contains the needle: - 576a4565b70f5a4c1a0925cabdb587a6 - ''; - "root/email7".text = '' - Message-ID: <1234578qwerty@host.local.network> - From: User2 - To: User1 - Cc: - Bcc: - Subject: This is a test Email from user2 to user1 - Reply-To: + this email contains the needle: + 576a4565b70f5a4c1a0925cabdb587a6 + ''; + "root/email7".text = + # mail + '' + Message-ID: <1234578qwerty@host.local.network> + From: User2 + To: User1 + Cc: + Bcc: + Subject: This is a test Email from user2 to user1 + Reply-To: - Hello User1, + Hello User1, - this email does not contain the needle :( - ''; + this email does not contain the needle :( + ''; }; }; }; - testScript = '' - start_all() + testScript = + # python + '' + start_all() - server.wait_for_unit("multi-user.target") - client.wait_for_unit("multi-user.target") + server.wait_for_unit("multi-user.target") + client.wait_for_unit("multi-user.target") - # TODO put this blocking into the systemd units? - server.wait_until_succeeds( - "set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" - ) + # TODO put this blocking into the systemd units? + server.wait_until_succeeds( + "set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" + ) - server.succeed("rspamadm dkim_keygen > /run/rspamd/dkim-test.key") - server.succeed("chown rspamd: /run/rspamd/dkim-test.key") + server.succeed("rspamadm dkim_keygen > /run/rspamd/dkim-test.key") + server.succeed("chown rspamd: /run/rspamd/dkim-test.key") - client.execute("cp -p /etc/root/.* ~/") - client.succeed("mkdir -p ~/mail") - client.succeed("ls -la ~/ >&2") - client.succeed("cat ~/.fetchmailrc >&2") - client.succeed("cat ~/.procmailrc >&2") - client.succeed("cat ~/.msmtprc >&2") + client.execute("cp -p /etc/root/.* ~/") + client.succeed("mkdir -p ~/mail") + client.succeed("ls -la ~/ >&2") + client.succeed("cat ~/.fetchmailrc >&2") + client.succeed("cat ~/.procmailrc >&2") + client.succeed("cat ~/.msmtprc >&2") - with subtest("imap retrieving mail"): - # fetchmail returns EXIT_CODE 1 when no new mail - client.succeed("fetchmail --nosslcertck -v || [ $? -eq 1 ] >&2") + with subtest("imap retrieving mail"): + # fetchmail returns EXIT_CODE 1 when no new mail + client.succeed("fetchmail --nosslcertck -v || [ $? -eq 1 ] >&2") - with subtest("submission port send mail"): - # send email from user2 to user1 - client.succeed( - "msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email1 >&2" - ) - # give the mail server some time to process the mail - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + with subtest("submission port send mail"): + # send email from user2 to user1 + client.succeed( + "msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email1 >&2" + ) + # give the mail server some time to process the mail + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - with subtest("imap retrieving mail 2"): - client.execute("rm ~/mail/*") - # fetchmail returns EXIT_CODE 0 when it retrieves mail - client.succeed("fetchmail --nosslcertck -v >&2") + with subtest("imap retrieving mail 2"): + client.execute("rm ~/mail/*") + # fetchmail returns EXIT_CODE 0 when it retrieves mail + client.succeed("fetchmail --nosslcertck -v >&2") - with subtest("remove sensitive information on submission port"): - client.succeed("cat ~/mail/* >&2") - ## make sure our IP is _not_ in the email header - client.fail("grep-ip ~/mail/*") - client.succeed("check-mail-id ~/mail/*") + with subtest("remove sensitive information on submission port"): + client.succeed("cat ~/mail/* >&2") + ## make sure our IP is _not_ in the email header + client.fail("grep-ip ~/mail/*") + client.succeed("check-mail-id ~/mail/*") - with subtest("have correct fqdn as sender"): - client.succeed("grep 'Received: from mail.example.com' ~/mail/*") + with subtest("have correct fqdn as sender"): + client.succeed("grep 'Received: from mail.example.com' ~/mail/*") - with subtest("dkim has user-specified size"): - server.succeed( - "openssl rsa -in /var/dkim/example2.com.dkim-rsa.key -text -noout | grep 'Private-Key: (1535 bit'" - ) + with subtest("dkim has user-specified size"): + server.succeed( + "openssl rsa -in /var/dkim/example2.com.dkim-rsa.key -text -noout | grep 'Private-Key: (1535 bit'" + ) - with subtest("dkim signing, multiple domains"): - client.execute("rm ~/mail/*") - # send email from user2 to user1 - client.succeed( - "msmtp -a test2 --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email2 >&2" - ) - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - # fetchmail returns EXIT_CODE 0 when it retrieves mail - client.succeed("fetchmail --nosslcertck -v") - client.succeed("cat ~/mail/* >&2") - # make sure it is dkim signed - client.succeed("grep 's=dkim-rsa' ~/mail/*") - client.succeed("grep 's=dkim-ed25519' ~/mail/*") - client.succeed("grep 's=dkim-file' ~/mail/*") + with subtest("dkim signing, multiple domains"): + client.execute("rm ~/mail/*") + # send email from user2 to user1 + client.succeed( + "msmtp -a test2 --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email2 >&2" + ) + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + # fetchmail returns EXIT_CODE 0 when it retrieves mail + client.succeed("fetchmail --nosslcertck -v") + client.succeed("cat ~/mail/* >&2") + # make sure it is dkim signed + client.succeed("grep 's=dkim-rsa' ~/mail/*") + client.succeed("grep 's=dkim-ed25519' ~/mail/*") + client.succeed("grep 's=dkim-file' ~/mail/*") - with subtest("aliases"): - client.execute("rm ~/mail/*") - # send email from chuck to postmaster - client.succeed( - "msmtp -a test3 --tls=on --tls-certcheck=off --auth=on postmaster@example.com < /etc/root/email2 >&2" - ) - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - # fetchmail returns EXIT_CODE 0 when it retrieves mail - client.succeed("fetchmail --nosslcertck -v") + with subtest("aliases"): + client.execute("rm ~/mail/*") + # send email from chuck to postmaster + client.succeed( + "msmtp -a test3 --tls=on --tls-certcheck=off --auth=on postmaster@example.com < /etc/root/email2 >&2" + ) + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + # fetchmail returns EXIT_CODE 0 when it retrieves mail + client.succeed("fetchmail --nosslcertck -v") - with subtest("domain catch-all"): - client.execute("rm ~/mail/*") - # send email from chuck to non-existent account - client.succeed( - "msmtp -a test3 --tls=on --tls-certcheck=off --auth=on lol@example.com < /etc/root/email2 >&2" - ) - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - # fetchmail returns EXIT_CODE 0 when it retrieves mail - client.succeed("fetchmail --nosslcertck -v") + with subtest("domain catch-all"): + client.execute("rm ~/mail/*") + # send email from chuck to non-existent account + client.succeed( + "msmtp -a test3 --tls=on --tls-certcheck=off --auth=on lol@example.com < /etc/root/email2 >&2" + ) + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + # fetchmail returns EXIT_CODE 0 when it retrieves mail + client.succeed("fetchmail --nosslcertck -v") - client.execute("rm ~/mail/*") - # send email from user1 to chuck - client.succeed( - "msmtp -a test4 --tls=on --tls-certcheck=off --auth=on chuck@example.com < /etc/root/email2 >&2" - ) - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - # fetchmail returns EXIT_CODE 1 when no new mail - # if this succeeds, it means that user1 received the mail that was intended for chuck. - client.fail("fetchmail --nosslcertck -v") + client.execute("rm ~/mail/*") + # send email from user1 to chuck + client.succeed( + "msmtp -a test4 --tls=on --tls-certcheck=off --auth=on chuck@example.com < /etc/root/email2 >&2" + ) + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + # fetchmail returns EXIT_CODE 1 when no new mail + # if this succeeds, it means that user1 received the mail that was intended for chuck. + client.fail("fetchmail --nosslcertck -v") - with subtest("extraVirtualAliases"): - client.execute("rm ~/mail/*") - # send email from single-alias to user1 - client.succeed( - "msmtp -a test5 --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email4 >&2" - ) - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - # fetchmail returns EXIT_CODE 0 when it retrieves mail - client.succeed("fetchmail --nosslcertck -v") + with subtest("extraVirtualAliases"): + client.execute("rm ~/mail/*") + # send email from single-alias to user1 + client.succeed( + "msmtp -a test5 --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email4 >&2" + ) + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + # fetchmail returns EXIT_CODE 0 when it retrieves mail + client.succeed("fetchmail --nosslcertck -v") - client.execute("rm ~/mail/*") - # send email from user1 to multi-alias (user{1,2}@example.com) - client.succeed( - "msmtp -a test --tls=on --tls-certcheck=off --auth=on multi-alias@example.com < /etc/root/email5 >&2" - ) - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - # fetchmail returns EXIT_CODE 0 when it retrieves mail - client.succeed("fetchmail --nosslcertck -v") + client.execute("rm ~/mail/*") + # send email from user1 to multi-alias (user{1,2}@example.com) + client.succeed( + "msmtp -a test --tls=on --tls-certcheck=off --auth=on multi-alias@example.com < /etc/root/email5 >&2" + ) + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + # fetchmail returns EXIT_CODE 0 when it retrieves mail + client.succeed("fetchmail --nosslcertck -v") - with subtest("quota"): - client.execute("rm ~/mail/*") - client.execute("mv ~/.fetchmailRcLowQuota ~/.fetchmailrc") + with subtest("quota"): + client.execute("rm ~/mail/*") + client.execute("mv ~/.fetchmailRcLowQuota ~/.fetchmailrc") - client.succeed( - "msmtp -a test3 --tls=on --tls-certcheck=off --auth=on lowquota@example.com < /etc/root/email2 >&2" - ) - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - # fetchmail returns EXIT_CODE 0 when it retrieves mail - client.fail("fetchmail --nosslcertck -v") + client.succeed( + "msmtp -a test3 --tls=on --tls-certcheck=off --auth=on lowquota@example.com < /etc/root/email2 >&2" + ) + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + # fetchmail returns EXIT_CODE 0 when it retrieves mail + client.fail("fetchmail --nosslcertck -v") - with subtest("imap sieve junk trainer"): - # send email from user2 to user1 - client.succeed( - "msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email1 >&2" - ) - # give the mail server some time to process the mail - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + with subtest("imap sieve junk trainer"): + # send email from user2 to user1 + client.succeed( + "msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email1 >&2" + ) + # give the mail server some time to process the mail + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - client.succeed("imap-mark-spam >&2") - server.wait_until_succeeds("journalctl -u dovecot | grep -i rspamd-learn-spam.sh >&2") - client.succeed("imap-mark-ham >&2") - server.wait_until_succeeds("journalctl -u dovecot | grep -i rspamd-learn-ham.sh >&2") + client.succeed("imap-mark-spam >&2") + server.wait_until_succeeds("journalctl -u dovecot | grep -i rspamd-learn-spam.sh >&2") + client.succeed("imap-mark-ham >&2") + server.wait_until_succeeds("journalctl -u dovecot | grep -i rspamd-learn-ham.sh >&2") - with subtest("full text search and indexation"): - # send 2 email from user2 to user1 - client.succeed( - "msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email6 >&2" - ) - client.succeed( - "msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email7 >&2" - ) - # give the mail server some time to process the mail - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + with subtest("full text search and indexation"): + # send 2 email from user2 to user1 + client.succeed( + "msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email6 >&2" + ) + client.succeed( + "msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email7 >&2" + ) + # give the mail server some time to process the mail + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - # should find exactly one email containing this - client.succeed("search INBOX 576a4565b70f5a4c1a0925cabdb587a6 >&2") - # should fail because this folder is not indexed - client.fail("search Junk a >&2") - # check that search really goes through the indexer - server.succeed("journalctl -u dovecot | grep 'fts-flatcurve(INBOX): Query ' >&2") - # check that Junk is not indexed - server.fail("journalctl -u dovecot | grep 'fts-flatcurve(JUNK): Indexing ' >&2") + # should find exactly one email containing this + client.succeed("search INBOX 576a4565b70f5a4c1a0925cabdb587a6 >&2") + # should fail because this folder is not indexed + client.fail("search Junk a >&2") + # check that search really goes through the indexer + server.succeed("journalctl -u dovecot | grep 'fts-flatcurve(INBOX): Query ' >&2") + # check that Junk is not indexed + server.fail("journalctl -u dovecot | grep 'fts-flatcurve(JUNK): Indexing ' >&2") - with subtest("dmarc reporting"): - server.systemctl("start rspamd-dmarc-reporter.service") + with subtest("dmarc reporting"): + server.systemctl("start rspamd-dmarc-reporter.service") - with subtest("no warnings or errors"): - server.fail("journalctl -u postfix | grep -i error >&2") - server.fail("journalctl -u postfix | grep -i warning >&2") - server.fail("journalctl -u dovecot | grep -v 'imap-login: Debug: SSL error: Connection closed' | grep -i error >&2") - # harmless ? https://dovecot.org/pipermail/dovecot/2020-August/119575.html - server.fail( - "journalctl -u dovecot | \ - grep -v 'Expunged message reappeared, giving a new UID' | \ - grep -v 'Time moved forwards' | \ - grep -i warning >&2" - ) - ''; + with subtest("no warnings or errors"): + server.fail("journalctl -u postfix | grep -i error >&2") + server.fail("journalctl -u postfix | grep -i warning >&2") + server.fail("journalctl -u dovecot | grep -v 'imap-login: Debug: SSL error: Connection closed' | grep -i error >&2") + # harmless ? https://dovecot.org/pipermail/dovecot/2020-August/119575.html + server.fail( + "journalctl -u dovecot | \ + grep -v 'Expunged message reappeared, giving a new UID' | \ + grep -v 'Time moved forwards' | \ + grep -i warning >&2" + ) + ''; } diff --git a/tests/internal.nix b/tests/internal.nix index 36bc4b3..a3cbfb8 100644 --- a/tests/internal.nix +++ b/tests/internal.nix @@ -122,6 +122,7 @@ in nodes, ... }: + # python '' machine.start() machine.wait_for_unit("multi-user.target") diff --git a/tests/ldap.nix b/tests/ldap.nix index c150567..d8f5af3 100644 --- a/tests/ldap.nix +++ b/tests/ldap.nix @@ -53,36 +53,38 @@ in }; }; }; - declarativeContents."dc=example" = '' - dn: dc=example - objectClass: domain - dc: example + declarativeContents."dc=example" = + #ldif + '' + dn: dc=example + objectClass: domain + dc: example - dn: cn=mail,dc=example - objectClass: organizationalRole - objectClass: simpleSecurityObject - objectClass: top - cn: mail - userPassword: ${bindPassword} + dn: cn=mail,dc=example + objectClass: organizationalRole + objectClass: simpleSecurityObject + objectClass: top + cn: mail + userPassword: ${bindPassword} - dn: ou=users,dc=example - objectClass: organizationalUnit - ou: users + dn: ou=users,dc=example + objectClass: organizationalUnit + ou: users - dn: cn=alice,ou=users,dc=example - objectClass: inetOrgPerson - cn: alice - sn: Foo - mail: alice@example.com - userPassword: ${alicePassword} + dn: cn=alice,ou=users,dc=example + objectClass: inetOrgPerson + cn: alice + sn: Foo + mail: alice@example.com + userPassword: ${alicePassword} - dn: cn=bob,ou=users,dc=example - objectClass: inetOrgPerson - cn: bob - sn: Bar - mail: bob@example.com - userPassword: ${bobPassword} - ''; + dn: cn=bob,ou=users,dc=example + objectClass: inetOrgPerson + cn: bob + sn: Bar + mail: bob@example.com + userPassword: ${bobPassword} + ''; }; mailserver = { @@ -121,6 +123,7 @@ in nodes, ... }: + # python '' import sys import re diff --git a/tests/multiple.nix b/tests/multiple.nix index 19b7aee..3f9b5db 100644 --- a/tests/multiple.nix +++ b/tests/multiple.nix @@ -90,29 +90,31 @@ in ]; }; }; - testScript = '' - start_all() + testScript = + # python + '' + start_all() - for domain in [domain1, domain2]: - domain.wait_for_unit("multi-user.target") - domain.wait_for_unit("dovecot.service") + for domain in [domain1, domain2]: + domain.wait_for_unit("multi-user.target") + domain.wait_for_unit("dovecot.service") - # TODO put this blocking into the systemd units? - domain1.wait_until_succeeds( - "set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" - ) - domain2.wait_until_succeeds( - "set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" - ) + # TODO put this blocking into the systemd units? + domain1.wait_until_succeeds( + "set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" + ) + domain2.wait_until_succeeds( + "set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" + ) - # user@domain1.com sends a mail to user@domain2.com via explicit TLS - client.succeed( - "mail-check send-and-read --smtp-port 587 --smtp-starttls --smtp-host domain1 --from-addr user@domain1.com --imap-host domain2 --to-addr user@domain2.com --src-password-file ${password} --dst-password-file ${password} --ignore-dkim-spf" - ) + # user@domain1.com sends a mail to user@domain2.com via explicit TLS + client.succeed( + "mail-check send-and-read --smtp-port 587 --smtp-starttls --smtp-host domain1 --from-addr user@domain1.com --imap-host domain2 --to-addr user@domain2.com --src-password-file ${password} --dst-password-file ${password} --ignore-dkim-spf" + ) - # Send a mail to the address forwarded via implicit TLS and check it is in the recipient mailbox - client.succeed( - "mail-check send-and-read --smtp-port 465 --smtp-ssl --smtp-host domain1 --from-addr user@domain1.com --imap-host domain2 --to-addr non-local@domain1.com --imap-username user@domain2.com --src-password-file ${password} --dst-password-file ${password} --ignore-dkim-spf" - ) - ''; + # Send a mail to the address forwarded via implicit TLS and check it is in the recipient mailbox + client.succeed( + "mail-check send-and-read --smtp-port 465 --smtp-ssl --smtp-host domain1 --from-addr user@domain1.com --imap-host domain2 --to-addr non-local@domain1.com --imap-username user@domain2.com --src-password-file ${password} --dst-password-file ${password} --ignore-dkim-spf" + ) + ''; } From 271e6e54fd25b9765dec179c853e7856487d0837 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Thu, 12 Mar 2026 13:21:51 +0100 Subject: [PATCH 2/3] Reorganize imports With the growing number of option refactors centralize the module import within the mail-server directory. Also group deprecations by release, so we can eventually yank them in bulk after a while. --- default.nix | 34 ++++++++++++++-------------------- mail-server/default.nix | 17 +++++++++++++++++ 2 files changed, 31 insertions(+), 20 deletions(-) create mode 100644 mail-server/default.nix diff --git a/default.nix b/default.nix index a56cf84..5e54952 100644 --- a/default.nix +++ b/default.nix @@ -1524,6 +1524,9 @@ in }; imports = [ + ./mail-server + + # NixOS 25.05 (mkRemovedOptionModule [ "mailserver" "fullTextSearch" "maintenance" "enable" ] '' This option is not needed for fts-flatcurve '') @@ -1549,19 +1552,6 @@ in (mkRemovedOptionModule [ "mailserver" "rebootAfterKernelUpgrade" "method" ] '' Use `system.autoUpgrade` instead. '') - ./mail-server/assertions.nix - ./mail-server/borgbackup.nix - ./mail-server/rsnapshot.nix - ./mail-server/clamav.nix - ./mail-server/monit.nix - ./mail-server/users.nix - ./mail-server/environment.nix - ./mail-server/networking.nix - ./mail-server/systemd.nix - ./mail-server/dovecot.nix - ./mail-server/postfix.nix - ./mail-server/rspamd.nix - ./mail-server/kresd.nix (mkRemovedOptionModule [ "mailserver" "policydSPFExtraConfig" ] '' SPF checking has been migrated to Rspamd, which makes this config redundant. Please look into the rspamd config to migrate your settings. It may be that they are redundant and are already configured in rspamd like for skip_addresses. @@ -1575,13 +1565,8 @@ in (mkRemovedOptionModule [ "mailserver" "smtpdForbidBareNewline" ] '' The workaround for the SMTP Smuggling attack is default enabled in Postfix >3.9. Use `services.postfix.config.smtpd_forbid_bare_newline` if you need to deviate from its default. '') - (mkRenamedOptionModule [ "mailserver" "dkimSigning" ] [ "mailserver" "dkim" "enable" ]) - (mkRenamedOptionModule [ "mailserver" "dkimKeyDirectory" ] [ "mailserver" "dkim" "keyDirectory" ]) - (mkRenamedOptionModule - [ "mailserver" "dkimSelector" ] - [ "mailserver" "dkim" "defaults" "selector" ] - ) - (mkRenamedOptionModule [ "mailserver" "dkimKeyType" ] [ "mailserver" "dkim" "defaults" "keyType" ]) + + # NixOS 25.11 (mkRenamedOptionModule [ "mailserver" "dmarcReporting" "domain" ] [ "mailserver" "systemDomain" ]) (mkRenamedOptionModule [ "mailserver" "dmarcReporting" "organizationName" ] @@ -1597,6 +1582,7 @@ in The name in the `FROM` field for DMARC report now uses the `mailserver.systemName`. '') + # NixOS 26.05 (mkRemovedOptionModule [ "mailserver" "certificateDomains" ] '' Configure `security.acme.certs.''${config.mailserver.fqdn}.extraDomains` instead. '') @@ -1607,5 +1593,13 @@ in (mkRenamedOptionModule [ "mailserver" "acmeCertificateName" ] [ "mailserver" "x509" "useACMEHost" ]) (mkRenamedOptionModule [ "mailserver" "certificateFile" ] [ "mailserver" "x509" "certificateFile" ]) (mkRenamedOptionModule [ "mailserver" "keyFile" ] [ "mailserver" "x509" "privateKeyFile" ]) + + (mkRenamedOptionModule [ "mailserver" "dkimSigning" ] [ "mailserver" "dkim" "enable" ]) + (mkRenamedOptionModule [ "mailserver" "dkimKeyDirectory" ] [ "mailserver" "dkim" "keyDirectory" ]) + (mkRenamedOptionModule + [ "mailserver" "dkimSelector" ] + [ "mailserver" "dkim" "defaults" "selector" ] + ) + (mkRenamedOptionModule [ "mailserver" "dkimKeyType" ] [ "mailserver" "dkim" "defaults" "keyType" ]) ]; } diff --git a/mail-server/default.nix b/mail-server/default.nix new file mode 100644 index 0000000..94f0a10 --- /dev/null +++ b/mail-server/default.nix @@ -0,0 +1,17 @@ +{ + imports = [ + ./assertions.nix + ./borgbackup.nix + ./rsnapshot.nix + ./clamav.nix + ./monit.nix + ./users.nix + ./environment.nix + ./networking.nix + ./systemd.nix + ./dovecot.nix + ./postfix.nix + ./rspamd.nix + ./kresd.nix + ]; +} From fdcb28e97eb4f302dfb21fa9eb84f895aa549fb9 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Thu, 12 Mar 2026 13:43:09 +0100 Subject: [PATCH 3/3] git-blame-ignore-revs: init Adds a mechanism to track and subsequently ignore non-functional treewide changes during git blame. --- .git-blame-ignore-revs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..80e5f12 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,12 @@ +# Ignore non-functional treewide changes by configuring +# +# $ git config blame.ignoreRevsFile .git-blame-ignore-revs +# +# or used temporarily with --ignore-revs-file= +# + +# nixfmt +1a7f3d718c5a6406b7d5b54f10f5c9c69ed90ef9 + +# language hints +06cc71c76eb52dc747704a317ac5e175ebdd2ba8