diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index f4c7cf601bf1c..11484d159b8ce 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -689,6 +689,7 @@
./services/web-apps/codimd.nix
./services/web-apps/frab.nix
./services/web-apps/mattermost.nix
+ ./services/web-apps/nextcloud.nix
./services/web-apps/nexus.nix
./services/web-apps/pgpkeyserver-lite.nix
./services/web-apps/matomo.nix
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
new file mode 100644
index 0000000000000..44c3df1d057bc
--- /dev/null
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -0,0 +1,463 @@
+{ config, lib, pkgs, ... }@args:
+
+with lib;
+
+let
+ cfg = config.services.nextcloud;
+
+ toKeyValue = generators.toKeyValue {
+ mkKeyValue = generators.mkKeyValueDefault {} " = ";
+ };
+
+ phpOptionsExtensions = ''
+ ${optionalString cfg.caching.apcu "extension=${cfg.phpPackages.apcu}/lib/php/extensions/apcu.so"}
+ ${optionalString cfg.caching.redis "extension=${cfg.phpPackages.redis}/lib/php/extensions/redis.so"}
+ ${optionalString cfg.caching.memcached "extension=${cfg.phpPackages.memcached}/lib/php/extensions/memcached.so"}
+ zend_extension = opcache.so
+ opcache.enable = 1
+ '';
+ phpOptions = {
+ upload_max_filesize = cfg.maxUploadSize;
+ post_max_size = cfg.maxUploadSize;
+ memory_limit = cfg.maxUploadSize;
+ } // cfg.phpOptions;
+ phpOptionsStr = phpOptionsExtensions + (toKeyValue phpOptions);
+
+ occ = pkgs.writeScriptBin "nextcloud-occ" ''
+ #! ${pkgs.stdenv.shell}
+ cd ${pkgs.nextcloud}
+ exec /run/wrappers/bin/sudo -u nextcloud \
+ NEXTCLOUD_CONFIG_DIR="${cfg.home}/config" \
+ ${config.services.phpfpm.phpPackage}/bin/php \
+ -c ${pkgs.writeText "php.ini" phpOptionsStr}\
+ occ $*
+ '';
+
+in {
+ options.services.nextcloud = {
+ enable = mkEnableOption "nextcloud";
+ hostName = mkOption {
+ type = types.str;
+ description = "FQDN for the nextcloud instance.";
+ };
+ home = mkOption {
+ type = types.str;
+ default = "/var/lib/nextcloud";
+ description = "Storage path of nextcloud.";
+ };
+ https = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable if there is a TLS terminating proxy in front of nextcloud.";
+ };
+
+ maxUploadSize = mkOption {
+ default = "512M";
+ type = types.str;
+ description = ''
+ Defines the upload limit for files. This changes the relevant options
+ in php.ini and nginx if enabled.
+ '';
+ };
+
+ skeletonDirectory = mkOption {
+ default = "";
+ type = types.str;
+ description = ''
+ The directory where the skeleton files are located. These files will be
+ copied to the data directory of new users. Leave empty to not copy any
+ skeleton files.
+ '';
+ };
+
+ nginx.enable = mkEnableOption "nginx vhost management";
+
+ webfinger = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable this option if you plan on using the webfinger plugin.
+ The appropriate nginx rewrite rules will be added to your configuration.
+ '';
+ };
+
+ phpPackages = mkOption {
+ type = types.attrs;
+ default = pkgs.php71Packages;
+ defaultText = "pkgs.php71Packages";
+ description = ''
+ Overridable attribute of the PHP packages set to use. If any caching
+ module is enabled, it will be taken from here. Therefore it should
+ match the version of PHP given to
+ services.phpfpm.phpPackage.
+ '';
+ };
+
+ phpOptions = mkOption {
+ type = types.attrsOf types.str;
+ default = {
+ "short_open_tag" = "Off";
+ "expose_php" = "Off";
+ "error_reporting" = "E_ALL & ~E_DEPRECATED & ~E_STRICT";
+ "display_errors" = "stderr";
+ "opcache.enable_cli" = "1";
+ "opcache.interned_strings_buffer" = "8";
+ "opcache.max_accelerated_files" = "10000";
+ "opcache.memory_consumption" = "128";
+ "opcache.revalidate_freq" = "1";
+ "opcache.fast_shutdown" = "1";
+ "openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt";
+ "catch_workers_output" = "yes";
+ };
+ description = ''
+ Options for PHP's php.ini file for nextcloud.
+ '';
+ };
+
+ config = {
+ dbtype = mkOption {
+ type = types.enum [ "sqlite" "pgsql" "mysql" ];
+ default = "sqlite";
+ description = "Database type.";
+ };
+ dbname = mkOption {
+ type = types.nullOr types.str;
+ default = "nextcloud";
+ description = "Database name.";
+ };
+ dbuser = mkOption {
+ type = types.nullOr types.str;
+ default = "nextcloud";
+ description = "Database user.";
+ };
+ dbpass = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Database password. Use dbpassFile to avoid this
+ being world-readable in the /nix/store.
+ '';
+ };
+ dbpassFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The full path to a file that contains the database password.
+ '';
+ };
+ dbhost = mkOption {
+ type = types.nullOr types.str;
+ default = "localhost";
+ description = "Database host.";
+ };
+ dbport = mkOption {
+ type = with types; nullOr (either int str);
+ default = null;
+ description = "Database port.";
+ };
+ dbtableprefix = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Table prefix in Nextcloud database.";
+ };
+ adminuser = mkOption {
+ type = types.str;
+ default = "root";
+ description = "Admin username.";
+ };
+ adminpass = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Database password. Use adminpassFile to avoid this
+ being world-readable in the /nix/store.
+ '';
+ };
+ adminpassFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The full path to a file that contains the admin's password.
+ '';
+ };
+
+ extraTrustedDomains = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Trusted domains, from which the nextcloud installation will be
+ acessible. You don't need to add
+ services.nextcloud.hostname here.
+ '';
+ };
+ };
+
+ caching = {
+ apcu = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to load the APCu module into PHP.
+ '';
+ };
+ redis = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to load the Redis module into PHP.
+ You still need to enable Redis in your config.php.
+ See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
+ '';
+ };
+ memcached = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to load the Memcached module into PHP.
+ You still need to enable Memcached in your config.php.
+ See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ { assertions = let acfg = cfg.config; in [
+ { assertion = !(acfg.dbpass != null && acfg.dbpassFile != null);
+ message = "Please specify no more than one of dbpass or dbpassFile";
+ }
+ { assertion = ((acfg.adminpass != null || acfg.adminpassFile != null)
+ && !(acfg.adminpass != null && acfg.adminpassFile != null));
+ message = "Please specify exactly one of adminpass or adminpassFile";
+ }
+ ];
+ }
+
+ { systemd.timers."nextcloud-cron" = {
+ wantedBy = [ "timers.target" ];
+ timerConfig.OnBootSec = "5m";
+ timerConfig.OnUnitActiveSec = "15m";
+ timerConfig.Unit = "nextcloud-cron.service";
+ };
+
+ systemd.services = {
+ "nextcloud-setup" = let
+ overrideConfig = pkgs.writeText "nextcloud-config.php" ''
+ [
+ [ 'path' => '${cfg.home}/apps', 'url' => '/apps', 'writable' => false ],
+ [ 'path' => '${cfg.home}/store-apps', 'url' => '/store-apps', 'writable' => true ],
+ ],
+ 'datadirectory' => '${cfg.home}/data',
+ 'skeletondirectory' => '${cfg.skeletonDirectory}',
+ ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"}
+ 'log_type' => 'syslog',
+ ];
+ '';
+ occInstallCmd = let
+ c = cfg.config;
+ adminpass = if c.adminpassFile != null
+ then ''"$(<"${toString c.adminpassFile}")"''
+ else ''"${toString c.adminpass}"'';
+ dbpass = if c.dbpassFile != null
+ then ''"$(<"${toString c.dbpassFile}")"''
+ else if c.dbpass != null
+ then ''"${toString c.dbpass}"''
+ else null;
+ installFlags = concatStringsSep " \\\n "
+ (mapAttrsToList (k: v: "${k} ${toString v}") {
+ "--database" = ''"${c.dbtype}"'';
+ # The following attributes are optional depending on the type of
+ # database. Those that evaluate to null on the left hand side
+ # will be omitted.
+ ${if c.dbname != null then "--database-name" else null} = ''"${c.dbname}"'';
+ ${if c.dbhost != null then "--database-host" else null} = ''"${c.dbhost}"'';
+ ${if c.dbport != null then "--database-port" else null} = ''"${toString c.dbport}"'';
+ ${if c.dbuser != null then "--database-user" else null} = ''"${c.dbuser}"'';
+ ${if (any (x: x != null) [c.dbpass c.dbpassFile])
+ then "--database-pass" else null} = dbpass;
+ ${if c.dbtableprefix != null
+ then "--database-table-prefix" else null} = ''"${toString c.dbtableprefix}"'';
+ "--admin-user" = ''"${c.adminuser}"'';
+ "--admin-pass" = adminpass;
+ "--data-dir" = ''"${cfg.home}/data"'';
+ });
+ in ''
+ ${occ}/bin/nextcloud-occ maintenance:install \
+ ${installFlags}
+ '';
+ occSetTrustedDomainsCmd = concatStringsSep "\n" (imap0
+ (i: v: ''
+ ${occ}/bin/nextcloud-occ config:system:set trusted_domains \
+ ${toString i} --value="${toString v}"
+ '') ([ cfg.hostName ] ++ cfg.config.extraTrustedDomains));
+
+ in {
+ wantedBy = [ "multi-user.target" ];
+ before = [ "phpfpm-nextcloud.service" ];
+ script = ''
+ chmod og+x ${cfg.home}
+ ln -sf ${pkgs.nextcloud}/apps ${cfg.home}/
+ mkdir -p ${cfg.home}/config ${cfg.home}/data ${cfg.home}/store-apps
+ ln -sf ${overrideConfig} ${cfg.home}/config/override.config.php
+
+ chown -R nextcloud:nginx ${cfg.home}/config ${cfg.home}/data ${cfg.home}/store-apps
+
+ # Do not install if already installed
+ if [[ ! -e ${cfg.home}/config/config.php ]]; then
+ ${occInstallCmd}
+ fi
+
+ ${occ}/bin/nextcloud-occ upgrade
+
+ ${occ}/bin/nextcloud-occ config:system:delete trusted_domains
+ ${occSetTrustedDomainsCmd}
+ '';
+ serviceConfig.Type = "oneshot";
+ };
+ "nextcloud-cron" = {
+ environment.NEXTCLOUD_CONFIG_DIR = "${cfg.home}/config";
+ serviceConfig.Type = "oneshot";
+ serviceConfig.User = "nextcloud";
+ serviceConfig.ExecStart = "${pkgs.php}/bin/php -f ${pkgs.nextcloud}/cron.php";
+ };
+ };
+
+ services.phpfpm = {
+ phpOptions = phpOptionsExtensions;
+ phpPackage = pkgs.php71;
+ pools.nextcloud = let
+ phpAdminValues = (toKeyValue
+ (foldr (a: b: a // b) {}
+ (mapAttrsToList (k: v: { "php_admin_value[${k}]" = v; })
+ phpOptions)));
+ in {
+ listen = "/run/phpfpm/nextcloud";
+ extraConfig = ''
+ listen.owner = nginx
+ listen.group = nginx
+ user = nextcloud
+ group = nginx
+ pm = dynamic
+ pm.max_children = 32
+ pm.start_servers = 2
+ pm.min_spare_servers = 2
+ pm.max_spare_servers = 4
+ env[NEXTCLOUD_CONFIG_DIR] = ${cfg.home}/config
+ env[PATH] = /run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin
+ ${phpAdminValues}
+ '';
+ };
+ };
+
+ users.extraUsers.nextcloud = {
+ home = "${cfg.home}";
+ group = "nginx";
+ createHome = true;
+ };
+
+ environment.systemPackages = [ occ ];
+ }
+
+ (mkIf cfg.nginx.enable {
+ services.nginx = {
+ enable = true;
+ virtualHosts = {
+ "${cfg.hostName}" = {
+ root = pkgs.nextcloud;
+ locations = {
+ "= /robots.txt" = {
+ priority = 100;
+ extraConfig = ''
+ allow all;
+ log_not_found off;
+ access_log off;
+ '';
+ };
+ "/" = {
+ priority = 200;
+ extraConfig = "rewrite ^ /index.php$uri;";
+ };
+ "~ ^/store-apps" = {
+ priority = 201;
+ extraConfig = "root ${cfg.home};";
+ };
+ "= /.well-known/carddav" = {
+ priority = 210;
+ extraConfig = "return 301 $scheme://$host/remote.php/dav;";
+ };
+ "= /.well-known/caldav" = {
+ priority = 210;
+ extraConfig = "return 301 $scheme://$host/remote.php/dav;";
+ };
+ "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/" = {
+ priority = 300;
+ extraConfig = "deny all;";
+ };
+ "~ ^/(?:\\.|autotest|occ|issue|indie|db_|console)" = {
+ priority = 300;
+ extraConfig = "deny all;";
+ };
+ "~ ^/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\\.php(?:$|/)" = {
+ priority = 500;
+ extraConfig = ''
+ include ${pkgs.nginxMainline}/conf/fastcgi.conf;
+ fastcgi_split_path_info ^(.+\.php)(/.*)$;
+ fastcgi_param PATH_INFO $fastcgi_path_info;
+ fastcgi_param HTTPS ${if cfg.https then "on" else "off"};
+ fastcgi_param modHeadersAvailable true;
+ fastcgi_param front_controller_active true;
+ fastcgi_pass unix:/run/phpfpm/nextcloud;
+ fastcgi_intercept_errors on;
+ fastcgi_request_buffering off;
+ fastcgi_read_timeout 120s;
+ '';
+ };
+ "~ ^/(?:updater|ocs-provider)(?:$|/)".extraConfig = ''
+ try_files $uri/ =404;
+ index index.php;
+ '';
+ "~ \\.(?:css|js|woff|svg|gif)$".extraConfig = ''
+ try_files $uri /index.php$uri$is_args$args;
+ add_header Cache-Control "public, max-age=15778463";
+ add_header X-Content-Type-Options nosniff;
+ add_header X-XSS-Protection "1; mode=block";
+ add_header X-Robots-Tag none;
+ add_header X-Download-Options noopen;
+ add_header X-Permitted-Cross-Domain-Policies none;
+ access_log off;
+ '';
+ "~ \\.(?:png|html|ttf|ico|jpg|jpeg)$".extraConfig = ''
+ try_files $uri /index.php$uri$is_args$args;
+ access_log off;
+ '';
+ };
+ extraConfig = ''
+ add_header X-Content-Type-Options nosniff;
+ add_header X-XSS-Protection "1; mode=block";
+ add_header X-Robots-Tag none;
+ add_header X-Download-Options noopen;
+ add_header X-Permitted-Cross-Domain-Policies none;
+ error_page 403 /core/templates/403.php;
+ error_page 404 /core/templates/404.php;
+ client_max_body_size ${cfg.maxUploadSize};
+ fastcgi_buffers 64 4K;
+ gzip on;
+ gzip_vary on;
+ gzip_comp_level 4;
+ gzip_min_length 256;
+ gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
+ gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
+
+ ${optionalString cfg.webfinger ''
+ rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
+ rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;
+ ''}
+ '';
+ };
+ };
+ };
+ })
+ ]);
+}
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index b231ee5a3f018..508398f03acea 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -245,8 +245,8 @@ let
}
''
) virtualHosts);
- mkLocations = locations: concatStringsSep "\n" (mapAttrsToList (location: config: ''
- location ${location} {
+ mkLocations = locations: concatStringsSep "\n" (map (config: ''
+ location ${config.location} {
${optionalString (config.proxyPass != null && !cfg.proxyResolveWhileRunning)
"proxy_pass ${config.proxyPass};"
}
@@ -266,7 +266,18 @@ let
${config.extraConfig}
${optionalString (config.proxyPass != null && cfg.recommendedProxySettings) "include ${recommendedProxyConfig};"}
}
- '') locations);
+ '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations)));
+ mkBasicAuth = vhostName: authDef: let
+ htpasswdFile = pkgs.writeText "${vhostName}.htpasswd" (
+ concatStringsSep "\n" (mapAttrsToList (user: password: ''
+ ${user}:{PLAIN}${password}
+ '') authDef)
+ );
+ in ''
+ auth_basic secured;
+ auth_basic_user_file ${htpasswdFile};
+ '';
+
mkHtpasswd = vhostName: authDef: pkgs.writeText "${vhostName}.htpasswd" (
concatStringsSep "\n" (mapAttrsToList (user: password: ''
${user}:{PLAIN}${password}
diff --git a/nixos/modules/services/web-servers/nginx/location-options.nix b/nixos/modules/services/web-servers/nginx/location-options.nix
index 4c772734a749a..9b44433d3845c 100644
--- a/nixos/modules/services/web-servers/nginx/location-options.nix
+++ b/nixos/modules/services/web-servers/nginx/location-options.nix
@@ -71,6 +71,16 @@ with lib;
These lines go to the end of the location verbatim.
'';
};
+
+ priority = mkOption {
+ type = types.int;
+ default = 1000;
+ description = ''
+ Order of this location block in relation to the others in the vhost.
+ The semantics are the same as with `lib.mkOrder`. Smaller values have
+ a greater priority.
+ '';
+ };
};
}
diff --git a/nixos/release.nix b/nixos/release.nix
index cce2c54f02bf5..0c2207c27ad7e 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -360,6 +360,7 @@ in rec {
tests.netdata = callTest tests/netdata.nix { };
tests.networking.networkd = callSubTests tests/networking.nix { networkd = true; };
tests.networking.scripted = callSubTests tests/networking.nix { networkd = false; };
+ tests.nextcloud = callSubTests tests/nextcloud { };
# TODO: put in networking.nix after the test becomes more complete
tests.networkingProxy = callTest tests/networking-proxy.nix {};
tests.nexus = callTest tests/nexus.nix { };
diff --git a/nixos/tests/nextcloud/basic.nix b/nixos/tests/nextcloud/basic.nix
new file mode 100644
index 0000000000000..c3b710f0f904c
--- /dev/null
+++ b/nixos/tests/nextcloud/basic.nix
@@ -0,0 +1,56 @@
+import ../make-test.nix ({ pkgs, ...}: let
+ adminpass = "notproduction";
+ adminuser = "root";
+in {
+ name = "nextcloud-basic";
+ meta = with pkgs.stdenv.lib.maintainers; {
+ maintainers = [ globin eqyiel ];
+ };
+
+ nodes = {
+ # The only thing the client needs to do is download a file.
+ client = { ... }: {};
+
+ nextcloud = { config, pkgs, ... }: {
+ networking.firewall.allowedTCPPorts = [ 80 ];
+
+ services.nextcloud = {
+ enable = true;
+ nginx.enable = true;
+ hostName = "nextcloud";
+ config = {
+ # Don't inherit adminuser since "root" is supposed to be the default
+ inherit adminpass;
+ };
+ };
+ };
+ };
+
+ testScript = let
+ withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
+ #!${pkgs.stdenv.shell}
+ export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+ export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/"
+ export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+ export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+ export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+ "''${@}"
+ '';
+ copySharedFile = pkgs.writeScript "copy-shared-file" ''
+ #!${pkgs.stdenv.shell}
+ echo 'hi' | ${withRcloneEnv} ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+ '';
+
+ diffSharedFile = pkgs.writeScript "diff-shared-file" ''
+ #!${pkgs.stdenv.shell}
+ diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
+ '';
+ in ''
+ startAll();
+ $nextcloud->waitForUnit("multi-user.target");
+ $nextcloud->succeed("curl -sSf http://nextcloud/login");
+ $nextcloud->succeed("${withRcloneEnv} ${copySharedFile}");
+ $client->waitForUnit("multi-user.target");
+ $client->succeed("${withRcloneEnv} ${diffSharedFile}");
+ '';
+})
diff --git a/nixos/tests/nextcloud/default.nix b/nixos/tests/nextcloud/default.nix
new file mode 100644
index 0000000000000..66da6794b961d
--- /dev/null
+++ b/nixos/tests/nextcloud/default.nix
@@ -0,0 +1,6 @@
+{ system ? builtins.currentSystem }:
+{
+ basic = import ./basic.nix { inherit system; };
+ with-postgresql-and-redis = import ./with-postgresql-and-redis.nix { inherit system; };
+ with-mysql-and-memcached = import ./with-mysql-and-memcached.nix { inherit system; };
+}
diff --git a/nixos/tests/nextcloud/with-mysql-and-memcached.nix b/nixos/tests/nextcloud/with-mysql-and-memcached.nix
new file mode 100644
index 0000000000000..c0d347238b47e
--- /dev/null
+++ b/nixos/tests/nextcloud/with-mysql-and-memcached.nix
@@ -0,0 +1,97 @@
+import ../make-test.nix ({ pkgs, ...}: let
+ adminpass = "hunter2";
+ adminuser = "root";
+in {
+ name = "nextcloud-with-mysql-and-memcached";
+ meta = with pkgs.stdenv.lib.maintainers; {
+ maintainers = [ eqyiel ];
+ };
+
+ nodes = {
+ # The only thing the client needs to do is download a file.
+ client = { ... }: {};
+
+ nextcloud = { config, pkgs, ... }: {
+ networking.firewall.allowedTCPPorts = [ 80 ];
+
+ services.nextcloud = {
+ enable = true;
+ hostName = "nextcloud";
+ nginx.enable = true;
+ https = true;
+ caching = {
+ apcu = true;
+ redis = false;
+ memcached = true;
+ };
+ config = {
+ dbtype = "mysql";
+ dbname = "nextcloud";
+ dbuser = "nextcloud";
+ dbhost = "127.0.0.1";
+ dbport = 3306;
+ dbpass = "hunter2";
+ # Don't inherit adminuser since "root" is supposed to be the default
+ inherit adminpass;
+ };
+ };
+
+ services.mysql = {
+ enable = true;
+ bind = "127.0.0.1";
+ package = pkgs.mariadb;
+ initialScript = pkgs.writeText "mysql-init" ''
+ CREATE USER 'nextcloud'@'localhost' IDENTIFIED BY 'hunter2';
+ CREATE DATABASE IF NOT EXISTS nextcloud;
+ GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER,
+ CREATE TEMPORARY TABLES ON nextcloud.* TO 'nextcloud'@'localhost'
+ IDENTIFIED BY 'hunter2';
+ FLUSH privileges;
+ '';
+ };
+
+ systemd.services."nextcloud-setup"= {
+ requires = ["mysql.service"];
+ after = ["mysql.service"];
+ };
+
+ services.memcached.enable = true;
+ };
+ };
+
+ testScript = let
+ configureMemcached = pkgs.writeScript "configure-memcached" ''
+ #!${pkgs.stdenv.shell}
+ nextcloud-occ config:system:set memcached_servers 0 0 --value 127.0.0.1 --type string
+ nextcloud-occ config:system:set memcached_servers 0 1 --value 11211 --type integer
+ nextcloud-occ config:system:set memcache.local --value '\OC\Memcache\APCu' --type string
+ nextcloud-occ config:system:set memcache.distributed --value '\OC\Memcache\Memcached' --type string
+ '';
+ withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
+ #!${pkgs.stdenv.shell}
+ export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+ export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/"
+ export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+ export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+ export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+ '';
+ copySharedFile = pkgs.writeScript "copy-shared-file" ''
+ #!${pkgs.stdenv.shell}
+ echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+ '';
+
+ diffSharedFile = pkgs.writeScript "diff-shared-file" ''
+ #!${pkgs.stdenv.shell}
+ diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
+ '';
+ in ''
+ startAll();
+ $nextcloud->waitForUnit("multi-user.target");
+ $nextcloud->succeed("${configureMemcached}");
+ $nextcloud->succeed("curl -sSf http://nextcloud/login");
+ $nextcloud->succeed("${withRcloneEnv} ${copySharedFile}");
+ $client->waitForUnit("multi-user.target");
+ $client->succeed("${withRcloneEnv} ${diffSharedFile}");
+
+ '';
+})
diff --git a/nixos/tests/nextcloud/with-postgresql-and-redis.nix b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
new file mode 100644
index 0000000000000..0351d4db69ac9
--- /dev/null
+++ b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
@@ -0,0 +1,130 @@
+import ../make-test.nix ({ pkgs, ...}: let
+ adminpass = "hunter2";
+ adminuser = "custom-admin-username";
+in {
+ name = "nextcloud-with-postgresql-and-redis";
+ meta = with pkgs.stdenv.lib.maintainers; {
+ maintainers = [ eqyiel ];
+ };
+
+ nodes = {
+ # The only thing the client needs to do is download a file.
+ client = { ... }: {};
+
+ nextcloud = { config, pkgs, ... }: {
+ networking.firewall.allowedTCPPorts = [ 80 ];
+
+ services.nextcloud = {
+ enable = true;
+ hostName = "nextcloud";
+ nginx.enable = true;
+ caching = {
+ apcu = false;
+ redis = true;
+ memcached = false;
+ };
+ config = {
+ dbtype = "pgsql";
+ dbname = "nextcloud";
+ dbuser = "nextcloud";
+ dbhost = "localhost";
+ dbpassFile = toString (pkgs.writeText "db-pass-file" ''
+ hunter2
+ '');
+ inherit adminuser;
+ adminpassFile = toString (pkgs.writeText "admin-pass-file" ''
+ ${adminpass}
+ '');
+ };
+ };
+
+ services.redis = {
+ unixSocket = "/var/run/redis/redis.sock";
+ enable = true;
+ extraConfig = ''
+ unixsocketperm 770
+ '';
+ };
+
+ systemd.services.redis = {
+ preStart = ''
+ mkdir -p /var/run/redis
+ chown ${config.services.redis.user}:${config.services.nginx.group} /var/run/redis
+ '';
+ serviceConfig.PermissionsStartOnly = true;
+ };
+
+ systemd.services."nextcloud-setup"= {
+ requires = ["postgresql.service"];
+ after = [
+ "postgresql.service"
+ "chown-redis-socket.service"
+ ];
+ };
+
+ # At the time of writing, redis creates its socket with the "nobody"
+ # group. I figure this is slightly less bad than making the socket world
+ # readable.
+ systemd.services."chown-redis-socket" = {
+ enable = true;
+ script = ''
+ until ${pkgs.redis}/bin/redis-cli ping; do
+ echo "waiting for redis..."
+ sleep 1
+ done
+ chown ${config.services.redis.user}:${config.services.nginx.group} /var/run/redis/redis.sock
+ '';
+ after = [ "redis.service" ];
+ requires = [ "redis.service" ];
+ wantedBy = [ "redis.service" ];
+ serviceConfig = {
+ Type = "oneshot";
+ };
+ };
+
+ services.postgresql = {
+ enable = true;
+ initialScript = pkgs.writeText "psql-init" ''
+ create role nextcloud with login password 'hunter2';
+ create database nextcloud with owner nextcloud;
+ '';
+ };
+ };
+ };
+
+ testScript = let
+ configureRedis = pkgs.writeScript "configure-redis" ''
+ #!${pkgs.stdenv.shell}
+ nextcloud-occ config:system:set redis 'host' --value '/var/run/redis/redis.sock' --type string
+ nextcloud-occ config:system:set redis 'port' --value 0 --type integer
+ nextcloud-occ config:system:set memcache.local --value '\OC\Memcache\Redis' --type string
+ nextcloud-occ config:system:set memcache.locking --value '\OC\Memcache\Redis' --type string
+ '';
+ withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
+ #!${pkgs.stdenv.shell}
+ export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+ export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/"
+ export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+ export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+ export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+ "''${@}"
+ '';
+ copySharedFile = pkgs.writeScript "copy-shared-file" ''
+ #!${pkgs.stdenv.shell}
+ echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+ '';
+
+ diffSharedFile = pkgs.writeScript "diff-shared-file" ''
+ #!${pkgs.stdenv.shell}
+ diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
+ '';
+ in ''
+ startAll();
+ $nextcloud->waitForUnit("multi-user.target");
+ $nextcloud->succeed("${configureRedis}");
+ $nextcloud->succeed("curl -sSf http://nextcloud/login");
+ $nextcloud->succeed("${withRcloneEnv} ${copySharedFile}");
+ $client->waitForUnit("multi-user.target");
+ $client->succeed("${withRcloneEnv} ${diffSharedFile}");
+ '';
+})
diff --git a/pkgs/servers/nextcloud/default.nix b/pkgs/servers/nextcloud/default.nix
index 8a0a848aa5d6e..3b5e46cdb2a80 100644
--- a/pkgs/servers/nextcloud/default.nix
+++ b/pkgs/servers/nextcloud/default.nix
@@ -1,14 +1,20 @@
-{ stdenv, fetchurl }:
+{ stdenv, fetchurl, fetchpatch }:
stdenv.mkDerivation rec {
- name= "nextcloud-${version}";
- version = "13.0.6";
+ name = "nextcloud-${version}";
+ version = "14.0.1";
src = fetchurl {
url = "https://download.nextcloud.com/server/releases/${name}.tar.bz2";
- sha256 = "1m38k5jafz2lniy6fmq17xffkgaqs6rl4w789sqpniva1fb9xz4h";
+ sha256 = "14ymc6fr91735yyc2gqh7c89mbbwsgamhhysf6crp9kp27l83z5a";
};
+ patches = [ (fetchpatch {
+ name = "Mailer-discover-sendmail-path-instead-of-hardcoding-.patch";
+ url = https://github.com/nextcloud/server/pull/11404.patch;
+ sha256 = "1h0cqnfwn735vqrm3yh9nh6a7h6srr9h29p13vywd6rqbcndqjjd";
+ }) ];
+
installPhase = ''
mkdir -p $out/
cp -R . $out/
@@ -17,7 +23,7 @@ stdenv.mkDerivation rec {
meta = {
description = "Sharing solution for files, calendars, contacts and more";
homepage = https://nextcloud.com;
- maintainers = with stdenv.lib.maintainers; [ schneefux bachp ];
+ maintainers = with stdenv.lib.maintainers; [ schneefux bachp globin fpletz ];
license = stdenv.lib.licenses.agpl3Plus;
platforms = with stdenv.lib.platforms; unix;
};