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.runCommand "${cfg.package.name or "nextcloud"}-with-apps" {
48 preferLocalBuild = true;
51 ln -sfv "${cfg.package}"/* "$out"
53 (mapAttrsToList (name: store: optionalString (store.enabled && store?linkTarget) ''
54 if [ -e "$out"/${name} ]; then
55 echo "Didn't expect ${name} already in $out!"
58 ln -sfTv ${store.linkTarget} "$out"/${name}
62 inherit (cfg) datadir;
64 phpPackage = cfg.phpPackage.buildEnv {
65 extensions = { enabled, all }:
67 ++ [ bz2 intl sodium ] # recommended
68 ++ optional cfg.enableImagemagick imagick
69 # Optionally enabled depending on caching settings
70 ++ optional cfg.caching.apcu apcu
71 ++ optional cfg.caching.redis redis
72 ++ optional cfg.caching.memcached memcached
74 ++ cfg.phpExtraExtensions all; # Enabled by user
75 extraConfig = toKeyValue cfg.phpOptions;
78 toKeyValue = generators.toKeyValue {
79 mkKeyValue = generators.mkKeyValueDefault {} " = ";
82 phpCli = concatStringsSep " " ([
83 "${getExe phpPackage}"
84 ] ++ optionals (cfg.cli.memoryLimit != null) [
85 "-dmemory_limit=${cfg.cli.memoryLimit}"
88 occ = pkgs.writeScriptBin "nextcloud-occ" ''
89 #! ${pkgs.runtimeShell}
92 if [[ "$USER" != nextcloud ]]; then
93 sudo='exec /run/wrappers/bin/sudo -u nextcloud'
95 $sudo ${pkgs.coreutils}/bin/env \
96 NEXTCLOUD_CONFIG_DIR="${datadir}/config" \
101 inherit (config.system) stateVersion;
103 mysqlLocal = cfg.database.createLocally && cfg.config.dbtype == "mysql";
104 pgsqlLocal = cfg.database.createLocally && cfg.config.dbtype == "pgsql";
106 nextcloudGreaterOrEqualThan = versionAtLeast cfg.package.version;
107 nextcloudOlderThan = versionOlder cfg.package.version;
109 # https://github.com/nextcloud/documentation/pull/11179
110 ocmProviderIsNotAStaticDirAnymore = nextcloudGreaterOrEqualThan "27.1.2"
111 || (nextcloudOlderThan "27.0.0" && nextcloudGreaterOrEqualThan "26.0.8");
115 requiresReadSecretFunction = c.dbpassFile != null || c.objectstore.s3.enable;
116 objectstoreConfig = let s3 = c.objectstore.s3; in optionalString s3.enable ''
118 'class' => '\\OC\\Files\\ObjectStore\\S3',
120 'bucket' => '${s3.bucket}',
121 'autocreate' => ${boolToString s3.autocreate},
122 'key' => '${s3.key}',
123 'secret' => nix_read_secret('${s3.secretFile}'),
124 ${optionalString (s3.hostname != null) "'hostname' => '${s3.hostname}',"}
125 ${optionalString (s3.port != null) "'port' => ${toString s3.port},"}
126 'use_ssl' => ${boolToString s3.useSsl},
127 ${optionalString (s3.region != null) "'region' => '${s3.region}',"}
128 'use_path_style' => ${boolToString s3.usePathStyle},
129 ${optionalString (s3.sseCKeyFile != null) "'sse_c_key' => nix_read_secret('${s3.sseCKeyFile}'),"}
133 showAppStoreSetting = cfg.appstoreEnable != null || cfg.extraApps != {};
134 renderedAppStoreSetting =
136 x = cfg.appstoreEnable;
138 if x == null then "false"
140 mkAppStoreConfig = name: { enabled, writable, ... }: optionalString enabled ''
141 [ 'path' => '${webroot}/${name}', 'url' => '/${name}', 'writable' => ${boolToString writable} ],
143 in pkgs.writeText "nextcloud-config.php" ''
145 ${optionalString requiresReadSecretFunction ''
146 function nix_read_secret($file) {
147 if (!file_exists($file)) {
148 throw new \RuntimeException(sprintf(
149 "Cannot start Nextcloud, secret file %s set by NixOS doesn't seem to "
150 . "exist! Please make sure that the file exists and has appropriate "
151 . "permissions for user & group 'nextcloud'!",
155 return trim(file_get_contents($file));
157 function nix_decode_json_file($file, $error) {
158 if (!file_exists($file)) {
159 throw new \RuntimeException(sprintf($error, $file));
161 $decoded = json_decode(file_get_contents($file), true);
163 if (json_last_error() !== JSON_ERROR_NONE) {
164 throw new \RuntimeException(sprintf("Cannot decode %s, because: %s", $file, json_last_error_msg()));
171 ${concatStrings (mapAttrsToList mkAppStoreConfig appStores)}
173 ${optionalString (showAppStoreSetting) "'appstoreenabled' => ${renderedAppStoreSetting},"}
174 ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"}
175 ${optionalString (c.dbname != null) "'dbname' => '${c.dbname}',"}
176 ${optionalString (c.dbhost != null) "'dbhost' => '${c.dbhost}',"}
177 ${optionalString (c.dbuser != null) "'dbuser' => '${c.dbuser}',"}
178 ${optionalString (c.dbtableprefix != null) "'dbtableprefix' => '${toString c.dbtableprefix}',"}
179 ${optionalString (c.dbpassFile != null) ''
180 'dbpassword' => nix_read_secret(
185 'dbtype' => '${c.dbtype}',
189 $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file(
190 "${jsonFormat.generate "nextcloud-settings.json" cfg.settings}",
191 "impossible: this should never happen (decoding generated settings file %s failed)"
194 ${optionalString (cfg.secretFile != null) ''
195 $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file(
197 "Cannot start Nextcloud, secrets file %s set by NixOS doesn't exist!"
204 (mkRenamedOptionModule
205 [ "services" "nextcloud" "cron" "memoryLimit" ]
206 [ "services" "nextcloud" "cli" "memoryLimit" ])
207 (mkRemovedOptionModule [ "services" "nextcloud" "enableBrokenCiphersForSSE" ] ''
208 This option has no effect since there's no supported Nextcloud version packaged here
209 using OpenSSL for RC4 SSE.
211 (mkRemovedOptionModule [ "services" "nextcloud" "config" "dbport" ] ''
212 Add port to services.nextcloud.config.dbhost instead.
214 (mkRenamedOptionModule
215 [ "services" "nextcloud" "logLevel" ] [ "services" "nextcloud" "settings" "loglevel" ])
216 (mkRenamedOptionModule
217 [ "services" "nextcloud" "logType" ] [ "services" "nextcloud" "settings" "log_type" ])
218 (mkRenamedOptionModule
219 [ "services" "nextcloud" "config" "defaultPhoneRegion" ] [ "services" "nextcloud" "settings" "default_phone_region" ])
220 (mkRenamedOptionModule
221 [ "services" "nextcloud" "config" "overwriteProtocol" ] [ "services" "nextcloud" "settings" "overwriteprotocol" ])
222 (mkRenamedOptionModule
223 [ "services" "nextcloud" "skeletonDirectory" ] [ "services" "nextcloud" "settings" "skeletondirectory" ])
224 (mkRenamedOptionModule
225 [ "services" "nextcloud" "globalProfiles" ] [ "services" "nextcloud" "settings" "profile.enabled" ])
226 (mkRenamedOptionModule
227 [ "services" "nextcloud" "config" "extraTrustedDomains" ] [ "services" "nextcloud" "settings" "trusted_domains" ])
228 (mkRenamedOptionModule
229 [ "services" "nextcloud" "config" "trustedProxies" ] [ "services" "nextcloud" "settings" "trusted_proxies" ])
230 (mkRenamedOptionModule ["services" "nextcloud" "extraOptions" ] [ "services" "nextcloud" "settings" ])
233 options.services.nextcloud = {
234 enable = mkEnableOption "nextcloud";
236 hostName = mkOption {
238 description = "FQDN for the nextcloud instance.";
242 default = "/var/lib/nextcloud";
243 description = "Storage path of nextcloud.";
247 default = config.services.nextcloud.home;
248 defaultText = literalExpression "config.services.nextcloud.home";
250 Nextcloud's data storage path. Will be [](#opt-services.nextcloud.home) by default.
251 This folder will be populated with a config.php file and a data folder which contains the state of the instance (excluding the database).";
253 example = "/mnt/nextcloud-file";
255 extraApps = mkOption {
256 type = types.attrsOf types.package;
259 Extra apps to install. Should be an attrSet of appid to packages generated by fetchNextcloudApp.
260 The appid must be identical to the "id" value in the apps appinfo/info.xml.
261 Using this will disable the appstore to prevent Nextcloud from updating these apps (see [](#opt-services.nextcloud.appstoreEnable)).
263 example = literalExpression ''
265 inherit (pkgs.nextcloud25Packages.apps) mail calendar contact;
266 phonetrack = pkgs.fetchNextcloudApp {
268 sha256 = "0qf366vbahyl27p9mshfma1as4nvql6w75zy2zk5xwwbp343vsbc";
269 url = "https://gitlab.com/eneiluj/phonetrack-oc/-/wikis/uploads/931aaaf8dca24bf31a7e169a83c17235/phonetrack-0.6.9.tar.gz";
275 extraAppsEnable = mkOption {
279 Automatically enable the apps in [](#opt-services.nextcloud.extraApps) every time Nextcloud starts.
280 If set to false, apps need to be enabled in the Nextcloud web user interface or with `nextcloud-occ app:enable`.
283 appstoreEnable = mkOption {
284 type = types.nullOr types.bool;
288 Allow the installation and updating of apps from the Nextcloud appstore.
289 Enabled by default unless there are packages in [](#opt-services.nextcloud.extraApps).
290 Set this to true to force enable the store even if [](#opt-services.nextcloud.extraApps) is used.
291 Set this to false to disable the installation of apps from the global appstore. App management is always enabled regardless of this setting.
297 description = "Use HTTPS for generated links.";
300 type = types.package;
301 description = "Which package to use for the Nextcloud instance.";
302 relatedPackages = [ "nextcloud28" "nextcloud29" "nextcloud30" ];
304 phpPackage = mkPackageOption pkgs "php" {
308 maxUploadSize = mkOption {
312 The upload limit for files. This changes the relevant options
313 in php.ini and nginx if enabled.
317 webfinger = mkOption {
321 Enable this option if you plan on using the webfinger plugin.
322 The appropriate nginx rewrite rules will be added to your configuration.
326 phpExtraExtensions = mkOption {
327 type = with types; functionTo (listOf package);
329 defaultText = literalExpression "all: []";
331 Additional PHP extensions to use for Nextcloud.
332 By default, only extensions necessary for a vanilla Nextcloud installation are enabled,
333 but you may choose from the list of available extensions and add further ones.
334 This is sometimes necessary to be able to install a certain Nextcloud app that has additional requirements.
336 example = literalExpression ''
337 all: [ all.pdlib all.bz2 ]
341 phpOptions = mkOption {
342 type = with types; attrsOf (oneOf [ str int ]);
343 defaultText = literalExpression (generators.toPretty { } defaultPHPSettings);
345 Options for PHP's php.ini file for nextcloud.
347 Please note that this option is _additive_ on purpose while the
348 attribute values inside the default are option defaults: that means that
352 services.nextcloud.phpOptions."opcache.interned_strings_buffer" = "23";
356 will override the `php.ini` option `opcache.interned_strings_buffer` without
357 discarding the rest of the defaults.
359 Overriding all of `phpOptions` (including `upload_max_filesize`, `post_max_size`
360 and `memory_limit` which all point to [](#opt-services.nextcloud.maxUploadSize)
361 by default) can be done like this:
365 services.nextcloud.phpOptions = lib.mkForce {
373 poolSettings = mkOption {
374 type = with types; attrsOf (oneOf [ str int bool ]);
377 "pm.max_children" = "32";
378 "pm.start_servers" = "2";
379 "pm.min_spare_servers" = "2";
380 "pm.max_spare_servers" = "4";
381 "pm.max_requests" = "500";
384 Options for nextcloud's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
388 poolConfig = mkOption {
389 type = types.nullOr types.lines;
392 Options for Nextcloud's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
396 fastcgiTimeout = mkOption {
400 FastCGI timeout for database connection in seconds.
406 createLocally = mkOption {
410 Whether to create the database and database user locally.
418 type = types.enum [ "sqlite" "pgsql" "mysql" ];
420 description = "Database type.";
423 type = types.nullOr types.str;
424 default = "nextcloud";
425 description = "Database name.";
428 type = types.nullOr types.str;
429 default = "nextcloud";
430 description = "Database user.";
432 dbpassFile = mkOption {
433 type = types.nullOr types.str;
436 The full path to a file that contains the database password.
440 type = types.nullOr types.str;
442 if pgsqlLocal then "/run/postgresql"
443 else if mysqlLocal then "localhost:/run/mysqld/mysqld.sock"
445 defaultText = "localhost";
446 example = "localhost:5000";
448 Database host (+port) or socket path.
449 If [](#opt-services.nextcloud.database.createLocally) is true and
450 [](#opt-services.nextcloud.config.dbtype) is either `pgsql` or `mysql`,
451 defaults to the correct Unix socket instead.
454 dbtableprefix = mkOption {
455 type = types.nullOr types.str;
458 Table prefix in Nextcloud's database.
460 __Note:__ since Nextcloud 20 it's not an option anymore to create a database
461 schema with a custom table prefix. This option only exists for backwards compatibility
462 with installations that were originally provisioned with Nextcloud <20.
465 adminuser = mkOption {
469 Username for the admin account. The username is only set during the
470 initial setup of Nextcloud! Since the username also acts as unique
471 ID internally, it cannot be changed later!
474 adminpassFile = mkOption {
477 The full path to a file that contains the admin's password. Must be
478 readable by user `nextcloud`. The password is set only in the initial
479 setup of Nextcloud by the systemd service `nextcloud-setup.service`.
484 enable = mkEnableOption ''
485 S3 object storage as primary storage.
487 This mounts a bucket on an Amazon S3 object storage or compatible
488 implementation into the virtual filesystem.
490 Further details about this feature can be found in the
491 [upstream documentation](https://docs.nextcloud.com/server/22/admin_manual/configuration_files/primary_storage.html)
495 example = "nextcloud";
497 The name of the S3 bucket.
500 autocreate = mkOption {
503 Create the objectstore if it does not exist.
508 example = "EJ39ITYZEUH5BGWDRUFY";
510 The access key for the S3 bucket.
513 secretFile = mkOption {
515 example = "/var/nextcloud-objectstore-s3-secret";
517 The full path to a file that contains the access secret. Must be
518 readable by user `nextcloud`.
521 hostname = mkOption {
522 type = types.nullOr types.str;
524 example = "example.com";
526 Required for some non-Amazon implementations.
530 type = types.nullOr types.port;
533 Required for some non-Amazon implementations.
540 Use SSL for objectstore access.
544 type = types.nullOr types.str;
548 Required for some non-Amazon implementations.
551 usePathStyle = mkOption {
555 Required for some non-Amazon S3 implementations.
557 Ordinarily, requests will be made with
558 `http://bucket.hostname.domain/`, but with path style
559 enabled requests are made with
560 `http://hostname.domain/bucket` instead.
563 sseCKeyFile = mkOption {
564 type = types.nullOr types.path;
566 example = "/var/nextcloud-objectstore-s3-sse-c-key";
568 If provided this is the full path to a file that contains the key
569 to enable [server-side encryption with customer-provided keys][1]
572 The file must contain a random 32-byte key encoded as a base64
573 string, e.g. generated with the command
576 openssl rand 32 | base64
579 Must be readable by user `nextcloud`.
581 [1]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerSideEncryptionCustomerKeys.html
588 enableImagemagick = mkEnableOption ''
589 the ImageMagick module for PHP.
590 This is used by the theming app and for generating previews of certain images (e.g. SVG and HEIF).
591 You may want to disable it for increased security. In that case, previews will still be available
592 for some images (e.g. JPEG and PNG).
593 See <https://github.com/nextcloud/server/issues/13099>
598 configureRedis = lib.mkOption {
599 type = lib.types.bool;
600 default = config.services.nextcloud.notify_push.enable;
601 defaultText = literalExpression "config.services.nextcloud.notify_push.enable";
603 Whether to configure Nextcloud to use the recommended Redis settings for small instances.
606 The `notify_push` app requires Redis to be configured. If this option is turned off, this must be configured manually.
616 Whether to load the APCu module into PHP.
623 Whether to load the Redis module into PHP.
624 You still need to enable Redis in your config.php.
625 See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
628 memcached = mkOption {
632 Whether to load the Memcached module into PHP.
633 You still need to enable Memcached in your config.php.
634 See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
643 Run a regular auto-update of all apps installed from the Nextcloud app store.
647 type = with types; either str (listOf str);
648 default = "05:00:00";
649 example = "Sun 14:00:00";
651 When to run the update. See `systemd.services.<name>.startAt`.
656 type = types.package;
658 defaultText = literalMD "generated script";
660 The nextcloud-occ program preconfigured to target this Nextcloud instance.
664 settings = mkOption {
665 type = types.submodule {
666 freeformType = jsonFormat.type;
669 loglevel = mkOption {
670 type = types.ints.between 0 4;
673 Log level value between 0 (DEBUG) and 4 (FATAL).
675 - 0 (debug): Log all activity.
677 - 1 (info): Log activity such as user logins and file activities, plus warnings, errors, and fatal errors.
679 - 2 (warn): Log successful operations, as well as warnings of potential problems, errors and fatal errors.
681 - 3 (error): Log failed operations and fatal errors.
683 - 4 (fatal): Log only fatal errors that cause the server to stop.
686 log_type = mkOption {
687 type = types.enum [ "errorlog" "file" "syslog" "systemd" ];
690 Logging backend to use.
691 systemd requires the php-systemd package to be added to services.nextcloud.phpExtraExtensions.
692 See the [nextcloud documentation](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/logging_configuration.html) for details.
695 skeletondirectory = mkOption {
699 The directory where the skeleton files are located. These files will be
700 copied to the data directory of new users. Leave empty to not copy any
704 trusted_domains = mkOption {
705 type = types.listOf types.str;
708 Trusted domains, from which the nextcloud installation will be
709 accessible. You don't need to add
710 `services.nextcloud.hostname` here.
713 trusted_proxies = mkOption {
714 type = types.listOf types.str;
717 Trusted proxies, to provide if the nextcloud installation is being
718 proxied to secure against e.g. spoofing.
721 overwriteprotocol = mkOption {
722 type = types.enum [ "" "http" "https" ];
726 Force Nextcloud to always use HTTP or HTTPS i.e. for link generation.
727 Nextcloud uses the currently used protocol by default, but when
728 behind a reverse-proxy, it may use `http` for everything although
729 Nextcloud may be served via HTTPS.
732 default_phone_region = mkOption {
737 An [ISO 3166-1](https://www.iso.org/iso-3166-country-codes.html)
738 country code which replaces automatic phone-number detection
739 without a country code.
741 As an example, with `DE` set as the default phone region,
742 the `+49` prefix can be omitted for phone numbers.
745 "profile.enabled" = mkEnableOption "global profiles" // {
747 Makes user-profiles globally available under `nextcloud.tld/u/user.name`.
748 Even though it's enabled by default in Nextcloud, it must be explicitly enabled
749 here because it has the side-effect that personal information is even accessible to
750 unauthenticated users by default.
751 By default, the following properties are set to “Show to everyone”
752 if this flag is enabled:
761 Only has an effect in Nextcloud 23 and later.
768 Extra options which should be appended to Nextcloud's config.php file.
770 example = literalExpression '' {
772 host = "/run/redis/redis.sock";
781 secretFile = mkOption {
782 type = types.nullOr types.str;
785 Secret options which will be appended to Nextcloud's config.php file (written as JSON, in the same
786 form as the [](#opt-services.nextcloud.settings) option), for example
787 `{"redis":{"password":"secret"}}`.
792 recommendedHttpHeaders = mkOption {
795 description = "Enable additional recommended HTTP response headers";
797 hstsMaxAge = mkOption {
798 type = types.ints.positive;
801 Value for the `max-age` directive of the HTTP
802 `Strict-Transport-Security` header.
804 See section 6.1.1 of IETF RFC 6797 for detailed information on this
805 directive and header.
810 cli.memoryLimit = mkOption {
811 type = types.nullOr types.str;
815 The `memory_limit` of PHP is equal to [](#opt-services.nextcloud.maxUploadSize).
816 The value can be customized for `nextcloud-cron.service` using this option.
821 config = mkIf cfg.enable (mkMerge [
824 upgradeWarning = major: nixos:
826 A legacy Nextcloud install (from before NixOS ${nixos}) may be installed.
828 After nextcloud${toString major} is installed successfully, you can safely upgrade
829 to ${toString (major + 1)}. The latest version available is Nextcloud${toString latest}.
831 Please note that Nextcloud doesn't support upgrades across multiple major versions
832 (i.e. an upgrade from 16 is possible to 17, but not 16 to 18).
834 The package can be upgraded by explicitly declaring the service-option
835 `services.nextcloud.package`.
838 in (optional (cfg.poolConfig != null) ''
839 Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release.
840 Please migrate your configuration to config.services.nextcloud.poolSettings.
842 ++ (optional (cfg.config.dbtableprefix != null) ''
843 Using `services.nextcloud.config.dbtableprefix` is deprecated. Fresh installations with this
844 option set are not allowed anymore since v20.
846 If you have an existing installation with a custom table prefix, make sure it is
847 set correctly in `config.php` and remove the option from your NixOS config.
849 ++ (optional (versionOlder cfg.package.version "26") (upgradeWarning 25 "23.05"))
850 ++ (optional (versionOlder cfg.package.version "27") (upgradeWarning 26 "23.11"))
851 ++ (optional (versionOlder cfg.package.version "28") (upgradeWarning 27 "24.05"))
852 ++ (optional (versionOlder cfg.package.version "29") (upgradeWarning 28 "24.11"))
853 ++ (optional (versionOlder cfg.package.version "30") (upgradeWarning 29 "24.11"));
855 services.nextcloud.package = with pkgs;
859 The `pkgs.nextcloud`-attribute has been removed. If it's supposed to be the default
860 nextcloud defined in an overlay, please set `services.nextcloud.package` to
863 else if versionOlder stateVersion "24.05" then nextcloud27
864 else if versionOlder stateVersion "24.11" then nextcloud29
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 }: ''
938 value = if c.dbpassFile != null
939 then ''"$(<"${toString c.dbpassFile}")"''
944 value = ''"$(<"${toString c.adminpassFile}")"'';
946 installFlags = concatStringsSep " \\\n "
947 (mapAttrsToList (k: v: "${k} ${toString v}") {
948 "--database" = ''"${c.dbtype}"'';
949 # The following attributes are optional depending on the type of
950 # database. Those that evaluate to null on the left hand side
952 ${if c.dbname != null then "--database-name" else null} = ''"${c.dbname}"'';
953 ${if c.dbhost != null then "--database-host" else null} = ''"${c.dbhost}"'';
954 ${if c.dbuser != null then "--database-user" else null} = ''"${c.dbuser}"'';
955 "--database-pass" = "\"\$${dbpass.arg}\"";
956 "--admin-user" = ''"${c.adminuser}"'';
957 "--admin-pass" = "\"\$${adminpass.arg}\"";
958 "--data-dir" = ''"${datadir}/data"'';
962 ${mkExport adminpass}
963 ${occ}/bin/nextcloud-occ maintenance:install \
966 occSetTrustedDomainsCmd = concatStringsSep "\n" (imap0
968 ${occ}/bin/nextcloud-occ config:system:set trusted_domains \
969 ${toString i} --value="${toString v}"
970 '') ([ cfg.hostName ] ++ cfg.settings.trusted_domains));
973 wantedBy = [ "multi-user.target" ];
974 wants = [ "nextcloud-update-db.service" ];
975 before = [ "phpfpm-nextcloud.service" ];
976 after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
977 requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
979 restartTriggers = [ overrideConfig ];
981 ${optionalString (c.dbpassFile != null) ''
982 if [ ! -r "${c.dbpassFile}" ]; then
983 echo "dbpassFile ${c.dbpassFile} is not readable by nextcloud:nextcloud! Aborting..."
986 if [ -z "$(<${c.dbpassFile})" ]; then
987 echo "dbpassFile ${c.dbpassFile} is empty!"
991 if [ ! -r "${c.adminpassFile}" ]; then
992 echo "adminpassFile ${c.adminpassFile} is not readable by nextcloud:nextcloud! Aborting..."
995 if [ -z "$(<${c.adminpassFile})" ]; then
996 echo "adminpassFile ${c.adminpassFile} is empty!"
1000 ${concatMapStrings (name: ''
1001 if [ -d "${cfg.home}"/${name} ]; then
1002 echo "Cleaning up ${name}; these are now bundled in the webroot store-path!"
1003 rm -r "${cfg.home}"/${name}
1005 '') [ "nix-apps" "apps" ]}
1007 # Do not install if already installed
1008 if [[ ! -e ${datadir}/config/config.php ]]; then
1012 ${occ}/bin/nextcloud-occ upgrade
1014 ${occ}/bin/nextcloud-occ config:system:delete trusted_domains
1016 ${optionalString (cfg.extraAppsEnable && cfg.extraApps != { }) ''
1017 # Try to enable apps
1018 ${occ}/bin/nextcloud-occ app:enable ${concatStringsSep " " (attrNames cfg.extraApps)}
1021 ${occSetTrustedDomainsCmd}
1023 serviceConfig.Type = "oneshot";
1024 serviceConfig.User = "nextcloud";
1025 # On Nextcloud ≥ 26, it is not necessary to patch the database files to prevent
1026 # an automatic creation of the database user.
1027 environment.NC_setup_create_db_user = lib.mkIf (nextcloudGreaterOrEqualThan "26") "false";
1030 after = [ "nextcloud-setup.service" ];
1031 environment.NEXTCLOUD_CONFIG_DIR = "${datadir}/config";
1035 ExecCondition = "${phpCli} -f ${webroot}/occ status -e";
1036 ExecStart = "${phpCli} -f ${webroot}/cron.php";
1037 KillMode = "process";
1040 nextcloud-update-plugins = mkIf cfg.autoUpdateApps.enable {
1041 after = [ "nextcloud-setup.service" ];
1044 ExecStart = "${occ}/bin/nextcloud-occ app:update --all";
1047 startAt = cfg.autoUpdateApps.startAt;
1049 nextcloud-update-db = {
1050 after = [ "nextcloud-setup.service" ];
1051 environment.NEXTCLOUD_CONFIG_DIR = "${datadir}/config";
1053 ${occ}/bin/nextcloud-occ db:add-missing-columns
1054 ${occ}/bin/nextcloud-occ db:add-missing-indices
1055 ${occ}/bin/nextcloud-occ db:add-missing-primary-keys
1060 ExecCondition = "${phpCli} -f ${webroot}/occ status -e";
1068 group = "nextcloud";
1069 phpPackage = phpPackage;
1071 NEXTCLOUD_CONFIG_DIR = "${datadir}/config";
1072 PATH = "/run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin";
1074 settings = mapAttrs (name: mkDefault) {
1075 "listen.owner" = config.services.nginx.user;
1076 "listen.group" = config.services.nginx.group;
1077 } // cfg.poolSettings;
1078 extraConfig = cfg.poolConfig;
1082 users.users.nextcloud = {
1083 home = "${cfg.home}";
1084 group = "nextcloud";
1085 isSystemUser = true;
1087 users.groups.nextcloud.members = [ "nextcloud" config.services.nginx.user ];
1089 environment.systemPackages = [ occ ];
1091 services.mysql = lib.mkIf mysqlLocal {
1093 package = lib.mkDefault pkgs.mariadb;
1094 ensureDatabases = [ cfg.config.dbname ];
1096 name = cfg.config.dbuser;
1097 ensurePermissions = { "${cfg.config.dbname}.*" = "ALL PRIVILEGES"; };
1101 services.postgresql = mkIf pgsqlLocal {
1103 ensureDatabases = [ cfg.config.dbname ];
1105 name = cfg.config.dbuser;
1106 ensureDBOwnership = true;
1110 services.redis.servers.nextcloud = lib.mkIf cfg.configureRedis {
1115 services.nextcloud = {
1116 caching.redis = lib.mkIf cfg.configureRedis true;
1117 settings = mkMerge [({
1118 datadirectory = lib.mkDefault "${datadir}/data";
1119 trusted_domains = [ cfg.hostName ];
1120 }) (lib.mkIf cfg.configureRedis {
1121 "memcache.distributed" = ''\OC\Memcache\Redis'';
1122 "memcache.locking" = ''\OC\Memcache\Redis'';
1124 host = config.services.redis.servers.nextcloud.unixSocket;
1130 services.nginx.enable = mkDefault true;
1132 services.nginx.virtualHosts.${cfg.hostName} = {
1145 if ( $http_user_agent ~ ^DavClnt ) {
1146 return 302 /remote.php/webdav/$is_args$args;
1150 "^~ /.well-known" = {
1153 absolute_redirect off;
1154 location = /.well-known/carddav {
1155 return 301 /remote.php/dav/;
1157 location = /.well-known/caldav {
1158 return 301 /remote.php/dav/;
1160 location ~ ^/\.well-known/(?!acme-challenge|pki-validation) {
1161 return 301 /index.php$request_uri;
1163 try_files $uri $uri/ =404;
1166 "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)" = {
1172 "~ ^/(?:\\.|autotest|occ|issue|indie|db_|console)" = {
1178 "~ \\.php(?:$|/)" = {
1181 # legacy support (i.e. static files and directories in cfg.package)
1182 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;
1183 include ${config.services.nginx.package}/conf/fastcgi.conf;
1184 fastcgi_split_path_info ^(.+?\.php)(\\/.*)$;
1185 set $path_info $fastcgi_path_info;
1186 try_files $fastcgi_script_name =404;
1187 fastcgi_param PATH_INFO $path_info;
1188 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
1189 fastcgi_param HTTPS ${if cfg.https then "on" else "off"};
1190 fastcgi_param modHeadersAvailable true;
1191 fastcgi_param front_controller_active true;
1192 fastcgi_pass unix:${fpm.socket};
1193 fastcgi_intercept_errors on;
1194 fastcgi_request_buffering off;
1195 fastcgi_read_timeout ${builtins.toString cfg.fastcgiTimeout}s;
1198 "~ \\.(?:css|js|mjs|svg|gif|png|jpg|jpeg|ico|wasm|tflite|map|html|ttf|bcmap|mp4|webm|ogg|flac)$".extraConfig = ''
1199 try_files $uri /index.php$request_uri;
1203 default_type text/javascript;
1205 location ~ \.wasm$ {
1206 default_type application/wasm;
1209 "~ ^\\/(?:updater|ocs-provider${optionalString (!ocmProviderIsNotAStaticDirAnymore) "|ocm-provider"})(?:$|\\/)".extraConfig = ''
1210 try_files $uri/ =404;
1216 return 301 /remote.php$request_uri;
1222 try_files $uri $uri/ /index.php$request_uri;
1227 index index.php index.html /index.php$request_uri;
1228 ${optionalString (cfg.nginx.recommendedHttpHeaders) ''
1229 add_header X-Content-Type-Options nosniff;
1230 add_header X-XSS-Protection "1; mode=block";
1231 add_header X-Robots-Tag "noindex, nofollow";
1232 add_header X-Download-Options noopen;
1233 add_header X-Permitted-Cross-Domain-Policies none;
1234 add_header X-Frame-Options sameorigin;
1235 add_header Referrer-Policy no-referrer;
1237 ${optionalString (cfg.https) ''
1238 add_header Strict-Transport-Security "max-age=${toString cfg.nginx.hstsMaxAge}; includeSubDomains" always;
1240 client_max_body_size ${cfg.maxUploadSize};
1241 fastcgi_buffers 64 4K;
1242 fastcgi_hide_header X-Powered-By;
1246 gzip_min_length 256;
1247 gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
1248 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;
1250 ${optionalString cfg.webfinger ''
1251 rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
1252 rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;
1259 meta.doc = ./nextcloud.md;