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