1 { config, lib, pkgs, ... }:
6 cfg = config.services.nextcloud;
7 fpm = config.services.phpfpm.pools.nextcloud;
9 jsonFormat = pkgs.formats.json {};
11 defaultPHPSettings = {
12 output_buffering = "0";
13 short_open_tag = "Off";
15 error_reporting = "E_ALL & ~E_DEPRECATED & ~E_STRICT";
16 display_errors = "stderr";
17 "opcache.interned_strings_buffer" = "8";
18 "opcache.max_accelerated_files" = "10000";
19 "opcache.memory_consumption" = "128";
20 "opcache.revalidate_freq" = "1";
21 "opcache.fast_shutdown" = "1";
22 "openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt";
23 catch_workers_output = "yes";
27 # default apps bundled with pkgs.nextcloudXX, e.g. files, contacts
32 # apps installed via cfg.extraApps
34 enabled = cfg.extraApps != { };
35 linkTarget = pkgs.linkFarm "nix-apps"
36 (mapAttrsToList (name: path: { inherit name path; }) cfg.extraApps);
39 # apps installed via the app store.
41 enabled = cfg.appstoreEnable == null || cfg.appstoreEnable;
42 linkTarget = "${cfg.home}/store-apps";
47 webroot = pkgs.runCommandLocal
48 "${cfg.package.name or "nextcloud"}-with-apps"
52 ln -sfv "${cfg.package}"/* "$out"
54 (mapAttrsToList (name: store: optionalString (store.enabled && store?linkTarget) ''
55 if [ -e "$out"/${name} ]; then
56 echo "Didn't expect ${name} already in $out!"
59 ln -sfTv ${store.linkTarget} "$out"/${name}
63 inherit (cfg) datadir;
65 phpPackage = cfg.phpPackage.buildEnv {
66 extensions = { enabled, all }:
68 ++ [ bz2 intl sodium ] # recommended
69 ++ optional cfg.enableImagemagick imagick
70 # Optionally enabled depending on caching settings
71 ++ optional cfg.caching.apcu apcu
72 ++ optional cfg.caching.redis redis
73 ++ optional cfg.caching.memcached memcached
75 ++ cfg.phpExtraExtensions all; # Enabled by user
76 extraConfig = toKeyValue cfg.phpOptions;
79 toKeyValue = generators.toKeyValue {
80 mkKeyValue = generators.mkKeyValueDefault {} " = ";
83 phpCli = concatStringsSep " " ([
84 "${getExe phpPackage}"
85 ] ++ optionals (cfg.cli.memoryLimit != null) [
86 "-dmemory_limit=${cfg.cli.memoryLimit}"
89 occ = pkgs.writeScriptBin "nextcloud-occ" ''
90 #! ${pkgs.runtimeShell}
93 if [[ "$USER" != nextcloud ]]; then
94 sudo='exec /run/wrappers/bin/sudo -u nextcloud'
96 $sudo ${pkgs.coreutils}/bin/env \
97 NEXTCLOUD_CONFIG_DIR="${datadir}/config" \
102 inherit (config.system) stateVersion;
104 mysqlLocal = cfg.database.createLocally && cfg.config.dbtype == "mysql";
105 pgsqlLocal = cfg.database.createLocally && cfg.config.dbtype == "pgsql";
107 nextcloudGreaterOrEqualThan = versionAtLeast cfg.package.version;
108 nextcloudOlderThan = versionOlder cfg.package.version;
110 # https://github.com/nextcloud/documentation/pull/11179
111 ocmProviderIsNotAStaticDirAnymore = nextcloudGreaterOrEqualThan "27.1.2"
112 || (nextcloudOlderThan "27.0.0" && nextcloudGreaterOrEqualThan "26.0.8");
116 requiresReadSecretFunction = c.dbpassFile != null || c.objectstore.s3.enable;
117 objectstoreConfig = let s3 = c.objectstore.s3; in optionalString s3.enable ''
119 'class' => '\\OC\\Files\\ObjectStore\\S3',
121 'bucket' => '${s3.bucket}',
122 'autocreate' => ${boolToString s3.autocreate},
123 'key' => '${s3.key}',
124 'secret' => nix_read_secret('${s3.secretFile}'),
125 ${optionalString (s3.hostname != null) "'hostname' => '${s3.hostname}',"}
126 ${optionalString (s3.port != null) "'port' => ${toString s3.port},"}
127 'use_ssl' => ${boolToString s3.useSsl},
128 ${optionalString (s3.region != null) "'region' => '${s3.region}',"}
129 'use_path_style' => ${boolToString s3.usePathStyle},
130 ${optionalString (s3.sseCKeyFile != null) "'sse_c_key' => nix_read_secret('${s3.sseCKeyFile}'),"}
134 showAppStoreSetting = cfg.appstoreEnable != null || cfg.extraApps != {};
135 renderedAppStoreSetting =
137 x = cfg.appstoreEnable;
139 if x == null then "false"
141 mkAppStoreConfig = name: { enabled, writable, ... }: optionalString enabled ''
142 [ 'path' => '${webroot}/${name}', 'url' => '/${name}', 'writable' => ${boolToString writable} ],
144 in pkgs.writeText "nextcloud-config.php" ''
146 ${optionalString requiresReadSecretFunction ''
147 function nix_read_secret($file) {
148 if (!file_exists($file)) {
149 throw new \RuntimeException(sprintf(
150 "Cannot start Nextcloud, secret file %s set by NixOS doesn't seem to "
151 . "exist! Please make sure that the file exists and has appropriate "
152 . "permissions for user & group 'nextcloud'!",
156 return trim(file_get_contents($file));
158 function nix_decode_json_file($file, $error) {
159 if (!file_exists($file)) {
160 throw new \RuntimeException(sprintf($error, $file));
162 $decoded = json_decode(file_get_contents($file), true);
164 if (json_last_error() !== JSON_ERROR_NONE) {
165 throw new \RuntimeException(sprintf("Cannot decode %s, because: %s", $file, json_last_error_msg()));
172 ${concatStrings (mapAttrsToList mkAppStoreConfig appStores)}
174 ${optionalString (showAppStoreSetting) "'appstoreenabled' => ${renderedAppStoreSetting},"}
175 ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"}
176 ${optionalString (c.dbname != null) "'dbname' => '${c.dbname}',"}
177 ${optionalString (c.dbhost != null) "'dbhost' => '${c.dbhost}',"}
178 ${optionalString (c.dbuser != null) "'dbuser' => '${c.dbuser}',"}
179 ${optionalString (c.dbtableprefix != null) "'dbtableprefix' => '${toString c.dbtableprefix}',"}
180 ${optionalString (c.dbpassFile != null) ''
181 'dbpassword' => nix_read_secret(
186 'dbtype' => '${c.dbtype}',
190 $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file(
191 "${jsonFormat.generate "nextcloud-settings.json" cfg.settings}",
192 "impossible: this should never happen (decoding generated settings file %s failed)"
195 ${optionalString (cfg.secretFile != null) ''
196 $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file(
198 "Cannot start Nextcloud, secrets file %s set by NixOS doesn't exist!"
205 (mkRenamedOptionModule
206 [ "services" "nextcloud" "cron" "memoryLimit" ]
207 [ "services" "nextcloud" "cli" "memoryLimit" ])
208 (mkRemovedOptionModule [ "services" "nextcloud" "enableBrokenCiphersForSSE" ] ''
209 This option has no effect since there's no supported Nextcloud version packaged here
210 using OpenSSL for RC4 SSE.
212 (mkRemovedOptionModule [ "services" "nextcloud" "config" "dbport" ] ''
213 Add port to services.nextcloud.config.dbhost instead.
215 (mkRenamedOptionModule
216 [ "services" "nextcloud" "logLevel" ] [ "services" "nextcloud" "settings" "loglevel" ])
217 (mkRenamedOptionModule
218 [ "services" "nextcloud" "logType" ] [ "services" "nextcloud" "settings" "log_type" ])
219 (mkRenamedOptionModule
220 [ "services" "nextcloud" "config" "defaultPhoneRegion" ] [ "services" "nextcloud" "settings" "default_phone_region" ])
221 (mkRenamedOptionModule
222 [ "services" "nextcloud" "config" "overwriteProtocol" ] [ "services" "nextcloud" "settings" "overwriteprotocol" ])
223 (mkRenamedOptionModule
224 [ "services" "nextcloud" "skeletonDirectory" ] [ "services" "nextcloud" "settings" "skeletondirectory" ])
225 (mkRenamedOptionModule
226 [ "services" "nextcloud" "globalProfiles" ] [ "services" "nextcloud" "settings" "profile.enabled" ])
227 (mkRenamedOptionModule
228 [ "services" "nextcloud" "config" "extraTrustedDomains" ] [ "services" "nextcloud" "settings" "trusted_domains" ])
229 (mkRenamedOptionModule
230 [ "services" "nextcloud" "config" "trustedProxies" ] [ "services" "nextcloud" "settings" "trusted_proxies" ])
231 (mkRenamedOptionModule ["services" "nextcloud" "extraOptions" ] [ "services" "nextcloud" "settings" ])
234 options.services.nextcloud = {
235 enable = mkEnableOption "nextcloud";
237 hostName = mkOption {
239 description = "FQDN for the nextcloud instance.";
243 default = "/var/lib/nextcloud";
244 description = "Storage path of nextcloud.";
248 default = config.services.nextcloud.home;
249 defaultText = literalExpression "config.services.nextcloud.home";
251 Nextcloud's data storage path. Will be [](#opt-services.nextcloud.home) by default.
252 This folder will be populated with a config.php file and a data folder which contains the state of the instance (excluding the database).";
254 example = "/mnt/nextcloud-file";
256 extraApps = mkOption {
257 type = types.attrsOf types.package;
260 Extra apps to install. Should be an attrSet of appid to packages generated by fetchNextcloudApp.
261 The appid must be identical to the "id" value in the apps appinfo/info.xml.
262 Using this will disable the appstore to prevent Nextcloud from updating these apps (see [](#opt-services.nextcloud.appstoreEnable)).
264 example = literalExpression ''
266 inherit (pkgs.nextcloud25Packages.apps) mail calendar contact;
267 phonetrack = pkgs.fetchNextcloudApp {
269 sha256 = "0qf366vbahyl27p9mshfma1as4nvql6w75zy2zk5xwwbp343vsbc";
270 url = "https://gitlab.com/eneiluj/phonetrack-oc/-/wikis/uploads/931aaaf8dca24bf31a7e169a83c17235/phonetrack-0.6.9.tar.gz";
276 extraAppsEnable = mkOption {
280 Automatically enable the apps in [](#opt-services.nextcloud.extraApps) every time Nextcloud starts.
281 If set to false, apps need to be enabled in the Nextcloud web user interface or with `nextcloud-occ app:enable`.
284 appstoreEnable = mkOption {
285 type = types.nullOr types.bool;
289 Allow the installation and updating of apps from the Nextcloud appstore.
290 Enabled by default unless there are packages in [](#opt-services.nextcloud.extraApps).
291 Set this to true to force enable the store even if [](#opt-services.nextcloud.extraApps) is used.
292 Set this to false to disable the installation of apps from the global appstore. App management is always enabled regardless of this setting.
298 description = "Use HTTPS for generated links.";
301 type = types.package;
302 description = "Which package to use for the Nextcloud instance.";
303 relatedPackages = [ "nextcloud28" "nextcloud29" ];
305 phpPackage = mkPackageOption pkgs "php" {
309 maxUploadSize = mkOption {
313 The upload limit for files. This changes the relevant options
314 in php.ini and nginx if enabled.
318 webfinger = mkOption {
322 Enable this option if you plan on using the webfinger plugin.
323 The appropriate nginx rewrite rules will be added to your configuration.
327 phpExtraExtensions = mkOption {
328 type = with types; functionTo (listOf package);
330 defaultText = literalExpression "all: []";
332 Additional PHP extensions to use for Nextcloud.
333 By default, only extensions necessary for a vanilla Nextcloud installation are enabled,
334 but you may choose from the list of available extensions and add further ones.
335 This is sometimes necessary to be able to install a certain Nextcloud app that has additional requirements.
337 example = literalExpression ''
338 all: [ all.pdlib all.bz2 ]
342 phpOptions = mkOption {
343 type = with types; attrsOf (oneOf [ str int ]);
344 defaultText = literalExpression (generators.toPretty { } defaultPHPSettings);
346 Options for PHP's php.ini file for nextcloud.
348 Please note that this option is _additive_ on purpose while the
349 attribute values inside the default are option defaults: that means that
353 services.nextcloud.phpOptions."opcache.interned_strings_buffer" = "23";
357 will override the `php.ini` option `opcache.interned_strings_buffer` without
358 discarding the rest of the defaults.
360 Overriding all of `phpOptions` (including `upload_max_filesize`, `post_max_size`
361 and `memory_limit` which all point to [](#opt-services.nextcloud.maxUploadSize)
362 by default) can be done like this:
366 services.nextcloud.phpOptions = lib.mkForce {
374 poolSettings = mkOption {
375 type = with types; attrsOf (oneOf [ str int bool ]);
378 "pm.max_children" = "32";
379 "pm.start_servers" = "2";
380 "pm.min_spare_servers" = "2";
381 "pm.max_spare_servers" = "4";
382 "pm.max_requests" = "500";
385 Options for nextcloud's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
389 poolConfig = mkOption {
390 type = types.nullOr types.lines;
393 Options for Nextcloud's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
397 fastcgiTimeout = mkOption {
401 FastCGI timeout for database connection in seconds.
407 createLocally = mkOption {
411 Whether to create the database and database user locally.
419 type = types.enum [ "sqlite" "pgsql" "mysql" ];
421 description = "Database type.";
424 type = types.nullOr types.str;
425 default = "nextcloud";
426 description = "Database name.";
429 type = types.nullOr types.str;
430 default = "nextcloud";
431 description = "Database user.";
433 dbpassFile = mkOption {
434 type = types.nullOr types.str;
437 The full path to a file that contains the database password.
441 type = types.nullOr types.str;
443 if pgsqlLocal then "/run/postgresql"
444 else if mysqlLocal then "localhost:/run/mysqld/mysqld.sock"
446 defaultText = "localhost";
447 example = "localhost:5000";
449 Database host (+port) or socket path.
450 If [](#opt-services.nextcloud.database.createLocally) is true and
451 [](#opt-services.nextcloud.config.dbtype) is either `pgsql` or `mysql`,
452 defaults to the correct Unix socket instead.
455 dbtableprefix = mkOption {
456 type = types.nullOr types.str;
459 Table prefix in Nextcloud's database.
461 __Note:__ since Nextcloud 20 it's not an option anymore to create a database
462 schema with a custom table prefix. This option only exists for backwards compatibility
463 with installations that were originally provisioned with Nextcloud <20.
466 adminuser = mkOption {
470 Username for the admin account. The username is only set during the
471 initial setup of Nextcloud! Since the username also acts as unique
472 ID internally, it cannot be changed later!
475 adminpassFile = mkOption {
478 The full path to a file that contains the admin's password. Must be
479 readable by user `nextcloud`. The password is set only in the initial
480 setup of Nextcloud by the systemd service `nextcloud-setup.service`.
485 enable = mkEnableOption ''
486 S3 object storage as primary storage.
488 This mounts a bucket on an Amazon S3 object storage or compatible
489 implementation into the virtual filesystem.
491 Further details about this feature can be found in the
492 [upstream documentation](https://docs.nextcloud.com/server/22/admin_manual/configuration_files/primary_storage.html)
496 example = "nextcloud";
498 The name of the S3 bucket.
501 autocreate = mkOption {
504 Create the objectstore if it does not exist.
509 example = "EJ39ITYZEUH5BGWDRUFY";
511 The access key for the S3 bucket.
514 secretFile = mkOption {
516 example = "/var/nextcloud-objectstore-s3-secret";
518 The full path to a file that contains the access secret. Must be
519 readable by user `nextcloud`.
522 hostname = mkOption {
523 type = types.nullOr types.str;
525 example = "example.com";
527 Required for some non-Amazon implementations.
531 type = types.nullOr types.port;
534 Required for some non-Amazon implementations.
541 Use SSL for objectstore access.
545 type = types.nullOr types.str;
549 Required for some non-Amazon implementations.
552 usePathStyle = mkOption {
556 Required for some non-Amazon S3 implementations.
558 Ordinarily, requests will be made with
559 `http://bucket.hostname.domain/`, but with path style
560 enabled requests are made with
561 `http://hostname.domain/bucket` instead.
564 sseCKeyFile = mkOption {
565 type = types.nullOr types.path;
567 example = "/var/nextcloud-objectstore-s3-sse-c-key";
569 If provided this is the full path to a file that contains the key
570 to enable [server-side encryption with customer-provided keys][1]
573 The file must contain a random 32-byte key encoded as a base64
574 string, e.g. generated with the command
577 openssl rand 32 | base64
580 Must be readable by user `nextcloud`.
582 [1]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerSideEncryptionCustomerKeys.html
589 enableImagemagick = mkEnableOption ''
590 the ImageMagick module for PHP.
591 This is used by the theming app and for generating previews of certain images (e.g. SVG and HEIF).
592 You may want to disable it for increased security. In that case, previews will still be available
593 for some images (e.g. JPEG and PNG).
594 See <https://github.com/nextcloud/server/issues/13099>
599 configureRedis = lib.mkOption {
600 type = lib.types.bool;
601 default = config.services.nextcloud.notify_push.enable;
602 defaultText = literalExpression "config.services.nextcloud.notify_push.enable";
604 Whether to configure Nextcloud to use the recommended Redis settings for small instances.
607 The `notify_push` app requires Redis to be configured. If this option is turned off, this must be configured manually.
617 Whether to load the APCu module into PHP.
624 Whether to load the Redis module into PHP.
625 You still need to enable Redis in your config.php.
626 See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
629 memcached = mkOption {
633 Whether to load the Memcached module into PHP.
634 You still need to enable Memcached in your config.php.
635 See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
644 Run a regular auto-update of all apps installed from the Nextcloud app store.
648 type = with types; either str (listOf str);
649 default = "05:00:00";
650 example = "Sun 14:00:00";
652 When to run the update. See `systemd.services.<name>.startAt`.
657 type = types.package;
659 defaultText = literalMD "generated script";
661 The nextcloud-occ program preconfigured to target this Nextcloud instance.
665 settings = mkOption {
666 type = types.submodule {
667 freeformType = jsonFormat.type;
670 loglevel = mkOption {
671 type = types.ints.between 0 4;
674 Log level value between 0 (DEBUG) and 4 (FATAL).
676 - 0 (debug): Log all activity.
678 - 1 (info): Log activity such as user logins and file activities, plus warnings, errors, and fatal errors.
680 - 2 (warn): Log successful operations, as well as warnings of potential problems, errors and fatal errors.
682 - 3 (error): Log failed operations and fatal errors.
684 - 4 (fatal): Log only fatal errors that cause the server to stop.
687 log_type = mkOption {
688 type = types.enum [ "errorlog" "file" "syslog" "systemd" ];
691 Logging backend to use.
692 systemd requires the php-systemd package to be added to services.nextcloud.phpExtraExtensions.
693 See the [nextcloud documentation](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/logging_configuration.html) for details.
696 skeletondirectory = mkOption {
700 The directory where the skeleton files are located. These files will be
701 copied to the data directory of new users. Leave empty to not copy any
705 trusted_domains = mkOption {
706 type = types.listOf types.str;
709 Trusted domains, from which the nextcloud installation will be
710 accessible. You don't need to add
711 `services.nextcloud.hostname` here.
714 trusted_proxies = mkOption {
715 type = types.listOf types.str;
718 Trusted proxies, to provide if the nextcloud installation is being
719 proxied to secure against e.g. spoofing.
722 overwriteprotocol = mkOption {
723 type = types.enum [ "" "http" "https" ];
727 Force Nextcloud to always use HTTP or HTTPS i.e. for link generation.
728 Nextcloud uses the currently used protocol by default, but when
729 behind a reverse-proxy, it may use `http` for everything although
730 Nextcloud may be served via HTTPS.
733 default_phone_region = mkOption {
738 An [ISO 3166-1](https://www.iso.org/iso-3166-country-codes.html)
739 country code which replaces automatic phone-number detection
740 without a country code.
742 As an example, with `DE` set as the default phone region,
743 the `+49` prefix can be omitted for phone numbers.
746 "profile.enabled" = mkEnableOption "global profiles" // {
748 Makes user-profiles globally available under `nextcloud.tld/u/user.name`.
749 Even though it's enabled by default in Nextcloud, it must be explicitly enabled
750 here because it has the side-effect that personal information is even accessible to
751 unauthenticated users by default.
752 By default, the following properties are set to “Show to everyone”
753 if this flag is enabled:
762 Only has an effect in Nextcloud 23 and later.
769 Extra options which should be appended to Nextcloud's config.php file.
771 example = literalExpression '' {
773 host = "/run/redis/redis.sock";
782 secretFile = mkOption {
783 type = types.nullOr types.str;
786 Secret options which will be appended to Nextcloud's config.php file (written as JSON, in the same
787 form as the [](#opt-services.nextcloud.settings) option), for example
788 `{"redis":{"password":"secret"}}`.
793 recommendedHttpHeaders = mkOption {
796 description = "Enable additional recommended HTTP response headers";
798 hstsMaxAge = mkOption {
799 type = types.ints.positive;
802 Value for the `max-age` directive of the HTTP
803 `Strict-Transport-Security` header.
805 See section 6.1.1 of IETF RFC 6797 for detailed information on this
806 directive and header.
811 cli.memoryLimit = mkOption {
812 type = types.nullOr types.str;
816 The `memory_limit` of PHP is equal to [](#opt-services.nextcloud.maxUploadSize).
817 The value can be customized for `nextcloud-cron.service` using this option.
822 config = mkIf cfg.enable (mkMerge [
825 upgradeWarning = major: nixos:
827 A legacy Nextcloud install (from before NixOS ${nixos}) may be installed.
829 After nextcloud${toString major} is installed successfully, you can safely upgrade
830 to ${toString (major + 1)}. The latest version available is Nextcloud${toString latest}.
832 Please note that Nextcloud doesn't support upgrades across multiple major versions
833 (i.e. an upgrade from 16 is possible to 17, but not 16 to 18).
835 The package can be upgraded by explicitly declaring the service-option
836 `services.nextcloud.package`.
839 in (optional (cfg.poolConfig != null) ''
840 Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release.
841 Please migrate your configuration to config.services.nextcloud.poolSettings.
843 ++ (optional (cfg.config.dbtableprefix != null) ''
844 Using `services.nextcloud.config.dbtableprefix` is deprecated. Fresh installations with this
845 option set are not allowed anymore since v20.
847 If you have an existing installation with a custom table prefix, make sure it is
848 set correctly in `config.php` and remove the option from your NixOS config.
850 ++ (optional (versionOlder cfg.package.version "25") (upgradeWarning 24 "22.11"))
851 ++ (optional (versionOlder cfg.package.version "26") (upgradeWarning 25 "23.05"))
852 ++ (optional (versionOlder cfg.package.version "27") (upgradeWarning 26 "23.11"))
853 ++ (optional (versionOlder cfg.package.version "28") (upgradeWarning 27 "24.05"))
854 ++ (optional (versionOlder cfg.package.version "29") (upgradeWarning 28 "24.11"));
856 services.nextcloud.package = with pkgs;
860 The `pkgs.nextcloud`-attribute has been removed. If it's supposed to be the default
861 nextcloud defined in an overlay, please set `services.nextcloud.package` to
864 else if versionOlder stateVersion "24.05" then nextcloud27
868 services.nextcloud.phpPackage =
869 if versionOlder cfg.package.version "29" then pkgs.php82
872 services.nextcloud.phpOptions = mkMerge [
873 (mapAttrs (const mkOptionDefault) defaultPHPSettings)
875 upload_max_filesize = cfg.maxUploadSize;
876 post_max_size = cfg.maxUploadSize;
877 memory_limit = cfg.maxUploadSize;
879 (mkIf cfg.caching.apcu {
880 "apc.enable_cli" = "1";
886 { assertion = cfg.database.createLocally -> cfg.config.dbpassFile == null;
888 Using `services.nextcloud.database.createLocally` with database
889 password authentication is no longer supported.
891 If you use an external database (or want to use password auth for any
892 other reason), set `services.nextcloud.database.createLocally` to
893 `false`. The database won't be managed for you (use `services.mysql`
894 if you want to set it up).
896 If you want this module to manage your nextcloud database for you,
897 unset `services.nextcloud.config.dbpassFile` and
898 `services.nextcloud.config.dbhost` to use socket authentication
904 { systemd.timers.nextcloud-cron = {
905 wantedBy = [ "timers.target" ];
906 after = [ "nextcloud-setup.service" ];
909 OnUnitActiveSec = "5m";
910 Unit = "nextcloud-cron.service";
914 systemd.tmpfiles.rules = map (dir: "d ${dir} 0750 nextcloud nextcloud - -") [
918 "${cfg.home}/store-apps"
920 "L+ ${datadir}/config/override.config.php - - - - ${overrideConfig}"
924 # When upgrading the Nextcloud package, Nextcloud can report errors such as
925 # "The files of the app [all apps in /var/lib/nextcloud/apps] were not replaced correctly"
926 # Restarting phpfpm on Nextcloud package update fixes these issues (but this is a workaround).
927 phpfpm-nextcloud.restartTriggers = [ webroot overrideConfig ];
929 nextcloud-setup = let
932 mkExport = { arg, value }: "export ${arg}=${value}";
935 value = if c.dbpassFile != null
936 then ''"$(<"${toString c.dbpassFile}")"''
941 value = ''"$(<"${toString c.adminpassFile}")"'';
943 installFlags = concatStringsSep " \\\n "
944 (mapAttrsToList (k: v: "${k} ${toString v}") {
945 "--database" = ''"${c.dbtype}"'';
946 # The following attributes are optional depending on the type of
947 # database. Those that evaluate to null on the left hand side
949 ${if c.dbname != null then "--database-name" else null} = ''"${c.dbname}"'';
950 ${if c.dbhost != null then "--database-host" else null} = ''"${c.dbhost}"'';
951 ${if c.dbuser != null then "--database-user" else null} = ''"${c.dbuser}"'';
952 "--database-pass" = "\"\$${dbpass.arg}\"";
953 "--admin-user" = ''"${c.adminuser}"'';
954 "--admin-pass" = "\"\$${adminpass.arg}\"";
955 "--data-dir" = ''"${datadir}/data"'';
959 ${mkExport adminpass}
960 ${occ}/bin/nextcloud-occ maintenance:install \
963 occSetTrustedDomainsCmd = concatStringsSep "\n" (imap0
965 ${occ}/bin/nextcloud-occ config:system:set trusted_domains \
966 ${toString i} --value="${toString v}"
967 '') ([ cfg.hostName ] ++ cfg.settings.trusted_domains));
970 wantedBy = [ "multi-user.target" ];
971 wants = [ "nextcloud-update-db.service" ];
972 before = [ "phpfpm-nextcloud.service" ];
973 after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
974 requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
976 restartTriggers = [ overrideConfig ];
978 ${optionalString (c.dbpassFile != null) ''
979 if [ ! -r "${c.dbpassFile}" ]; then
980 echo "dbpassFile ${c.dbpassFile} is not readable by nextcloud:nextcloud! Aborting..."
983 if [ -z "$(<${c.dbpassFile})" ]; then
984 echo "dbpassFile ${c.dbpassFile} is empty!"
988 if [ ! -r "${c.adminpassFile}" ]; then
989 echo "adminpassFile ${c.adminpassFile} is not readable by nextcloud:nextcloud! Aborting..."
992 if [ -z "$(<${c.adminpassFile})" ]; then
993 echo "adminpassFile ${c.adminpassFile} is empty!"
997 ${concatMapStrings (name: ''
998 if [ -d "${cfg.home}"/${name} ]; then
999 echo "Cleaning up ${name}; these are now bundled in the webroot store-path!"
1000 rm -r "${cfg.home}"/${name}
1002 '') [ "nix-apps" "apps" ]}
1004 # Do not install if already installed
1005 if [[ ! -e ${datadir}/config/config.php ]]; then
1009 ${occ}/bin/nextcloud-occ upgrade
1011 ${occ}/bin/nextcloud-occ config:system:delete trusted_domains
1013 ${optionalString (cfg.extraAppsEnable && cfg.extraApps != { }) ''
1014 # Try to enable apps
1015 ${occ}/bin/nextcloud-occ app:enable ${concatStringsSep " " (attrNames cfg.extraApps)}
1018 ${occSetTrustedDomainsCmd}
1020 serviceConfig.Type = "oneshot";
1021 serviceConfig.User = "nextcloud";
1022 # On Nextcloud ≥ 26, it is not necessary to patch the database files to prevent
1023 # an automatic creation of the database user.
1024 environment.NC_setup_create_db_user = lib.mkIf (nextcloudGreaterOrEqualThan "26") "false";
1027 after = [ "nextcloud-setup.service" ];
1028 environment.NEXTCLOUD_CONFIG_DIR = "${datadir}/config";
1032 ExecCondition = "${phpCli} -f ${webroot}/occ status -e";
1033 ExecStart = "${phpCli} -f ${webroot}/cron.php";
1034 KillMode = "process";
1037 nextcloud-update-plugins = mkIf cfg.autoUpdateApps.enable {
1038 after = [ "nextcloud-setup.service" ];
1041 ExecStart = "${occ}/bin/nextcloud-occ app:update --all";
1044 startAt = cfg.autoUpdateApps.startAt;
1046 nextcloud-update-db = {
1047 after = [ "nextcloud-setup.service" ];
1048 environment.NEXTCLOUD_CONFIG_DIR = "${datadir}/config";
1050 ${occ}/bin/nextcloud-occ db:add-missing-columns
1051 ${occ}/bin/nextcloud-occ db:add-missing-indices
1052 ${occ}/bin/nextcloud-occ db:add-missing-primary-keys
1057 ExecCondition = "${phpCli} -f ${webroot}/occ status -e";
1065 group = "nextcloud";
1066 phpPackage = phpPackage;
1068 NEXTCLOUD_CONFIG_DIR = "${datadir}/config";
1069 PATH = "/run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin";
1071 settings = mapAttrs (name: mkDefault) {
1072 "listen.owner" = config.services.nginx.user;
1073 "listen.group" = config.services.nginx.group;
1074 } // cfg.poolSettings;
1075 extraConfig = cfg.poolConfig;
1079 users.users.nextcloud = {
1080 home = "${cfg.home}";
1081 group = "nextcloud";
1082 isSystemUser = true;
1084 users.groups.nextcloud.members = [ "nextcloud" config.services.nginx.user ];
1086 environment.systemPackages = [ occ ];
1088 services.mysql = lib.mkIf mysqlLocal {
1090 package = lib.mkDefault pkgs.mariadb;
1091 ensureDatabases = [ cfg.config.dbname ];
1093 name = cfg.config.dbuser;
1094 ensurePermissions = { "${cfg.config.dbname}.*" = "ALL PRIVILEGES"; };
1098 services.postgresql = mkIf pgsqlLocal {
1100 ensureDatabases = [ cfg.config.dbname ];
1102 name = cfg.config.dbuser;
1103 ensureDBOwnership = true;
1107 services.redis.servers.nextcloud = lib.mkIf cfg.configureRedis {
1112 services.nextcloud = {
1113 caching.redis = lib.mkIf cfg.configureRedis true;
1114 settings = mkMerge [({
1115 datadirectory = lib.mkDefault "${datadir}/data";
1116 trusted_domains = [ cfg.hostName ];
1117 }) (lib.mkIf cfg.configureRedis {
1118 "memcache.distributed" = ''\OC\Memcache\Redis'';
1119 "memcache.locking" = ''\OC\Memcache\Redis'';
1121 host = config.services.redis.servers.nextcloud.unixSocket;
1127 services.nginx.enable = mkDefault true;
1129 services.nginx.virtualHosts.${cfg.hostName} = {
1142 if ( $http_user_agent ~ ^DavClnt ) {
1143 return 302 /remote.php/webdav/$is_args$args;
1147 "^~ /.well-known" = {
1150 absolute_redirect off;
1151 location = /.well-known/carddav {
1152 return 301 /remote.php/dav/;
1154 location = /.well-known/caldav {
1155 return 301 /remote.php/dav/;
1157 location ~ ^/\.well-known/(?!acme-challenge|pki-validation) {
1158 return 301 /index.php$request_uri;
1160 try_files $uri $uri/ =404;
1163 "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)" = {
1169 "~ ^/(?:\\.|autotest|occ|issue|indie|db_|console)" = {
1175 "~ \\.php(?:$|/)" = {
1178 # legacy support (i.e. static files and directories in cfg.package)
1179 rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[s${optionalString (!ocmProviderIsNotAStaticDirAnymore) "m"}]-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri;
1180 include ${config.services.nginx.package}/conf/fastcgi.conf;
1181 fastcgi_split_path_info ^(.+?\.php)(\\/.*)$;
1182 set $path_info $fastcgi_path_info;
1183 try_files $fastcgi_script_name =404;
1184 fastcgi_param PATH_INFO $path_info;
1185 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
1186 fastcgi_param HTTPS ${if cfg.https then "on" else "off"};
1187 fastcgi_param modHeadersAvailable true;
1188 fastcgi_param front_controller_active true;
1189 fastcgi_pass unix:${fpm.socket};
1190 fastcgi_intercept_errors on;
1191 fastcgi_request_buffering off;
1192 fastcgi_read_timeout ${builtins.toString cfg.fastcgiTimeout}s;
1195 "~ \\.(?:css|js|mjs|svg|gif|png|jpg|jpeg|ico|wasm|tflite|map|html|ttf|bcmap|mp4|webm|ogg|flac)$".extraConfig = ''
1196 try_files $uri /index.php$request_uri;
1200 default_type text/javascript;
1202 location ~ \.wasm$ {
1203 default_type application/wasm;
1206 "~ ^\\/(?:updater|ocs-provider${optionalString (!ocmProviderIsNotAStaticDirAnymore) "|ocm-provider"})(?:$|\\/)".extraConfig = ''
1207 try_files $uri/ =404;
1213 return 301 /remote.php$request_uri;
1219 try_files $uri $uri/ /index.php$request_uri;
1224 index index.php index.html /index.php$request_uri;
1225 ${optionalString (cfg.nginx.recommendedHttpHeaders) ''
1226 add_header X-Content-Type-Options nosniff;
1227 add_header X-XSS-Protection "1; mode=block";
1228 add_header X-Robots-Tag "noindex, nofollow";
1229 add_header X-Download-Options noopen;
1230 add_header X-Permitted-Cross-Domain-Policies none;
1231 add_header X-Frame-Options sameorigin;
1232 add_header Referrer-Policy no-referrer;
1234 ${optionalString (cfg.https) ''
1235 add_header Strict-Transport-Security "max-age=${toString cfg.nginx.hstsMaxAge}; includeSubDomains" always;
1237 client_max_body_size ${cfg.maxUploadSize};
1238 fastcgi_buffers 64 4K;
1239 fastcgi_hide_header X-Powered-By;
1243 gzip_min_length 256;
1244 gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
1245 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;
1247 ${optionalString cfg.webfinger ''
1248 rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
1249 rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;
1256 meta.doc = ./nextcloud.md;