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" "nextcloud30" ];
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 "26") (upgradeWarning 25 "23.05"))
851 ++ (optional (versionOlder cfg.package.version "27") (upgradeWarning 26 "23.11"))
852 ++ (optional (versionOlder cfg.package.version "28") (upgradeWarning 27 "24.05"))
853 ++ (optional (versionOlder cfg.package.version "29") (upgradeWarning 28 "24.11"))
854 ++ (optional (versionOlder cfg.package.version "30") (upgradeWarning 29 "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
865 else if versionOlder stateVersion "24.11" then nextcloud29
869 services.nextcloud.phpPackage =
870 if versionOlder cfg.package.version "29" then pkgs.php82
873 services.nextcloud.phpOptions = mkMerge [
874 (mapAttrs (const mkOptionDefault) defaultPHPSettings)
876 upload_max_filesize = cfg.maxUploadSize;
877 post_max_size = cfg.maxUploadSize;
878 memory_limit = cfg.maxUploadSize;
880 (mkIf cfg.caching.apcu {
881 "apc.enable_cli" = "1";
887 { assertion = cfg.database.createLocally -> cfg.config.dbpassFile == null;
889 Using `services.nextcloud.database.createLocally` with database
890 password authentication is no longer supported.
892 If you use an external database (or want to use password auth for any
893 other reason), set `services.nextcloud.database.createLocally` to
894 `false`. The database won't be managed for you (use `services.mysql`
895 if you want to set it up).
897 If you want this module to manage your nextcloud database for you,
898 unset `services.nextcloud.config.dbpassFile` and
899 `services.nextcloud.config.dbhost` to use socket authentication
905 { systemd.timers.nextcloud-cron = {
906 wantedBy = [ "timers.target" ];
907 after = [ "nextcloud-setup.service" ];
910 OnUnitActiveSec = "5m";
911 Unit = "nextcloud-cron.service";
915 systemd.tmpfiles.rules = map (dir: "d ${dir} 0750 nextcloud nextcloud - -") [
919 "${cfg.home}/store-apps"
921 "L+ ${datadir}/config/override.config.php - - - - ${overrideConfig}"
925 # When upgrading the Nextcloud package, Nextcloud can report errors such as
926 # "The files of the app [all apps in /var/lib/nextcloud/apps] were not replaced correctly"
927 # Restarting phpfpm on Nextcloud package update fixes these issues (but this is a workaround).
928 phpfpm-nextcloud.restartTriggers = [ webroot overrideConfig ];
930 nextcloud-setup = let
933 mkExport = { arg, value }: ''
939 value = if c.dbpassFile != null
940 then ''"$(<"${toString c.dbpassFile}")"''
945 value = ''"$(<"${toString c.adminpassFile}")"'';
947 installFlags = concatStringsSep " \\\n "
948 (mapAttrsToList (k: v: "${k} ${toString v}") {
949 "--database" = ''"${c.dbtype}"'';
950 # The following attributes are optional depending on the type of
951 # database. Those that evaluate to null on the left hand side
953 ${if c.dbname != null then "--database-name" else null} = ''"${c.dbname}"'';
954 ${if c.dbhost != null then "--database-host" else null} = ''"${c.dbhost}"'';
955 ${if c.dbuser != null then "--database-user" else null} = ''"${c.dbuser}"'';
956 "--database-pass" = "\"\$${dbpass.arg}\"";
957 "--admin-user" = ''"${c.adminuser}"'';
958 "--admin-pass" = "\"\$${adminpass.arg}\"";
959 "--data-dir" = ''"${datadir}/data"'';
963 ${mkExport adminpass}
964 ${occ}/bin/nextcloud-occ maintenance:install \
967 occSetTrustedDomainsCmd = concatStringsSep "\n" (imap0
969 ${occ}/bin/nextcloud-occ config:system:set trusted_domains \
970 ${toString i} --value="${toString v}"
971 '') ([ cfg.hostName ] ++ cfg.settings.trusted_domains));
974 wantedBy = [ "multi-user.target" ];
975 wants = [ "nextcloud-update-db.service" ];
976 before = [ "phpfpm-nextcloud.service" ];
977 after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
978 requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
980 restartTriggers = [ overrideConfig ];
982 ${optionalString (c.dbpassFile != null) ''
983 if [ ! -r "${c.dbpassFile}" ]; then
984 echo "dbpassFile ${c.dbpassFile} is not readable by nextcloud:nextcloud! Aborting..."
987 if [ -z "$(<${c.dbpassFile})" ]; then
988 echo "dbpassFile ${c.dbpassFile} is empty!"
992 if [ ! -r "${c.adminpassFile}" ]; then
993 echo "adminpassFile ${c.adminpassFile} is not readable by nextcloud:nextcloud! Aborting..."
996 if [ -z "$(<${c.adminpassFile})" ]; then
997 echo "adminpassFile ${c.adminpassFile} is empty!"
1001 ${concatMapStrings (name: ''
1002 if [ -d "${cfg.home}"/${name} ]; then
1003 echo "Cleaning up ${name}; these are now bundled in the webroot store-path!"
1004 rm -r "${cfg.home}"/${name}
1006 '') [ "nix-apps" "apps" ]}
1008 # Do not install if already installed
1009 if [[ ! -e ${datadir}/config/config.php ]]; then
1013 ${occ}/bin/nextcloud-occ upgrade
1015 ${occ}/bin/nextcloud-occ config:system:delete trusted_domains
1017 ${optionalString (cfg.extraAppsEnable && cfg.extraApps != { }) ''
1018 # Try to enable apps
1019 ${occ}/bin/nextcloud-occ app:enable ${concatStringsSep " " (attrNames cfg.extraApps)}
1022 ${occSetTrustedDomainsCmd}
1024 serviceConfig.Type = "oneshot";
1025 serviceConfig.User = "nextcloud";
1026 # On Nextcloud ≥ 26, it is not necessary to patch the database files to prevent
1027 # an automatic creation of the database user.
1028 environment.NC_setup_create_db_user = lib.mkIf (nextcloudGreaterOrEqualThan "26") "false";
1031 after = [ "nextcloud-setup.service" ];
1032 environment.NEXTCLOUD_CONFIG_DIR = "${datadir}/config";
1036 ExecCondition = "${phpCli} -f ${webroot}/occ status -e";
1037 ExecStart = "${phpCli} -f ${webroot}/cron.php";
1038 KillMode = "process";
1041 nextcloud-update-plugins = mkIf cfg.autoUpdateApps.enable {
1042 after = [ "nextcloud-setup.service" ];
1045 ExecStart = "${occ}/bin/nextcloud-occ app:update --all";
1048 startAt = cfg.autoUpdateApps.startAt;
1050 nextcloud-update-db = {
1051 after = [ "nextcloud-setup.service" ];
1052 environment.NEXTCLOUD_CONFIG_DIR = "${datadir}/config";
1054 ${occ}/bin/nextcloud-occ db:add-missing-columns
1055 ${occ}/bin/nextcloud-occ db:add-missing-indices
1056 ${occ}/bin/nextcloud-occ db:add-missing-primary-keys
1061 ExecCondition = "${phpCli} -f ${webroot}/occ status -e";
1069 group = "nextcloud";
1070 phpPackage = phpPackage;
1072 NEXTCLOUD_CONFIG_DIR = "${datadir}/config";
1073 PATH = "/run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin";
1075 settings = mapAttrs (name: mkDefault) {
1076 "listen.owner" = config.services.nginx.user;
1077 "listen.group" = config.services.nginx.group;
1078 } // cfg.poolSettings;
1079 extraConfig = cfg.poolConfig;
1083 users.users.nextcloud = {
1084 home = "${cfg.home}";
1085 group = "nextcloud";
1086 isSystemUser = true;
1088 users.groups.nextcloud.members = [ "nextcloud" config.services.nginx.user ];
1090 environment.systemPackages = [ occ ];
1092 services.mysql = lib.mkIf mysqlLocal {
1094 package = lib.mkDefault pkgs.mariadb;
1095 ensureDatabases = [ cfg.config.dbname ];
1097 name = cfg.config.dbuser;
1098 ensurePermissions = { "${cfg.config.dbname}.*" = "ALL PRIVILEGES"; };
1102 services.postgresql = mkIf pgsqlLocal {
1104 ensureDatabases = [ cfg.config.dbname ];
1106 name = cfg.config.dbuser;
1107 ensureDBOwnership = true;
1111 services.redis.servers.nextcloud = lib.mkIf cfg.configureRedis {
1116 services.nextcloud = {
1117 caching.redis = lib.mkIf cfg.configureRedis true;
1118 settings = mkMerge [({
1119 datadirectory = lib.mkDefault "${datadir}/data";
1120 trusted_domains = [ cfg.hostName ];
1121 }) (lib.mkIf cfg.configureRedis {
1122 "memcache.distributed" = ''\OC\Memcache\Redis'';
1123 "memcache.locking" = ''\OC\Memcache\Redis'';
1125 host = config.services.redis.servers.nextcloud.unixSocket;
1131 services.nginx.enable = mkDefault true;
1133 services.nginx.virtualHosts.${cfg.hostName} = {
1146 if ( $http_user_agent ~ ^DavClnt ) {
1147 return 302 /remote.php/webdav/$is_args$args;
1151 "^~ /.well-known" = {
1154 absolute_redirect off;
1155 location = /.well-known/carddav {
1156 return 301 /remote.php/dav/;
1158 location = /.well-known/caldav {
1159 return 301 /remote.php/dav/;
1161 location ~ ^/\.well-known/(?!acme-challenge|pki-validation) {
1162 return 301 /index.php$request_uri;
1164 try_files $uri $uri/ =404;
1167 "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)" = {
1173 "~ ^/(?:\\.|autotest|occ|issue|indie|db_|console)" = {
1179 "~ \\.php(?:$|/)" = {
1182 # legacy support (i.e. static files and directories in cfg.package)
1183 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;
1184 include ${config.services.nginx.package}/conf/fastcgi.conf;
1185 fastcgi_split_path_info ^(.+?\.php)(\\/.*)$;
1186 set $path_info $fastcgi_path_info;
1187 try_files $fastcgi_script_name =404;
1188 fastcgi_param PATH_INFO $path_info;
1189 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
1190 fastcgi_param HTTPS ${if cfg.https then "on" else "off"};
1191 fastcgi_param modHeadersAvailable true;
1192 fastcgi_param front_controller_active true;
1193 fastcgi_pass unix:${fpm.socket};
1194 fastcgi_intercept_errors on;
1195 fastcgi_request_buffering off;
1196 fastcgi_read_timeout ${builtins.toString cfg.fastcgiTimeout}s;
1199 "~ \\.(?:css|js|mjs|svg|gif|png|jpg|jpeg|ico|wasm|tflite|map|html|ttf|bcmap|mp4|webm|ogg|flac)$".extraConfig = ''
1200 try_files $uri /index.php$request_uri;
1204 default_type text/javascript;
1206 location ~ \.wasm$ {
1207 default_type application/wasm;
1210 "~ ^\\/(?:updater|ocs-provider${optionalString (!ocmProviderIsNotAStaticDirAnymore) "|ocm-provider"})(?:$|\\/)".extraConfig = ''
1211 try_files $uri/ =404;
1217 return 301 /remote.php$request_uri;
1223 try_files $uri $uri/ /index.php$request_uri;
1228 index index.php index.html /index.php$request_uri;
1229 ${optionalString (cfg.nginx.recommendedHttpHeaders) ''
1230 add_header X-Content-Type-Options nosniff;
1231 add_header X-XSS-Protection "1; mode=block";
1232 add_header X-Robots-Tag "noindex, nofollow";
1233 add_header X-Download-Options noopen;
1234 add_header X-Permitted-Cross-Domain-Policies none;
1235 add_header X-Frame-Options sameorigin;
1236 add_header Referrer-Policy no-referrer;
1238 ${optionalString (cfg.https) ''
1239 add_header Strict-Transport-Security "max-age=${toString cfg.nginx.hstsMaxAge}; includeSubDomains" always;
1241 client_max_body_size ${cfg.maxUploadSize};
1242 fastcgi_buffers 64 4K;
1243 fastcgi_hide_header X-Powered-By;
1247 gzip_min_length 256;
1248 gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
1249 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;
1251 ${optionalString cfg.webfinger ''
1252 rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
1253 rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;
1260 meta.doc = ./nextcloud.md;