at 25.11-pre 56 kB view raw
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}