From 2718336490b59ae396cf30a4f7cbd8333a028abd Mon Sep 17 00:00:00 2001 From: provokateurin Date: Tue, 11 Nov 2025 02:07:19 +0100 Subject: [PATCH] nixos/nextcloud: Add services.nextcloud.settings.mail_* options --- nixos/modules/services/web-apps/nextcloud.nix | 127 ++++++++++++++++++ nixos/tests/nextcloud/default.nix | 4 +- nixos/tests/nextcloud/with-mail.nix | 100 ++++++++++++++ nixos/tests/nextcloud/with-objectstore.nix | 1 - 4 files changed, 229 insertions(+), 3 deletions(-) create mode 100644 nixos/tests/nextcloud/with-mail.nix diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix index e05c8955cafe..c866f1b7c062 100644 --- a/nixos/modules/services/web-apps/nextcloud.nix +++ b/nixos/modules/services/web-apps/nextcloud.nix @@ -997,6 +997,126 @@ in The preview providers that should be explicitly enabled. ''; }; + mail_domain = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + The return address that you want to appear on emails sent by the Nextcloud server, for example `nc-admin@example.com`, substituting your own domain, of course. + ''; + }; + mail_from_address = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + FROM address that overrides the built-in `sharing-noreply` and `lostpassword-noreply` FROM addresses. + Defaults to different FROM addresses depending on the feature. + ''; + }; + mail_smtpdebug = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Enable SMTP class debugging. + `loglevel` will likely need to be adjusted too. + [See docs](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/email_configuration.html#enabling-debug-mode). + ''; + }; + mail_smtpmode = lib.mkOption { + type = lib.types.enum [ + "sendmail" + "smtp" + "qmail" + "null" # Yes, this is really a string null and not null. + ]; + default = "smtp"; + description = '' + Which mode to use for sending mail. + If you are using local or remote SMTP, set this to `smtp`. + For the `sendmail` option, you need an installed and working email system on the server, with your local `sendmail` installation. + For `qmail`, the binary is /var/qmail/bin/sendmail, and it must be installed on your Unix system. + Use the string null to send no mails (disable mail delivery). This can be useful if mails should be sent via APIs and rendering messages is not necessary. + ''; + }; + mail_smtphost = lib.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + description = '' + This depends on `mail_smtpmode`. Specify the IP address of your mail server host. This may contain multiple hosts separated by a semicolon. If you need to specify the port number, append it to the IP address separated by a colon, like this: `127.0.0.1:24`. + ''; + }; + mail_smtpport = lib.mkOption { + type = lib.types.port; + default = 25; + description = '' + This depends on `mail_smtpmode`. Specify the port for sending mail. + ''; + }; + mail_smtptimeout = lib.mkOption { + type = lib.types.int; + default = 10; + description = '' + This depends on `mail_smtpmode`. This sets the SMTP server timeout, in seconds. You may need to increase this if you are running an anti-malware or spam scanner. + ''; + }; + mail_smtpsecure = lib.mkOption { + type = lib.types.enum [ + "" + "ssl" + ]; + default = ""; + description = '' + This depends on `mail_smtpmode`. Specify `ssl` when you are using SSL/TLS. Any other value will be ignored. + If the server advertises STARTTLS capabilities, they might be used, but they cannot be enforced by this config option. + ''; + }; + mail_smtpauth = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + This depends on `mail_smtpmode`. Change this to `true` if your mail server requires authentication. + ''; + }; + mail_smtpname = lib.mkOption { + type = lib.types.str; + default = ""; + description = '' + This depends on `mail_smtpauth`. Specify the username for authenticating to the SMTP server. + ''; + }; + # mail_smtppassword is skipped as it must be set through services.nextcloud.secrets + mail_template_class = lib.mkOption { + type = lib.types.str; + default = "\\OC\\Mail\\EMailTemplate"; + description = '' + Replaces the default mail template layout. This can be utilized if the options to modify the mail texts with the theming app are not enough. + The class must extend `\OC\Mail\EMailTemplate` + ''; + }; + mail_send_plaintext_only = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Email will be sent by default with an HTML and a plain text body. This option allows sending only plain text emails. + ''; + }; + mail_smtpstreamoptions = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = '' + This depends on `mail_smtpmode`. Array of additional streams options that will be passed to underlying Swift mailer implementation. + ''; + }; + mail_sendmailmode = lib.mkOption { + type = lib.types.enum [ + "smtp" + "pipe" + ]; + default = "smtp"; + description = '' + For `smtp`, the sendmail binary is started with the parameter `-bs`: Use the SMTP protocol on standard input and output. + For `pipe`, the binary is started with the parameters `-t`: Read message from STDIN and extract recipients. + ''; + }; }; }; default = { }; @@ -1187,6 +1307,13 @@ in If `services.nextcloud.config.adminpassFile` is null, `services.nextcloud.config.adminuser` must be null as well in order to disable initial admin user creation. ''; } + { + assertion = !(cfg.settings ? mail_smtppassword); + message = '' + The option `services.nextcloud.settings.mail_smtppassword` must not be used, as it puts the password into the world-readable nix store. + Use `services.nextcloud.secrets.mail_smtppassword` instead and set it to a file containing the password. + ''; + } ]; } diff --git a/nixos/tests/nextcloud/default.nix b/nixos/tests/nextcloud/default.nix index f452a4bf1218..72f7a28070b5 100644 --- a/nixos/tests/nextcloud/default.nix +++ b/nixos/tests/nextcloud/default.nix @@ -75,8 +75,7 @@ let inherit (config) test-helpers; in mkBefore '' - nextcloud.start() - client.start() + start_all() nextcloud.wait_for_unit("multi-user.target") ${test-helpers.init} @@ -136,6 +135,7 @@ let ./with-mysql-and-memcached.nix ./with-postgresql-and-redis.nix ./with-objectstore.nix + ./with-mail.nix ] ++ (pkgs.lib.optional (version >= 32) ./without-admin-user.nix) ); diff --git a/nixos/tests/nextcloud/with-mail.nix b/nixos/tests/nextcloud/with-mail.nix new file mode 100644 index 000000000000..24f1ef283056 --- /dev/null +++ b/nixos/tests/nextcloud/with-mail.nix @@ -0,0 +1,100 @@ +{ + name, + pkgs, + testBase, + system, + ... +}: +with import ../../lib/testing-python.nix { inherit system pkgs; }; +runTest ( + { config, lib, ... }: + let + certs = import ../common/acme/server/snakeoil-certs.nix; + domain = certs.domain; + in + { + inherit name; + + meta.maintainers = lib.teams.nextcloud.members; + + imports = [ testBase ]; + + nodes = { + nextcloud = + { + config, + pkgs, + nodes, + ... + }: + { + security.pki.certificateFiles = [ certs.ca.cert ]; + + networking.extraHosts = '' + ${nodes.stalwart.networking.primaryIPAddress} ${domain} + ''; + + environment.etc."nextcloud/mail_smtppassword".text = "foobar"; + + services.nextcloud = { + config.dbtype = "sqlite"; + + settings = { + mail_from_address = "alice"; + mail_domain = domain; + mail_smtpmode = "smtp"; + mail_smtphost = domain; + mail_smtpport = 587; + mail_smtpauth = true; + mail_smtpname = "alice"; + mail_send_plaintext_only = true; + }; + + secrets.mail_smtppassword = "/etc/nextcloud/mail_smtppassword"; + }; + }; + + stalwart = + { pkgs, ... }: + { + imports = [ ../stalwart/stalwart-mail-config.nix ]; + + networking.firewall.allowedTCPPorts = [ 587 ]; + + environment.systemPackages = [ + (pkgs.writers.writePython3Bin "test-imap-read" { } '' + from imaplib import IMAP4 + + with IMAP4('localhost') as imap: + imap.starttls() + status, [caps] = imap.login('bob', 'foobar') + assert status == 'OK' + imap.select() + status, [ref] = imap.search(None, 'ALL') + assert status == 'OK' + [msgId] = ref.split() + status, msg = imap.fetch(msgId, 'BODY[TEXT]') + assert status == 'OK' + assert (msg[0][1].strip() + == (b'Well done, ${config.adminuser}!\r\n\r\n' + b'If you received this email, the email configuration ' + b's=\r\neems to be correct.\r\n\r\n\r\n--=20\r\n' + b'Nextcloud - a safe home for all your data=\r\n\r\n' + b'This is an automatically sent email, please do not reply.')) + '') + ]; + }; + }; + + test-helpers.init = '' + stalwart.wait_for_unit("multi-user.target") + stalwart.wait_until_succeeds("nc -vzw 2 localhost 587") + + nextcloud.succeed("nc -vzw 2 ${domain} 587") + nextcloud.succeed("curl -sS --fail-with-body -u ${config.adminuser}:${config.adminpass} -H 'OCS-APIRequest: true' -X PUT http://nextcloud/ocs/v2.php/cloud/users/${config.adminuser} -H 'Content-Type: application/json' --data-raw '{\"key\":\"email\",\"value\":\"bob@${domain}\"}'") + nextcloud.succeed("curl -sS --fail-with-body -u ${config.adminuser}:${config.adminpass} -H 'OCS-APIRequest: true' -X POST http://nextcloud/settings/admin/mailtest") + + stalwart.succeed("test-imap-read") + ''; + } +) diff --git a/nixos/tests/nextcloud/with-objectstore.nix b/nixos/tests/nextcloud/with-objectstore.nix index c652df46b1c4..44777d6d8ac0 100644 --- a/nixos/tests/nextcloud/with-objectstore.nix +++ b/nixos/tests/nextcloud/with-objectstore.nix @@ -111,7 +111,6 @@ runTest ( }; test-helpers.init = '' - minio.start() minio.wait_for_open_port(9000) minio.wait_for_unit("nginx.service") minio.wait_for_open_port(443)