1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9
10let
11 cfg = config.services.nextcloud;
12
13 overridePackage = cfg.package.override {
14 inherit (config.security.pki) caBundle;
15 };
16
17 fpm = config.services.phpfpm.pools.nextcloud;
18
19 jsonFormat = pkgs.formats.json { };
20
21 defaultPHPSettings = {
22 output_buffering = "0";
23 short_open_tag = "Off";
24 expose_php = "Off";
25 error_reporting = "E_ALL & ~E_DEPRECATED & ~E_STRICT";
26 display_errors = "stderr";
27 "opcache.interned_strings_buffer" = "8";
28 "opcache.max_accelerated_files" = "10000";
29 "opcache.memory_consumption" = "128";
30 "opcache.revalidate_freq" = "1";
31 "opcache.fast_shutdown" = "1";
32 "openssl.cafile" = config.security.pki.caBundle;
33 catch_workers_output = "yes";
34 };
35
36 appStores = {
37 # default apps bundled with pkgs.nextcloudXX, e.g. files, contacts
38 apps = {
39 enabled = true;
40 writable = false;
41 };
42 # apps installed via cfg.extraApps
43 nix-apps = {
44 enabled = cfg.extraApps != { };
45 linkTarget = pkgs.linkFarm "nix-apps" (
46 mapAttrsToList (name: path: { inherit name path; }) cfg.extraApps
47 );
48 writable = false;
49 };
50 # apps installed via the app store.
51 store-apps = {
52 enabled = cfg.appstoreEnable == null || cfg.appstoreEnable;
53 linkTarget = "${cfg.home}/store-apps";
54 writable = true;
55 };
56 };
57
58 webroot =
59 pkgs.runCommand "${overridePackage.name or "nextcloud"}-with-apps"
60 {
61 preferLocalBuild = true;
62 }
63 ''
64 mkdir $out
65 ln -sfv "${overridePackage}"/* "$out"
66 ${concatStrings (
67 mapAttrsToList (
68 name: store:
69 optionalString (store.enabled && store ? linkTarget) ''
70 if [ -e "$out"/${name} ]; then
71 echo "Didn't expect ${name} already in $out!"
72 exit 1
73 fi
74 ln -sfTv ${store.linkTarget} "$out"/${name}
75 ''
76 ) appStores
77 )}
78 '';
79
80 inherit (cfg) datadir;
81
82 phpPackage = cfg.phpPackage.buildEnv {
83 extensions =
84 { enabled, all }:
85 (
86 with all;
87 enabled
88 ++ [
89 bz2
90 intl
91 sodium
92 ] # recommended
93 ++ optional cfg.enableImagemagick imagick
94 # Optionally enabled depending on caching settings
95 ++ optional cfg.caching.apcu apcu
96 ++ optional cfg.caching.redis redis
97 ++ optional cfg.caching.memcached memcached
98 )
99 ++ cfg.phpExtraExtensions all; # Enabled by user
100 extraConfig = toKeyValue cfg.phpOptions;
101 };
102
103 toKeyValue = generators.toKeyValue {
104 mkKeyValue = generators.mkKeyValueDefault { } " = ";
105 };
106
107 phpCli = concatStringsSep " " (
108 [
109 "${getExe phpPackage}"
110 ]
111 ++ optionals (cfg.cli.memoryLimit != null) [
112 "-dmemory_limit=${cfg.cli.memoryLimit}"
113 ]
114 );
115
116 # NOTE: The credentials required by all services at runtime, not including things like the
117 # admin password which is only needed by the setup service.
118 runtimeSystemdCredentials =
119 [ ]
120 ++ (lib.optional (cfg.config.dbpassFile != null) "dbpass:${cfg.config.dbpassFile}")
121 ++ (lib.optional (cfg.config.objectstore.s3.enable) "s3_secret:${cfg.config.objectstore.s3.secretFile}")
122 ++ (lib.optional (
123 cfg.config.objectstore.s3.sseCKeyFile != null
124 ) "s3_sse_c_key:${cfg.config.objectstore.s3.sseCKeyFile}")
125 ++ (lib.optional (cfg.secretFile != null) "secret_file:${cfg.secretFile}");
126
127 requiresRuntimeSystemdCredentials = (lib.length runtimeSystemdCredentials) != 0;
128
129 occ = pkgs.writeShellApplication {
130 name = "nextcloud-occ";
131
132 text =
133 let
134 command = ''
135 ${lib.getExe' pkgs.coreutils "env"} \
136 NEXTCLOUD_CONFIG_DIR="${datadir}/config" \
137 ${phpCli} \
138 occ "$@"
139 '';
140 in
141 ''
142 cd ${webroot}
143
144 # NOTE: This is templated at eval time
145 requiresRuntimeSystemdCredentials=${lib.boolToString requiresRuntimeSystemdCredentials}
146
147 # NOTE: This wrapper is both used in the internal nextcloud service units
148 # and by users outside a service context for administration. As such,
149 # when there's an existing CREDENTIALS_DIRECTORY, we inherit it for use
150 # in the nix_read_secret() php function.
151 # When there's no CREDENTIALS_DIRECTORY we try to use systemd-run to
152 # load the credentials just as in a service unit.
153 # NOTE: If there are no credentials that are required at runtime then there's no need
154 # to load any credentials.
155 if [[ $requiresRuntimeSystemdCredentials == true && -z "''${CREDENTIALS_DIRECTORY:-}" ]]; then
156 exec ${lib.getExe' config.systemd.package "systemd-run"} \
157 ${
158 lib.escapeShellArgs (
159 map (credential: "--property=LoadCredential=${credential}") runtimeSystemdCredentials
160 )
161 } \
162 --uid=nextcloud \
163 --same-dir \
164 --pty \
165 --wait \
166 --collect \
167 --service-type=exec \
168 --quiet \
169 ${command}
170 elif [[ "$USER" != nextcloud ]]; then
171 if [[ -x /run/wrappers/bin/sudo ]]; then
172 exec /run/wrappers/bin/sudo \
173 --preserve-env=CREDENTIALS_DIRECTORY \
174 --user=nextcloud \
175 ${command}
176 else
177 exec ${lib.getExe' pkgs.util-linux "runuser"} \
178 --whitelist-environment=CREDENTIALS_DIRECTORY \
179 --user=nextcloud \
180 ${command}
181 fi
182 else
183 exec ${command}
184 fi
185 '';
186 };
187
188 inherit (config.system) stateVersion;
189
190 mysqlLocal = cfg.database.createLocally && cfg.config.dbtype == "mysql";
191 pgsqlLocal = cfg.database.createLocally && cfg.config.dbtype == "pgsql";
192
193 nextcloudGreaterOrEqualThan = versionAtLeast overridePackage.version;
194 nextcloudOlderThan = versionOlder overridePackage.version;
195
196 # https://github.com/nextcloud/documentation/pull/11179
197 ocmProviderIsNotAStaticDirAnymore =
198 nextcloudGreaterOrEqualThan "27.1.2"
199 || (nextcloudOlderThan "27.0.0" && nextcloudGreaterOrEqualThan "26.0.8");
200
201 overrideConfig =
202 let
203 c = cfg.config;
204 objectstoreConfig =
205 let
206 s3 = c.objectstore.s3;
207 in
208 optionalString s3.enable ''
209 'objectstore' => [
210 'class' => '\\OC\\Files\\ObjectStore\\S3',
211 'arguments' => [
212 'bucket' => '${s3.bucket}',
213 'verify_bucket_exists' => ${boolToString s3.verify_bucket_exists},
214 'key' => '${s3.key}',
215 'secret' => nix_read_secret('s3_secret'),
216 ${optionalString (s3.hostname != null) "'hostname' => '${s3.hostname}',"}
217 ${optionalString (s3.port != null) "'port' => ${toString s3.port},"}
218 'use_ssl' => ${boolToString s3.useSsl},
219 ${optionalString (s3.region != null) "'region' => '${s3.region}',"}
220 'use_path_style' => ${boolToString s3.usePathStyle},
221 ${optionalString (s3.sseCKeyFile != null) "'sse_c_key' => nix_read_secret('s3_sse_c_key'),"}
222 ],
223 ]
224 '';
225 showAppStoreSetting = cfg.appstoreEnable != null || cfg.extraApps != { };
226 renderedAppStoreSetting =
227 let
228 x = cfg.appstoreEnable;
229 in
230 if x == null then "false" else boolToString x;
231 mkAppStoreConfig =
232 name:
233 { enabled, writable, ... }:
234 optionalString enabled ''
235 [ 'path' => '${webroot}/${name}', 'url' => '/${name}', 'writable' => ${boolToString writable} ],
236 '';
237 in
238 pkgs.writeText "nextcloud-config.php" ''
239 <?php
240 ${optionalString requiresRuntimeSystemdCredentials ''
241 function nix_read_secret($credential_name) {
242 $credentials_directory = getenv("CREDENTIALS_DIRECTORY");
243 if (!$credentials_directory) {
244 error_log(sprintf(
245 "Cannot read credential '%s' passed by NixOS, \$CREDENTIALS_DIRECTORY is not set!",
246 $credential_name
247 ));
248 exit(1);
249 }
250
251 $credential_path = $credentials_directory . "/" . $credential_name;
252 if (!is_readable($credential_path)) {
253 error_log(sprintf(
254 "Cannot read credential '%s' passed by NixOS, it does not exist or is not readable!",
255 $credential_path,
256 ));
257 exit(1);
258 }
259
260 return trim(file_get_contents($credential_path));
261 }
262
263 function nix_read_secret_and_decode_json_file($credential_name) {
264 $decoded = json_decode(nix_read_secret($credential_name), true);
265
266 if (json_last_error() !== JSON_ERROR_NONE) {
267 error_log(sprintf("Cannot decode %s, because: %s", $file, json_last_error_msg()));
268 exit(1);
269 }
270
271 return $decoded;
272 }
273 ''}
274 function nix_decode_json_file($file, $error) {
275 if (!file_exists($file)) {
276 throw new \RuntimeException(sprintf($error, $file));
277 }
278 $decoded = json_decode(file_get_contents($file), true);
279
280 if (json_last_error() !== JSON_ERROR_NONE) {
281 throw new \RuntimeException(sprintf("Cannot decode %s, because: %s", $file, json_last_error_msg()));
282 }
283
284 return $decoded;
285 }
286 $CONFIG = [
287 'apps_paths' => [
288 ${concatStrings (mapAttrsToList mkAppStoreConfig appStores)}
289 ],
290 ${optionalString (showAppStoreSetting) "'appstoreenabled' => ${renderedAppStoreSetting},"}
291 ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"}
292 ${optionalString (c.dbname != null) "'dbname' => '${c.dbname}',"}
293 ${optionalString (c.dbhost != null) "'dbhost' => '${c.dbhost}',"}
294 ${optionalString (c.dbuser != null) "'dbuser' => '${c.dbuser}',"}
295 ${optionalString (c.dbtableprefix != null) "'dbtableprefix' => '${toString c.dbtableprefix}',"}
296 ${optionalString (c.dbpassFile != null) "'dbpassword' => nix_read_secret('dbpass'),"}
297 'dbtype' => '${c.dbtype}',
298 ${objectstoreConfig}
299 ];
300
301 $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file(
302 "${jsonFormat.generate "nextcloud-settings.json" cfg.settings}",
303 "impossible: this should never happen (decoding generated settings file %s failed)"
304 ));
305
306 ${optionalString (cfg.secretFile != null) ''
307 $CONFIG = array_replace_recursive($CONFIG, nix_read_secret_and_decode_json_file('secret_file'));
308 ''}
309 '';
310in
311{
312
313 imports = [
314 (mkRenamedOptionModule
315 [ "services" "nextcloud" "cron" "memoryLimit" ]
316 [ "services" "nextcloud" "cli" "memoryLimit" ]
317 )
318 (mkRemovedOptionModule [ "services" "nextcloud" "enableBrokenCiphersForSSE" ] ''
319 This option has no effect since there's no supported Nextcloud version packaged here
320 using OpenSSL for RC4 SSE.
321 '')
322 (mkRemovedOptionModule [ "services" "nextcloud" "config" "dbport" ] ''
323 Add port to services.nextcloud.config.dbhost instead.
324 '')
325 (mkRenamedOptionModule
326 [ "services" "nextcloud" "logLevel" ]
327 [ "services" "nextcloud" "settings" "loglevel" ]
328 )
329 (mkRenamedOptionModule
330 [ "services" "nextcloud" "logType" ]
331 [ "services" "nextcloud" "settings" "log_type" ]
332 )
333 (mkRenamedOptionModule
334 [ "services" "nextcloud" "config" "defaultPhoneRegion" ]
335 [ "services" "nextcloud" "settings" "default_phone_region" ]
336 )
337 (mkRenamedOptionModule
338 [ "services" "nextcloud" "config" "overwriteProtocol" ]
339 [ "services" "nextcloud" "settings" "overwriteprotocol" ]
340 )
341 (mkRenamedOptionModule
342 [ "services" "nextcloud" "skeletonDirectory" ]
343 [ "services" "nextcloud" "settings" "skeletondirectory" ]
344 )
345 (mkRenamedOptionModule
346 [ "services" "nextcloud" "globalProfiles" ]
347 [ "services" "nextcloud" "settings" "profile.enabled" ]
348 )
349 (mkRenamedOptionModule
350 [ "services" "nextcloud" "config" "extraTrustedDomains" ]
351 [ "services" "nextcloud" "settings" "trusted_domains" ]
352 )
353 (mkRenamedOptionModule
354 [ "services" "nextcloud" "config" "trustedProxies" ]
355 [ "services" "nextcloud" "settings" "trusted_proxies" ]
356 )
357 (mkRenamedOptionModule
358 [ "services" "nextcloud" "extraOptions" ]
359 [ "services" "nextcloud" "settings" ]
360 )
361 (mkRenamedOptionModule
362 [ "services" "nextcloud" "config" "objectstore" "s3" "autocreate" ]
363 [ "services" "nextcloud" "config" "objectstore" "s3" "verify_bucket_exists" ]
364 )
365 ];
366
367 options.services.nextcloud = {
368 enable = mkEnableOption "nextcloud";
369
370 hostName = mkOption {
371 type = types.str;
372 description = "FQDN for the nextcloud instance.";
373 };
374 home = mkOption {
375 type = types.str;
376 default = "/var/lib/nextcloud";
377 description = "Storage path of nextcloud.";
378 };
379 datadir = mkOption {
380 type = types.str;
381 default = config.services.nextcloud.home;
382 defaultText = literalExpression "config.services.nextcloud.home";
383 description = ''
384 Nextcloud's data storage path. Will be [](#opt-services.nextcloud.home) by default.
385 This folder will be populated with a config.php file and a data folder which contains the state of the instance (excluding the database).";
386 '';
387 example = "/mnt/nextcloud-file";
388 };
389 extraApps = mkOption {
390 type = types.attrsOf types.package;
391 default = { };
392 description = ''
393 Extra apps to install. Should be an attrSet of appid to packages generated by fetchNextcloudApp.
394 The appid must be identical to the "id" value in the apps appinfo/info.xml.
395 Using this will disable the appstore to prevent Nextcloud from updating these apps (see [](#opt-services.nextcloud.appstoreEnable)).
396 '';
397 example = literalExpression ''
398 {
399 inherit (pkgs.nextcloud31Packages.apps) mail calendar contacts;
400 phonetrack = pkgs.fetchNextcloudApp {
401 name = "phonetrack";
402 sha256 = "0qf366vbahyl27p9mshfma1as4nvql6w75zy2zk5xwwbp343vsbc";
403 url = "https://gitlab.com/eneiluj/phonetrack-oc/-/wikis/uploads/931aaaf8dca24bf31a7e169a83c17235/phonetrack-0.6.9.tar.gz";
404 version = "0.6.9";
405 };
406 }
407 '';
408 };
409 extraAppsEnable = mkOption {
410 type = types.bool;
411 default = true;
412 description = ''
413 Automatically enable the apps in [](#opt-services.nextcloud.extraApps) every time Nextcloud starts.
414 If set to false, apps need to be enabled in the Nextcloud web user interface or with `nextcloud-occ app:enable`.
415 '';
416 };
417 appstoreEnable = mkOption {
418 type = types.nullOr types.bool;
419 default = null;
420 example = true;
421 description = ''
422 Allow the installation and updating of apps from the Nextcloud appstore.
423 Enabled by default unless there are packages in [](#opt-services.nextcloud.extraApps).
424 Set this to true to force enable the store even if [](#opt-services.nextcloud.extraApps) is used.
425 Set this to false to disable the installation of apps from the global appstore. App management is always enabled regardless of this setting.
426 '';
427 };
428 https = mkOption {
429 type = types.bool;
430 default = false;
431 description = ''
432 Use HTTPS for generated links.
433
434 Be aware that this also enables HTTP Strict Transport Security (HSTS) headers.
435 '';
436 };
437 package = mkOption {
438 type = types.package;
439 description = "Which package to use for the Nextcloud instance.";
440 relatedPackages = [
441 "nextcloud30"
442 "nextcloud31"
443 ];
444 };
445 phpPackage = mkPackageOption pkgs "php" {
446 example = "php82";
447 };
448
449 finalPackage = mkOption {
450 type = types.package;
451 readOnly = true;
452 description = ''
453 Package to the finalized Nextcloud package, including all installed apps.
454 This is automatically set by the module.
455 '';
456 };
457
458 maxUploadSize = mkOption {
459 default = "512M";
460 type = types.str;
461 description = ''
462 The upload limit for files. This changes the relevant options
463 in php.ini and nginx if enabled.
464 '';
465 };
466
467 webfinger = mkOption {
468 type = types.bool;
469 default = false;
470 description = ''
471 Enable this option if you plan on using the webfinger plugin.
472 The appropriate nginx rewrite rules will be added to your configuration.
473 '';
474 };
475
476 phpExtraExtensions = mkOption {
477 type = with types; functionTo (listOf package);
478 default = all: [ ];
479 defaultText = literalExpression "all: []";
480 description = ''
481 Additional PHP extensions to use for Nextcloud.
482 By default, only extensions necessary for a vanilla Nextcloud installation are enabled,
483 but you may choose from the list of available extensions and add further ones.
484 This is sometimes necessary to be able to install a certain Nextcloud app that has additional requirements.
485 '';
486 example = literalExpression ''
487 all: [ all.pdlib all.bz2 ]
488 '';
489 };
490
491 phpOptions = mkOption {
492 type =
493 with types;
494 attrsOf (oneOf [
495 str
496 int
497 ]);
498 defaultText = literalExpression (
499 generators.toPretty { } (
500 defaultPHPSettings // { "openssl.cafile" = literalExpression "config.security.pki.caBundle"; }
501 )
502 );
503 description = ''
504 Options for PHP's php.ini file for nextcloud.
505
506 Please note that this option is _additive_ on purpose while the
507 attribute values inside the default are option defaults: that means that
508
509 ```nix
510 {
511 services.nextcloud.phpOptions."opcache.interned_strings_buffer" = "23";
512 }
513 ```
514
515 will override the `php.ini` option `opcache.interned_strings_buffer` without
516 discarding the rest of the defaults.
517
518 Overriding all of `phpOptions` (including `upload_max_filesize`, `post_max_size`
519 and `memory_limit` which all point to [](#opt-services.nextcloud.maxUploadSize)
520 by default) can be done like this:
521
522 ```nix
523 {
524 services.nextcloud.phpOptions = lib.mkForce {
525 /* ... */
526 };
527 }
528 ```
529 '';
530 };
531
532 poolSettings = mkOption {
533 type =
534 with types;
535 attrsOf (oneOf [
536 str
537 int
538 bool
539 ]);
540 default = {
541 "pm" = "dynamic";
542 "pm.max_children" = "120";
543 "pm.start_servers" = "12";
544 "pm.min_spare_servers" = "6";
545 "pm.max_spare_servers" = "18";
546 "pm.max_requests" = "500";
547 };
548 description = ''
549 Options for nextcloud's PHP pool. See the documentation on `php-fpm.conf` for details on
550 configuration directives. The above are recommended for a server with 4GiB of RAM.
551
552 It's advisable to read the [section about PHPFPM tuning in the upstream manual](https://docs.nextcloud.com/server/30/admin_manual/installation/server_tuning.html#tune-php-fpm)
553 and consider customizing the values.
554 '';
555 };
556
557 poolConfig = mkOption {
558 type = types.nullOr types.lines;
559 default = null;
560 description = ''
561 Options for Nextcloud's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
562 '';
563 };
564
565 fastcgiTimeout = mkOption {
566 type = types.int;
567 default = 120;
568 description = ''
569 FastCGI timeout for database connection in seconds.
570 '';
571 };
572
573 database = {
574
575 createLocally = mkOption {
576 type = types.bool;
577 default = false;
578 description = ''
579 Whether to create the database and database user locally.
580 '';
581 };
582
583 };
584
585 config = {
586 dbtype = mkOption {
587 type = types.enum [
588 "sqlite"
589 "pgsql"
590 "mysql"
591 ];
592 description = "Database type.";
593 };
594 dbname = mkOption {
595 type = types.nullOr types.str;
596 default = "nextcloud";
597 description = "Database name.";
598 };
599 dbuser = mkOption {
600 type = types.nullOr types.str;
601 default = "nextcloud";
602 description = "Database user.";
603 };
604 dbpassFile = mkOption {
605 type = types.nullOr types.str;
606 default = null;
607 description = ''
608 The full path to a file that contains the database password.
609 '';
610 };
611 dbhost = mkOption {
612 type = types.nullOr types.str;
613 default =
614 if pgsqlLocal then
615 "/run/postgresql"
616 else if mysqlLocal then
617 "localhost:/run/mysqld/mysqld.sock"
618 else
619 "localhost";
620 defaultText = "localhost";
621 example = "localhost:5000";
622 description = ''
623 Database host (+port) or socket path.
624 If [](#opt-services.nextcloud.database.createLocally) is true and
625 [](#opt-services.nextcloud.config.dbtype) is either `pgsql` or `mysql`,
626 defaults to the correct Unix socket instead.
627 '';
628 };
629 dbtableprefix = mkOption {
630 type = types.nullOr types.str;
631 default = null;
632 description = ''
633 Table prefix in Nextcloud's database.
634
635 __Note:__ since Nextcloud 20 it's not an option anymore to create a database
636 schema with a custom table prefix. This option only exists for backwards compatibility
637 with installations that were originally provisioned with Nextcloud <20.
638 '';
639 };
640 adminuser = mkOption {
641 type = types.str;
642 default = "root";
643 description = ''
644 Username for the admin account. The username is only set during the
645 initial setup of Nextcloud! Since the username also acts as unique
646 ID internally, it cannot be changed later!
647 '';
648 };
649 adminpassFile = mkOption {
650 type = types.str;
651 description = ''
652 The full path to a file that contains the admin's password. The password is
653 set only in the initial setup of Nextcloud by the systemd service `nextcloud-setup.service`.
654 '';
655 };
656 objectstore = {
657 s3 = {
658 enable = mkEnableOption ''
659 S3 object storage as primary storage.
660
661 This mounts a bucket on an Amazon S3 object storage or compatible
662 implementation into the virtual filesystem.
663
664 Further details about this feature can be found in the
665 [upstream documentation](https://docs.nextcloud.com/server/22/admin_manual/configuration_files/primary_storage.html)
666 '';
667 bucket = mkOption {
668 type = types.str;
669 example = "nextcloud";
670 description = ''
671 The name of the S3 bucket.
672 '';
673 };
674 verify_bucket_exists = mkOption {
675 type = types.bool;
676 default = true;
677 description = ''
678 Create the objectstore bucket if it does not exist.
679 '';
680 };
681 key = mkOption {
682 type = types.str;
683 example = "EJ39ITYZEUH5BGWDRUFY";
684 description = ''
685 The access key for the S3 bucket.
686 '';
687 };
688 secretFile = mkOption {
689 type = types.str;
690 example = "/var/nextcloud-objectstore-s3-secret";
691 description = ''
692 The full path to a file that contains the access secret.
693 '';
694 };
695 hostname = mkOption {
696 type = types.nullOr types.str;
697 default = null;
698 example = "example.com";
699 description = ''
700 Required for some non-Amazon implementations.
701 '';
702 };
703 port = mkOption {
704 type = types.nullOr types.port;
705 default = null;
706 description = ''
707 Required for some non-Amazon implementations.
708 '';
709 };
710 useSsl = mkOption {
711 type = types.bool;
712 default = true;
713 description = ''
714 Use SSL for objectstore access.
715 '';
716 };
717 region = mkOption {
718 type = types.nullOr types.str;
719 default = null;
720 example = "REGION";
721 description = ''
722 Required for some non-Amazon implementations.
723 '';
724 };
725 usePathStyle = mkOption {
726 type = types.bool;
727 default = false;
728 description = ''
729 Required for some non-Amazon S3 implementations.
730
731 Ordinarily, requests will be made with
732 `http://bucket.hostname.domain/`, but with path style
733 enabled requests are made with
734 `http://hostname.domain/bucket` instead.
735 '';
736 };
737 sseCKeyFile = mkOption {
738 type = types.nullOr types.path;
739 default = null;
740 example = "/var/nextcloud-objectstore-s3-sse-c-key";
741 description = ''
742 If provided this is the full path to a file that contains the key
743 to enable [server-side encryption with customer-provided keys][1]
744 (SSE-C).
745
746 The file must contain a random 32-byte key encoded as a base64
747 string, e.g. generated with the command
748
749 ```
750 openssl rand 32 | base64
751 ```
752
753 [1]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerSideEncryptionCustomerKeys.html
754 '';
755 };
756 };
757 };
758 };
759
760 enableImagemagick =
761 mkEnableOption ''
762 the ImageMagick module for PHP.
763 This is used by the theming app and for generating previews of certain images (e.g. SVG and HEIF).
764 You may want to disable it for increased security. In that case, previews will still be available
765 for some images (e.g. JPEG and PNG).
766 See <https://github.com/nextcloud/server/issues/13099>
767 ''
768 // {
769 default = true;
770 };
771
772 configureRedis = lib.mkOption {
773 type = lib.types.bool;
774 default = config.services.nextcloud.notify_push.enable;
775 defaultText = literalExpression "config.services.nextcloud.notify_push.enable";
776 description = ''
777 Whether to configure Nextcloud to use the recommended Redis settings for small instances.
778
779 ::: {.note}
780 The `notify_push` app requires Redis to be configured. If this option is turned off, this must be configured manually.
781 :::
782 '';
783 };
784
785 caching = {
786 apcu = mkOption {
787 type = types.bool;
788 default = true;
789 description = ''
790 Whether to load the APCu module into PHP.
791 '';
792 };
793 redis = mkOption {
794 type = types.bool;
795 default = false;
796 description = ''
797 Whether to load the Redis module into PHP.
798 You still need to enable Redis in your config.php.
799 See <https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/caching_configuration.html>
800 '';
801 };
802 memcached = mkOption {
803 type = types.bool;
804 default = false;
805 description = ''
806 Whether to load the Memcached module into PHP.
807 You still need to enable Memcached in your config.php.
808 See <https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/caching_configuration.html>
809 '';
810 };
811 };
812 autoUpdateApps = {
813 enable = mkOption {
814 type = types.bool;
815 default = false;
816 description = ''
817 Run a regular auto-update of all apps installed from the Nextcloud app store.
818 '';
819 };
820 startAt = mkOption {
821 type = with types; either str (listOf str);
822 default = "05:00:00";
823 example = "Sun 14:00:00";
824 description = ''
825 When to run the update. See `systemd.services.<name>.startAt`.
826 '';
827 };
828 };
829 occ = mkOption {
830 type = types.package;
831 default = occ;
832 defaultText = literalMD "generated script";
833 description = ''
834 The nextcloud-occ program preconfigured to target this Nextcloud instance.
835 '';
836 };
837
838 settings = mkOption {
839 type = types.submodule {
840 freeformType = jsonFormat.type;
841 options = {
842
843 loglevel = mkOption {
844 type = types.ints.between 0 4;
845 default = 2;
846 description = ''
847 Log level value between 0 (DEBUG) and 4 (FATAL).
848
849 - 0 (debug): Log all activity.
850
851 - 1 (info): Log activity such as user logins and file activities, plus warnings, errors, and fatal errors.
852
853 - 2 (warn): Log successful operations, as well as warnings of potential problems, errors and fatal errors.
854
855 - 3 (error): Log failed operations and fatal errors.
856
857 - 4 (fatal): Log only fatal errors that cause the server to stop.
858 '';
859 };
860 log_type = mkOption {
861 type = types.enum [
862 "errorlog"
863 "file"
864 "syslog"
865 "systemd"
866 ];
867 default = "syslog";
868 description = ''
869 Logging backend to use.
870 systemd requires the php-systemd package to be added to services.nextcloud.phpExtraExtensions.
871 See the [nextcloud documentation](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/logging_configuration.html) for details.
872 '';
873 };
874 skeletondirectory = mkOption {
875 default = "";
876 type = types.str;
877 description = ''
878 The directory where the skeleton files are located. These files will be
879 copied to the data directory of new users. Leave empty to not copy any
880 skeleton files.
881 '';
882 };
883 trusted_domains = mkOption {
884 type = types.listOf types.str;
885 default = [ ];
886 description = ''
887 Trusted domains, from which the nextcloud installation will be
888 accessible. You don't need to add
889 `services.nextcloud.hostname` here.
890 '';
891 };
892 trusted_proxies = mkOption {
893 type = types.listOf types.str;
894 default = [ ];
895 description = ''
896 Trusted proxies, to provide if the nextcloud installation is being
897 proxied to secure against e.g. spoofing.
898 '';
899 };
900 overwriteprotocol = mkOption {
901 type = types.enum [
902 ""
903 "http"
904 "https"
905 ];
906 default = "";
907 example = "https";
908 description = ''
909 Force Nextcloud to always use HTTP or HTTPS i.e. for link generation.
910 Nextcloud uses the currently used protocol by default, but when
911 behind a reverse-proxy, it may use `http` for everything although
912 Nextcloud may be served via HTTPS.
913 '';
914 };
915 default_phone_region = mkOption {
916 default = "";
917 type = types.str;
918 example = "DE";
919 description = ''
920 An [ISO 3166-1](https://www.iso.org/iso-3166-country-codes.html)
921 country code which replaces automatic phone-number detection
922 without a country code.
923
924 As an example, with `DE` set as the default phone region,
925 the `+49` prefix can be omitted for phone numbers.
926 '';
927 };
928 "profile.enabled" = mkEnableOption "global profiles" // {
929 description = ''
930 Makes user-profiles globally available under `nextcloud.tld/u/user.name`.
931 Even though it's enabled by default in Nextcloud, it must be explicitly enabled
932 here because it has the side-effect that personal information is even accessible to
933 unauthenticated users by default.
934 By default, the following properties are set to “Show to everyone”
935 if this flag is enabled:
936 - About
937 - Full name
938 - Headline
939 - Organisation
940 - Profile picture
941 - Role
942 - Twitter
943 - Website
944 Only has an effect in Nextcloud 23 and later.
945 '';
946 };
947 };
948 };
949 default = { };
950 description = ''
951 Extra options which should be appended to Nextcloud's config.php file.
952 '';
953 example = literalExpression ''
954 {
955 redis = {
956 host = "/run/redis/redis.sock";
957 port = 0;
958 dbindex = 0;
959 password = "secret";
960 timeout = 1.5;
961 };
962 }
963 '';
964 };
965
966 secretFile = mkOption {
967 type = types.nullOr types.str;
968 default = null;
969 description = ''
970 Secret options which will be appended to Nextcloud's config.php file (written as JSON, in the same
971 form as the [](#opt-services.nextcloud.settings) option), for example
972 `{"redis":{"password":"secret"}}`.
973 '';
974 };
975
976 nginx = {
977 recommendedHttpHeaders = mkOption {
978 type = types.bool;
979 default = true;
980 description = "Enable additional recommended HTTP response headers";
981 };
982 hstsMaxAge = mkOption {
983 type = types.ints.positive;
984 default = 15552000;
985 description = ''
986 Value for the `max-age` directive of the HTTP
987 `Strict-Transport-Security` header.
988
989 See section 6.1.1 of IETF RFC 6797 for detailed information on this
990 directive and header.
991 '';
992 };
993 };
994
995 cli.memoryLimit = mkOption {
996 type = types.nullOr types.str;
997 default = null;
998 example = "1G";
999 description = ''
1000 The `memory_limit` of PHP is equal to [](#opt-services.nextcloud.maxUploadSize).
1001 The value can be customized for `nextcloud-cron.service` using this option.
1002 '';
1003 };
1004 };
1005
1006 config = mkIf cfg.enable (mkMerge [
1007 {
1008 warnings =
1009 let
1010 latest = 31;
1011 upgradeWarning = major: nixos: ''
1012 A legacy Nextcloud install (from before NixOS ${nixos}) may be installed.
1013
1014 After nextcloud${toString major} is installed successfully, you can safely upgrade
1015 to ${toString (major + 1)}. The latest version available is Nextcloud${toString latest}.
1016
1017 Please note that Nextcloud doesn't support upgrades across multiple major versions
1018 (i.e. an upgrade from 16 is possible to 17, but not 16 to 18).
1019
1020 The package can be upgraded by explicitly declaring the service-option
1021 `services.nextcloud.package`.
1022 '';
1023
1024 in
1025 (optional (cfg.poolConfig != null) ''
1026 Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release.
1027 Please migrate your configuration to config.services.nextcloud.poolSettings.
1028 '')
1029 ++ (optional (cfg.config.dbtableprefix != null) ''
1030 Using `services.nextcloud.config.dbtableprefix` is deprecated. Fresh installations with this
1031 option set are not allowed anymore since v20.
1032
1033 If you have an existing installation with a custom table prefix, make sure it is
1034 set correctly in `config.php` and remove the option from your NixOS config.
1035 '')
1036 ++ (optional (versionOlder overridePackage.version "26") (upgradeWarning 25 "23.05"))
1037 ++ (optional (versionOlder overridePackage.version "27") (upgradeWarning 26 "23.11"))
1038 ++ (optional (versionOlder overridePackage.version "28") (upgradeWarning 27 "24.05"))
1039 ++ (optional (versionOlder overridePackage.version "29") (upgradeWarning 28 "24.11"))
1040 ++ (optional (versionOlder overridePackage.version "30") (upgradeWarning 29 "24.11"))
1041 ++ (optional (versionOlder overridePackage.version "31") (upgradeWarning 30 "25.05"));
1042
1043 services.nextcloud.package =
1044 with pkgs;
1045 mkDefault (
1046 if pkgs ? nextcloud then
1047 throw ''
1048 The `pkgs.nextcloud`-attribute has been removed. If it's supposed to be the default
1049 nextcloud defined in an overlay, please set `services.nextcloud.package` to
1050 `pkgs.nextcloud`.
1051 ''
1052 else if versionOlder stateVersion "24.05" then
1053 nextcloud27
1054 else if versionOlder stateVersion "24.11" then
1055 nextcloud29
1056 else if versionOlder stateVersion "25.05" then
1057 nextcloud30
1058 else
1059 nextcloud31
1060 );
1061
1062 services.nextcloud.phpPackage = pkgs.php83;
1063
1064 services.nextcloud.phpOptions = mkMerge [
1065 (mapAttrs (const mkOptionDefault) defaultPHPSettings)
1066 {
1067 upload_max_filesize = cfg.maxUploadSize;
1068 post_max_size = cfg.maxUploadSize;
1069 memory_limit = cfg.maxUploadSize;
1070 }
1071 (mkIf cfg.caching.apcu {
1072 "apc.enable_cli" = "1";
1073 })
1074 ];
1075 }
1076
1077 {
1078 assertions = [
1079 {
1080 assertion = cfg.database.createLocally -> cfg.config.dbpassFile == null;
1081 message = ''
1082 Using `services.nextcloud.database.createLocally` with database
1083 password authentication is no longer supported.
1084
1085 If you use an external database (or want to use password auth for any
1086 other reason), set `services.nextcloud.database.createLocally` to
1087 `false`. The database won't be managed for you (use `services.mysql`
1088 if you want to set it up).
1089
1090 If you want this module to manage your nextcloud database for you,
1091 unset `services.nextcloud.config.dbpassFile` and
1092 `services.nextcloud.config.dbhost` to use socket authentication
1093 instead of password.
1094 '';
1095 }
1096 ];
1097 }
1098
1099 {
1100 systemd.timers.nextcloud-cron = {
1101 wantedBy = [ "timers.target" ];
1102 after = [ "nextcloud-setup.service" ];
1103 timerConfig = {
1104 OnBootSec = "5m";
1105 OnUnitActiveSec = "5m";
1106 Unit = "nextcloud-cron.service";
1107 };
1108 };
1109
1110 systemd.tmpfiles.rules =
1111 map (dir: "d ${dir} 0750 nextcloud nextcloud - -") [
1112 "${cfg.home}"
1113 "${datadir}/config"
1114 "${datadir}/data"
1115 "${cfg.home}/store-apps"
1116 ]
1117 ++ [
1118 "L+ ${datadir}/config/override.config.php - - - - ${overrideConfig}"
1119 ];
1120
1121 services.nextcloud.finalPackage = webroot;
1122
1123 systemd.services = {
1124 nextcloud-setup =
1125 let
1126 c = cfg.config;
1127 occInstallCmd =
1128 let
1129 mkExport =
1130 { arg, value }:
1131 ''
1132 ${arg}=${value};
1133 export ${arg};
1134 '';
1135 dbpass = {
1136 arg = "DBPASS";
1137 value = if c.dbpassFile != null then ''"$(<"$CREDENTIALS_DIRECTORY/dbpass")"'' else ''""'';
1138 };
1139 adminpass = {
1140 arg = "ADMINPASS";
1141 value = ''"$(<"$CREDENTIALS_DIRECTORY/adminpass")"'';
1142 };
1143 installFlags = concatStringsSep " \\\n " (
1144 mapAttrsToList (k: v: "${k} ${toString v}") {
1145 "--database" = ''"${c.dbtype}"'';
1146 # The following attributes are optional depending on the type of
1147 # database. Those that evaluate to null on the left hand side
1148 # will be omitted.
1149 ${if c.dbname != null then "--database-name" else null} = ''"${c.dbname}"'';
1150 ${if c.dbhost != null then "--database-host" else null} = ''"${c.dbhost}"'';
1151 ${if c.dbuser != null then "--database-user" else null} = ''"${c.dbuser}"'';
1152 "--database-pass" = "\"\$${dbpass.arg}\"";
1153 "--admin-user" = ''"${c.adminuser}"'';
1154 "--admin-pass" = "\"\$${adminpass.arg}\"";
1155 "--data-dir" = ''"${datadir}/data"'';
1156 }
1157 );
1158 in
1159 ''
1160 ${mkExport dbpass}
1161 ${mkExport adminpass}
1162 ${lib.getExe occ} maintenance:install \
1163 ${installFlags}
1164 '';
1165 occSetTrustedDomainsCmd = concatStringsSep "\n" (
1166 imap0 (i: v: ''
1167 ${lib.getExe occ} config:system:set trusted_domains \
1168 ${toString i} --value="${toString v}"
1169 '') ([ cfg.hostName ] ++ cfg.settings.trusted_domains)
1170 );
1171
1172 in
1173 {
1174 wantedBy = [ "multi-user.target" ];
1175 wants = [ "nextcloud-update-db.service" ];
1176 before = [ "phpfpm-nextcloud.service" ];
1177 after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
1178 requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
1179 path = [ occ ];
1180 restartTriggers = [ overrideConfig ];
1181 script = ''
1182 ${optionalString (c.dbpassFile != null) ''
1183 if [ -z "$(<"$CREDENTIALS_DIRECTORY/dbpass")" ]; then
1184 echo "dbpassFile ${c.dbpassFile} is empty!"
1185 exit 1
1186 fi
1187 ''}
1188 if [ -z "$(<"$CREDENTIALS_DIRECTORY/adminpass")" ]; then
1189 echo "adminpassFile ${c.adminpassFile} is empty!"
1190 exit 1
1191 fi
1192
1193 # Check if systemd-tmpfiles setup worked correctly
1194 if [[ ! -O "${datadir}/config" ]]; then
1195 echo "${datadir}/config is not owned by user 'nextcloud'!"
1196 echo "Please check the logs via 'journalctl -u systemd-tmpfiles-setup'"
1197 echo "and make sure there are no unsafe path transitions."
1198 echo "(https://nixos.org/manual/nixos/stable/#module-services-nextcloud-pitfalls-during-upgrade)"
1199 exit 1
1200 fi
1201
1202 ${concatMapStrings
1203 (name: ''
1204 if [ -d "${cfg.home}"/${name} ]; then
1205 echo "Cleaning up ${name}; these are now bundled in the webroot store-path!"
1206 rm -r "${cfg.home}"/${name}
1207 fi
1208 '')
1209 [
1210 "nix-apps"
1211 "apps"
1212 ]
1213 }
1214
1215 # Do not install if already installed
1216 if [[ ! -s ${datadir}/config/config.php ]]; then
1217 ${occInstallCmd}
1218 fi
1219
1220 ${lib.getExe occ} upgrade
1221
1222 ${lib.getExe occ} config:system:delete trusted_domains
1223
1224 ${optionalString (cfg.extraAppsEnable && cfg.extraApps != { }) ''
1225 # Try to enable apps
1226 ${lib.getExe occ} app:enable ${concatStringsSep " " (attrNames cfg.extraApps)}
1227 ''}
1228
1229 ${occSetTrustedDomainsCmd}
1230 '';
1231 serviceConfig.Type = "oneshot";
1232 serviceConfig.User = "nextcloud";
1233 serviceConfig.LoadCredential = [
1234 "adminpass:${cfg.config.adminpassFile}"
1235 ] ++ runtimeSystemdCredentials;
1236 # On Nextcloud ≥ 26, it is not necessary to patch the database files to prevent
1237 # an automatic creation of the database user.
1238 environment.NC_setup_create_db_user = lib.mkIf (nextcloudGreaterOrEqualThan "26") "false";
1239 };
1240 nextcloud-cron = {
1241 after = [ "nextcloud-setup.service" ];
1242 # NOTE: In contrast to the occ wrapper script running phpCli directly will not
1243 # set NEXTCLOUD_CONFIG_DIR by itself currently.
1244 environment.NEXTCLOUD_CONFIG_DIR = "${datadir}/config";
1245 script = ''
1246 # NOTE: This early returns the script when nextcloud is in maintenance mode
1247 # or needs `occ upgrade`. Using ExecCondition= is not possible here
1248 # because it doesn't work with systemd credentials.
1249 if [[ $(${lib.getExe occ} status --output=json | ${lib.getExe pkgs.jq} '. | if .maintenance or .needsDbUpgrade then "skip" else "" end' --raw-output) == "skip" ]]; then
1250 echo "Nextcloud is in maintenance mode or needs DB upgrade, exiting."
1251 exit 0
1252 fi
1253
1254 ${phpCli} -f ${webroot}/cron.php
1255 '';
1256 serviceConfig = {
1257 Type = "exec";
1258 User = "nextcloud";
1259 KillMode = "process";
1260 LoadCredential = runtimeSystemdCredentials;
1261 };
1262 };
1263 nextcloud-update-plugins = mkIf cfg.autoUpdateApps.enable {
1264 after = [ "nextcloud-setup.service" ];
1265 serviceConfig = {
1266 Type = "oneshot";
1267 ExecStart = "${lib.getExe occ} app:update --all";
1268 User = "nextcloud";
1269 LoadCredential = runtimeSystemdCredentials;
1270 };
1271 startAt = cfg.autoUpdateApps.startAt;
1272 };
1273 nextcloud-update-db = {
1274 after = [ "nextcloud-setup.service" ];
1275 script = ''
1276 # NOTE: This early returns the script when nextcloud is in maintenance mode
1277 # or needs `occ upgrade`. Using ExecCondition= is not possible here
1278 # because it doesn't work with systemd credentials.
1279 if [[ $(${lib.getExe occ} status --output=json | ${lib.getExe pkgs.jq} '. | if .maintenance or .needsDbUpgrade then "skip" else "" end' --raw-output) == "skip" ]]; then
1280 echo "Nextcloud is in maintenance mode or needs DB upgrade, exiting."
1281 exit 0
1282 fi
1283
1284 ${lib.getExe occ} db:add-missing-columns
1285 ${lib.getExe occ} db:add-missing-indices
1286 ${lib.getExe occ} db:add-missing-primary-keys
1287 '';
1288 serviceConfig = {
1289 Type = "exec";
1290 User = "nextcloud";
1291 LoadCredential = runtimeSystemdCredentials;
1292 };
1293 };
1294
1295 phpfpm-nextcloud =
1296 {
1297 # When upgrading the Nextcloud package, Nextcloud can report errors such as
1298 # "The files of the app [all apps in /var/lib/nextcloud/apps] were not replaced correctly"
1299 # Restarting phpfpm on Nextcloud package update fixes these issues (but this is a workaround).
1300 restartTriggers = [
1301 webroot
1302 overrideConfig
1303 ];
1304 }
1305 // lib.optionalAttrs requiresRuntimeSystemdCredentials {
1306 serviceConfig.LoadCredential = runtimeSystemdCredentials;
1307
1308 # FIXME: We use a hack to make the credential files readable by the nextcloud
1309 # user by copying them somewhere else and overriding CREDENTIALS_DIRECTORY
1310 # for php. This is currently necessary as the unit runs as root.
1311 serviceConfig.RuntimeDirectory = lib.mkForce "phpfpm phpfpm-nextcloud";
1312 preStart = ''
1313 umask 0077
1314
1315 # NOTE: Runtime directories for this service are currently preserved
1316 # between restarts.
1317 rm -rf /run/phpfpm-nextcloud/credentials/
1318 mkdir -p /run/phpfpm-nextcloud/credentials/
1319 cp "$CREDENTIALS_DIRECTORY"/* /run/phpfpm-nextcloud/credentials/
1320 chown -R nextcloud:nextcloud /run/phpfpm-nextcloud/credentials/
1321 '';
1322 };
1323 };
1324
1325 services.phpfpm = {
1326 pools.nextcloud = {
1327 user = "nextcloud";
1328 group = "nextcloud";
1329 phpPackage = phpPackage;
1330 phpEnv = {
1331 CREDENTIALS_DIRECTORY = "/run/phpfpm-nextcloud/credentials/";
1332 NEXTCLOUD_CONFIG_DIR = "${datadir}/config";
1333 PATH = "/run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin";
1334 };
1335 settings =
1336 mapAttrs (name: mkDefault) {
1337 "listen.owner" = config.services.nginx.user;
1338 "listen.group" = config.services.nginx.group;
1339 }
1340 // cfg.poolSettings;
1341 extraConfig = cfg.poolConfig;
1342 };
1343 };
1344
1345 users.users.nextcloud = {
1346 home = "${cfg.home}";
1347 group = "nextcloud";
1348 isSystemUser = true;
1349 };
1350 users.groups.nextcloud.members = [
1351 "nextcloud"
1352 config.services.nginx.user
1353 ];
1354
1355 environment.systemPackages = [ occ ];
1356
1357 services.mysql = lib.mkIf mysqlLocal {
1358 enable = true;
1359 package = lib.mkDefault pkgs.mariadb;
1360 ensureDatabases = [ cfg.config.dbname ];
1361 ensureUsers = [
1362 {
1363 name = cfg.config.dbuser;
1364 ensurePermissions = {
1365 "${cfg.config.dbname}.*" = "ALL PRIVILEGES";
1366 };
1367 }
1368 ];
1369 };
1370
1371 services.postgresql = mkIf pgsqlLocal {
1372 enable = true;
1373 ensureDatabases = [ cfg.config.dbname ];
1374 ensureUsers = [
1375 {
1376 name = cfg.config.dbuser;
1377 ensureDBOwnership = true;
1378 }
1379 ];
1380 };
1381
1382 services.redis.servers.nextcloud = lib.mkIf cfg.configureRedis {
1383 enable = true;
1384 user = "nextcloud";
1385 };
1386
1387 services.nextcloud = {
1388 caching.redis = lib.mkIf cfg.configureRedis true;
1389 settings = mkMerge [
1390 ({
1391 datadirectory = lib.mkDefault "${datadir}/data";
1392 trusted_domains = [ cfg.hostName ];
1393 "upgrade.disable-web" = true;
1394 # NixOS already provides its own integrity check and the nix store is read-only, therefore Nextcloud does not need to do its own integrity checks.
1395 "integrity.check.disabled" = true;
1396 })
1397 (lib.mkIf cfg.configureRedis {
1398 "memcache.distributed" = ''\OC\Memcache\Redis'';
1399 "memcache.locking" = ''\OC\Memcache\Redis'';
1400 redis = {
1401 host = config.services.redis.servers.nextcloud.unixSocket;
1402 port = 0;
1403 };
1404 })
1405 ];
1406 };
1407
1408 services.nginx.enable = mkDefault true;
1409
1410 services.nginx.virtualHosts.${cfg.hostName} = {
1411 root = webroot;
1412 locations = {
1413 "= /robots.txt" = {
1414 priority = 100;
1415 extraConfig = ''
1416 allow all;
1417 access_log off;
1418 '';
1419 };
1420 "= /" = {
1421 priority = 100;
1422 extraConfig = ''
1423 if ( $http_user_agent ~ ^DavClnt ) {
1424 return 302 /remote.php/webdav/$is_args$args;
1425 }
1426 '';
1427 };
1428 "^~ /.well-known" = {
1429 priority = 210;
1430 extraConfig = ''
1431 absolute_redirect off;
1432 location = /.well-known/carddav {
1433 return 301 /remote.php/dav/;
1434 }
1435 location = /.well-known/caldav {
1436 return 301 /remote.php/dav/;
1437 }
1438 location ~ ^/\.well-known/(?!acme-challenge|pki-validation) {
1439 return 301 /index.php$request_uri;
1440 }
1441 try_files $uri $uri/ =404;
1442 '';
1443 };
1444 "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)" = {
1445 priority = 450;
1446 extraConfig = ''
1447 return 404;
1448 '';
1449 };
1450 "~ ^/(?:\\.|autotest|occ|issue|indie|db_|console)" = {
1451 priority = 450;
1452 extraConfig = ''
1453 return 404;
1454 '';
1455 };
1456 "~ \\.php(?:$|/)" = {
1457 priority = 500;
1458 extraConfig = ''
1459 # legacy support (i.e. static files and directories in cfg.package)
1460 rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[s${
1461 optionalString (!ocmProviderIsNotAStaticDirAnymore) "m"
1462 }]-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri;
1463 include ${config.services.nginx.package}/conf/fastcgi.conf;
1464 fastcgi_split_path_info ^(.+?\.php)(\\/.*)$;
1465 set $path_info $fastcgi_path_info;
1466 try_files $fastcgi_script_name =404;
1467 fastcgi_param PATH_INFO $path_info;
1468 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
1469 fastcgi_param HTTPS ${if cfg.https then "on" else "off"};
1470 fastcgi_param modHeadersAvailable true;
1471 fastcgi_param front_controller_active true;
1472 fastcgi_pass unix:${fpm.socket};
1473 fastcgi_intercept_errors on;
1474 fastcgi_request_buffering off;
1475 fastcgi_read_timeout ${builtins.toString cfg.fastcgiTimeout}s;
1476 '';
1477 };
1478 "~ \\.(?:css|js|mjs|svg|gif|png|jpg|jpeg|ico|wasm|tflite|map|html|ttf|bcmap|mp4|webm|ogg|flac)$".extraConfig =
1479 ''
1480 try_files $uri /index.php$request_uri;
1481 expires 6M;
1482 access_log off;
1483 location ~ \.mjs$ {
1484 default_type text/javascript;
1485 }
1486 location ~ \.wasm$ {
1487 default_type application/wasm;
1488 }
1489 '';
1490 "~ ^\\/(?:updater|ocs-provider${
1491 optionalString (!ocmProviderIsNotAStaticDirAnymore) "|ocm-provider"
1492 })(?:$|\\/)".extraConfig =
1493 ''
1494 try_files $uri/ =404;
1495 index index.php;
1496 '';
1497 "/remote" = {
1498 priority = 1500;
1499 extraConfig = ''
1500 return 301 /remote.php$request_uri;
1501 '';
1502 };
1503 "/" = {
1504 priority = 1600;
1505 extraConfig = ''
1506 try_files $uri $uri/ /index.php$request_uri;
1507 '';
1508 };
1509 };
1510 extraConfig = ''
1511 index index.php index.html /index.php$request_uri;
1512 ${optionalString (cfg.nginx.recommendedHttpHeaders) ''
1513 add_header X-Content-Type-Options nosniff;
1514 add_header X-XSS-Protection "1; mode=block";
1515 add_header X-Robots-Tag "noindex, nofollow";
1516 add_header X-Download-Options noopen;
1517 add_header X-Permitted-Cross-Domain-Policies none;
1518 add_header X-Frame-Options sameorigin;
1519 add_header Referrer-Policy no-referrer;
1520 ''}
1521 ${optionalString (cfg.https) ''
1522 add_header Strict-Transport-Security "max-age=${toString cfg.nginx.hstsMaxAge}; includeSubDomains" always;
1523 ''}
1524 client_max_body_size ${cfg.maxUploadSize};
1525 fastcgi_buffers 64 4K;
1526 fastcgi_hide_header X-Powered-By;
1527 gzip on;
1528 gzip_vary on;
1529 gzip_comp_level 4;
1530 gzip_min_length 256;
1531 gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
1532 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;
1533
1534 ${optionalString cfg.webfinger ''
1535 rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
1536 rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;
1537 ''}
1538 '';
1539 };
1540 }
1541 ]);
1542
1543 meta.doc = ./nextcloud.md;
1544 meta.maintainers = teams.nextcloud;
1545}