Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 150 additions & 1 deletion nixos/modules/services/web-apps/nextcloud.nix
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ let
++ (lib.optional (
cfg.config.objectstore.s3.sseCKeyFile != null
) "s3_sse_c_key:${cfg.config.objectstore.s3.sseCKeyFile}")
++ (lib.optional (cfg.secretFile != null) "secret_file:${cfg.secretFile}");
++ (lib.optional (cfg.secretFile != null) "secret_file:${cfg.secretFile}")
++ (lib.mapAttrsToList (credential: file: "${credential}:${file}") cfg.secrets);

requiresRuntimeSystemdCredentials = (lib.length runtimeSystemdCredentials) != 0;

Expand Down Expand Up @@ -296,6 +297,9 @@ let
) "'dbtableprefix' => '${toString c.dbtableprefix}',"}
${lib.optionalString (c.dbpassFile != null) "'dbpassword' => nix_read_secret('dbpass'),"}
'dbtype' => '${c.dbtype}',
${lib.concatStringsSep "\n" (
lib.mapAttrsToList (name: credential: "'${name}' => nix_read_secret('${name}'),") cfg.secrets
)}
${objectstoreConfig}
];

Expand Down Expand Up @@ -390,6 +394,24 @@ in
'';
example = "/mnt/nextcloud-file";
};
secrets = lib.mkOption {
type = lib.types.attrsOf (
lib.types.pathWith {
inStore = false;
absolute = true;
}
);
default = { };
description = ''
Secret files to read into entries in `config.php`.
This uses `nix_read_secret` and LoadCredential to read the contents of the file into the entry in `config.php`.
'';
example = lib.literalExpression ''
{
oidc_login_client_secret = "/run/secrets/nextcloud_oidc_secret";
}
'';
};
extraApps = lib.mkOption {
type = lib.types.attrsOf lib.types.package;
default = { };
Expand Down Expand Up @@ -975,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 `[email protected]`, 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`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uhm, what will happen if you specify both mail_smtpport and 127.0.0.1:24 as smtphost?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I have no idea, this is just copied from the sample config docs. I suppose the mail_smtpport would act as a default and with the colon it would be possible to override it for each server.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I see.

We don't have much of a choice anyways.

'';
};
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 = { };
Expand Down Expand Up @@ -1165,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.
'';
}
];
}

Expand Down
6 changes: 6 additions & 0 deletions nixos/tests/nextcloud/basic.nix
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,11 @@ runTest (
};
phpExtraExtensions = all: [ all.bz2 ];
nginx.enableFastcgiRequestBuffering = true;
secrets.mysecret = "/etc/nextcloud/mysecretfile";
};

environment.etc."nextcloud/mysecretfile".text = "foobar";

specialisation.withoutMagick.configuration = {
services.nextcloud.enableImagemagick = false;
};
Expand Down Expand Up @@ -116,6 +119,9 @@ runTest (
client_hash = client.succeed("nix-hash testfile.bin").strip()
nextcloud_hash = nextcloud.succeed("nix-hash /var/lib/nextcloud-data/data/root/files/testfile.bin").strip()
t.assertEqual(client_hash, nextcloud_hash)

with subtest("secrets"):
assert "foobar" == nextcloud.succeed("nextcloud-occ config:system:get mysecret").strip()
'';
}
)
4 changes: 2 additions & 2 deletions nixos/tests/nextcloud/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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)
);
Expand Down
100 changes: 100 additions & 0 deletions nixos/tests/nextcloud/with-mail.nix
Original file line number Diff line number Diff line change
@@ -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")
'';
}
)
1 change: 0 additions & 1 deletion nixos/tests/nextcloud/with-objectstore.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading