at 23.11-pre 47 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.nextcloud; 7 fpm = config.services.phpfpm.pools.nextcloud; 8 9 jsonFormat = pkgs.formats.json {}; 10 11 inherit (cfg) datadir; 12 13 phpPackage = cfg.phpPackage.buildEnv { 14 extensions = { enabled, all }: 15 (with all; 16 # disable default openssl extension 17 (lib.filter (e: e.pname != "php-openssl") enabled) 18 # use OpenSSL 1.1 for RC4 Nextcloud encryption if user 19 # has acknowledged the brokenness of the ciphers (RC4). 20 # TODO: remove when https://github.com/nextcloud/server/issues/32003 is fixed. 21 ++ (if cfg.enableBrokenCiphersForSSE then [ cfg.phpPackage.extensions.openssl-legacy ] else [ cfg.phpPackage.extensions.openssl ]) 22 ++ optional cfg.enableImagemagick imagick 23 # Optionally enabled depending on caching settings 24 ++ optional cfg.caching.apcu apcu 25 ++ optional cfg.caching.redis redis 26 ++ optional cfg.caching.memcached memcached 27 ) 28 ++ cfg.phpExtraExtensions all; # Enabled by user 29 extraConfig = toKeyValue phpOptions; 30 }; 31 32 toKeyValue = generators.toKeyValue { 33 mkKeyValue = generators.mkKeyValueDefault {} " = "; 34 }; 35 36 phpOptions = { 37 upload_max_filesize = cfg.maxUploadSize; 38 post_max_size = cfg.maxUploadSize; 39 memory_limit = cfg.maxUploadSize; 40 } // cfg.phpOptions 41 // optionalAttrs cfg.caching.apcu { 42 "apc.enable_cli" = "1"; 43 }; 44 45 occ = pkgs.writeScriptBin "nextcloud-occ" '' 46 #! ${pkgs.runtimeShell} 47 cd ${cfg.package} 48 sudo=exec 49 if [[ "$USER" != nextcloud ]]; then 50 sudo='exec /run/wrappers/bin/sudo -u nextcloud --preserve-env=NEXTCLOUD_CONFIG_DIR --preserve-env=OC_PASS' 51 fi 52 export NEXTCLOUD_CONFIG_DIR="${datadir}/config" 53 $sudo \ 54 ${phpPackage}/bin/php \ 55 occ "$@" 56 ''; 57 58 inherit (config.system) stateVersion; 59 60 mysqlLocal = cfg.database.createLocally && cfg.config.dbtype == "mysql"; 61 pgsqlLocal = cfg.database.createLocally && cfg.config.dbtype == "pgsql"; 62 63in { 64 65 imports = [ 66 (mkRemovedOptionModule [ "services" "nextcloud" "config" "adminpass" ] '' 67 Please use `services.nextcloud.config.adminpassFile' instead! 68 '') 69 (mkRemovedOptionModule [ "services" "nextcloud" "config" "dbpass" ] '' 70 Please use `services.nextcloud.config.dbpassFile' instead! 71 '') 72 (mkRemovedOptionModule [ "services" "nextcloud" "nginx" "enable" ] '' 73 The nextcloud module supports `nginx` as reverse-proxy by default and doesn't 74 support other reverse-proxies officially. 75 76 However it's possible to use an alternative reverse-proxy by 77 78 * disabling nginx 79 * setting `listen.owner` & `listen.group` in the phpfpm-pool to a different value 80 81 Further details about this can be found in the `Nextcloud`-section of the NixOS-manual 82 (which can be opened e.g. by running `nixos-help`). 83 '') 84 (mkRemovedOptionModule [ "services" "nextcloud" "disableImagemagick" ] '' 85 Use services.nextcloud.enableImagemagick instead. 86 '') 87 ]; 88 89 options.services.nextcloud = { 90 enable = mkEnableOption (lib.mdDoc "nextcloud"); 91 92 enableBrokenCiphersForSSE = mkOption { 93 type = types.bool; 94 default = versionOlder stateVersion "22.11"; 95 defaultText = literalExpression "versionOlder system.stateVersion \"22.11\""; 96 description = lib.mdDoc '' 97 This option enables using the OpenSSL PHP extension linked against OpenSSL 1.1 98 rather than latest OpenSSL ( 3), this is not recommended unless you need 99 it for server-side encryption (SSE). SSE uses the legacy RC4 cipher which is 100 considered broken for several years now. See also [RFC7465](https://datatracker.ietf.org/doc/html/rfc7465). 101 102 This cipher has been disabled in OpenSSL 3 and requires 103 a specific legacy profile to re-enable it. 104 105 If you deploy Nextcloud using OpenSSL  3 for PHP and have 106 server-side encryption configured, you will not be able to access 107 your files anymore. Enabling this option can restore access to your files. 108 Upon testing we didn't encounter any data corruption when turning 109 this on and off again, but this cannot be guaranteed for 110 each Nextcloud installation. 111 112 It is `true` by default for systems with a [](#opt-system.stateVersion) below 113 `22.11` to make sure that existing installations won't break on update. On newer 114 NixOS systems you have to explicitly enable it on your own. 115 116 Please note that this only provides additional value when using 117 external storage such as S3 since it's not an end-to-end encryption. 118 If this is not the case, 119 it is advised to [disable server-side encryption](https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html#disabling-encryption) and set this to `false`. 120 121 In the future, Nextcloud may move to AES-256-GCM, by then, 122 this option will be removed. 123 ''; 124 }; 125 hostName = mkOption { 126 type = types.str; 127 description = lib.mdDoc "FQDN for the nextcloud instance."; 128 }; 129 home = mkOption { 130 type = types.str; 131 default = "/var/lib/nextcloud"; 132 description = lib.mdDoc "Storage path of nextcloud."; 133 }; 134 datadir = mkOption { 135 type = types.str; 136 default = config.services.nextcloud.home; 137 defaultText = literalExpression "config.services.nextcloud.home"; 138 description = lib.mdDoc '' 139 Data storage path of nextcloud. Will be [](#opt-services.nextcloud.home) by default. 140 This folder will be populated with a config.php and data folder which contains the state of the instance (excl the database)."; 141 ''; 142 example = "/mnt/nextcloud-file"; 143 }; 144 extraApps = mkOption { 145 type = types.attrsOf types.package; 146 default = { }; 147 description = lib.mdDoc '' 148 Extra apps to install. Should be an attrSet of appid to packages generated by fetchNextcloudApp. 149 The appid must be identical to the "id" value in the apps appinfo/info.xml. 150 Using this will disable the appstore to prevent Nextcloud from updating these apps (see [](#opt-services.nextcloud.appstoreEnable)). 151 ''; 152 example = literalExpression '' 153 { 154 maps = pkgs.fetchNextcloudApp { 155 name = "maps"; 156 sha256 = "007y80idqg6b6zk6kjxg4vgw0z8fsxs9lajnv49vv1zjy6jx2i1i"; 157 url = "https://github.com/nextcloud/maps/releases/download/v0.1.9/maps-0.1.9.tar.gz"; 158 version = "0.1.9"; 159 }; 160 phonetrack = pkgs.fetchNextcloudApp { 161 name = "phonetrack"; 162 sha256 = "0qf366vbahyl27p9mshfma1as4nvql6w75zy2zk5xwwbp343vsbc"; 163 url = "https://gitlab.com/eneiluj/phonetrack-oc/-/wikis/uploads/931aaaf8dca24bf31a7e169a83c17235/phonetrack-0.6.9.tar.gz"; 164 version = "0.6.9"; 165 }; 166 } 167 ''; 168 }; 169 extraAppsEnable = mkOption { 170 type = types.bool; 171 default = true; 172 description = lib.mdDoc '' 173 Automatically enable the apps in [](#opt-services.nextcloud.extraApps) every time nextcloud starts. 174 If set to false, apps need to be enabled in the Nextcloud user interface or with nextcloud-occ app:enable. 175 ''; 176 }; 177 appstoreEnable = mkOption { 178 type = types.nullOr types.bool; 179 default = null; 180 example = true; 181 description = lib.mdDoc '' 182 Allow the installation of apps and app updates from the store. 183 Enabled by default unless there are packages in [](#opt-services.nextcloud.extraApps). 184 Set to true to force enable the store even if [](#opt-services.nextcloud.extraApps) is used. 185 Set to false to disable the installation of apps from the global appstore. App management is always enabled regardless of this setting. 186 ''; 187 }; 188 logLevel = mkOption { 189 type = types.ints.between 0 4; 190 default = 2; 191 description = lib.mdDoc "Log level value between 0 (DEBUG) and 4 (FATAL)."; 192 }; 193 logType = mkOption { 194 type = types.enum [ "errorlog" "file" "syslog" "systemd" ]; 195 default = "syslog"; 196 description = lib.mdDoc '' 197 Logging backend to use. 198 systemd requires the php-systemd package to be added to services.nextcloud.phpExtraExtensions. 199 See the [nextcloud documentation](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/logging_configuration.html) for details. 200 ''; 201 }; 202 https = mkOption { 203 type = types.bool; 204 default = false; 205 description = lib.mdDoc "Use https for generated links."; 206 }; 207 package = mkOption { 208 type = types.package; 209 description = lib.mdDoc "Which package to use for the Nextcloud instance."; 210 relatedPackages = [ "nextcloud25" "nextcloud26" ]; 211 }; 212 phpPackage = mkOption { 213 type = types.package; 214 relatedPackages = [ "php80" "php81" ]; 215 defaultText = "pkgs.php"; 216 description = lib.mdDoc '' 217 PHP package to use for Nextcloud. 218 ''; 219 }; 220 221 maxUploadSize = mkOption { 222 default = "512M"; 223 type = types.str; 224 description = lib.mdDoc '' 225 Defines the upload limit for files. This changes the relevant options 226 in php.ini and nginx if enabled. 227 ''; 228 }; 229 230 skeletonDirectory = mkOption { 231 default = ""; 232 type = types.str; 233 description = lib.mdDoc '' 234 The directory where the skeleton files are located. These files will be 235 copied to the data directory of new users. Leave empty to not copy any 236 skeleton files. 237 ''; 238 }; 239 240 webfinger = mkOption { 241 type = types.bool; 242 default = false; 243 description = lib.mdDoc '' 244 Enable this option if you plan on using the webfinger plugin. 245 The appropriate nginx rewrite rules will be added to your configuration. 246 ''; 247 }; 248 249 phpExtraExtensions = mkOption { 250 type = with types; functionTo (listOf package); 251 default = all: []; 252 defaultText = literalExpression "all: []"; 253 description = lib.mdDoc '' 254 Additional PHP extensions to use for nextcloud. 255 By default, only extensions necessary for a vanilla nextcloud installation are enabled, 256 but you may choose from the list of available extensions and add further ones. 257 This is sometimes necessary to be able to install a certain nextcloud app that has additional requirements. 258 ''; 259 example = literalExpression '' 260 all: [ all.pdlib all.bz2 ] 261 ''; 262 }; 263 264 phpOptions = mkOption { 265 type = types.attrsOf types.str; 266 default = { 267 short_open_tag = "Off"; 268 expose_php = "Off"; 269 error_reporting = "E_ALL & ~E_DEPRECATED & ~E_STRICT"; 270 display_errors = "stderr"; 271 "opcache.enable_cli" = "1"; 272 "opcache.interned_strings_buffer" = "8"; 273 "opcache.max_accelerated_files" = "10000"; 274 "opcache.memory_consumption" = "128"; 275 "opcache.revalidate_freq" = "1"; 276 "opcache.fast_shutdown" = "1"; 277 "openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt"; 278 catch_workers_output = "yes"; 279 }; 280 description = lib.mdDoc '' 281 Options for PHP's php.ini file for nextcloud. 282 ''; 283 }; 284 285 poolSettings = mkOption { 286 type = with types; attrsOf (oneOf [ str int bool ]); 287 default = { 288 "pm" = "dynamic"; 289 "pm.max_children" = "32"; 290 "pm.start_servers" = "2"; 291 "pm.min_spare_servers" = "2"; 292 "pm.max_spare_servers" = "4"; 293 "pm.max_requests" = "500"; 294 }; 295 description = lib.mdDoc '' 296 Options for nextcloud's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives. 297 ''; 298 }; 299 300 poolConfig = mkOption { 301 type = types.nullOr types.lines; 302 default = null; 303 description = lib.mdDoc '' 304 Options for nextcloud's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives. 305 ''; 306 }; 307 308 fastcgiTimeout = mkOption { 309 type = types.int; 310 default = 120; 311 description = lib.mdDoc '' 312 FastCGI timeout for database connection in seconds. 313 ''; 314 }; 315 316 database = { 317 318 createLocally = mkOption { 319 type = types.bool; 320 default = false; 321 description = lib.mdDoc '' 322 Create the database and database user locally. 323 ''; 324 }; 325 326 }; 327 328 329 config = { 330 dbtype = mkOption { 331 type = types.enum [ "sqlite" "pgsql" "mysql" ]; 332 default = "sqlite"; 333 description = lib.mdDoc "Database type."; 334 }; 335 dbname = mkOption { 336 type = types.nullOr types.str; 337 default = "nextcloud"; 338 description = lib.mdDoc "Database name."; 339 }; 340 dbuser = mkOption { 341 type = types.nullOr types.str; 342 default = "nextcloud"; 343 description = lib.mdDoc "Database user."; 344 }; 345 dbpassFile = mkOption { 346 type = types.nullOr types.str; 347 default = null; 348 description = lib.mdDoc '' 349 The full path to a file that contains the database password. 350 ''; 351 }; 352 dbhost = mkOption { 353 type = types.nullOr types.str; 354 default = 355 if pgsqlLocal then "/run/postgresql" 356 else if mysqlLocal then "localhost:/run/mysqld/mysqld.sock" 357 else "localhost"; 358 defaultText = "localhost"; 359 description = lib.mdDoc '' 360 Database host or socket path. Defaults to the correct unix socket 361 instead if `services.nextcloud.database.createLocally` is true and 362 `services.nextcloud.config.dbtype` is either `pgsql` or `mysql`. 363 ''; 364 }; 365 dbport = mkOption { 366 type = with types; nullOr (either int str); 367 default = null; 368 description = lib.mdDoc "Database port."; 369 }; 370 dbtableprefix = mkOption { 371 type = types.nullOr types.str; 372 default = null; 373 description = lib.mdDoc "Table prefix in Nextcloud database."; 374 }; 375 adminuser = mkOption { 376 type = types.str; 377 default = "root"; 378 description = lib.mdDoc "Admin username."; 379 }; 380 adminpassFile = mkOption { 381 type = types.str; 382 description = lib.mdDoc '' 383 The full path to a file that contains the admin's password. Must be 384 readable by user `nextcloud`. 385 ''; 386 }; 387 388 extraTrustedDomains = mkOption { 389 type = types.listOf types.str; 390 default = []; 391 description = lib.mdDoc '' 392 Trusted domains, from which the nextcloud installation will be 393 accessible. You don't need to add 394 `services.nextcloud.hostname` here. 395 ''; 396 }; 397 398 trustedProxies = mkOption { 399 type = types.listOf types.str; 400 default = []; 401 description = lib.mdDoc '' 402 Trusted proxies, to provide if the nextcloud installation is being 403 proxied to secure against e.g. spoofing. 404 ''; 405 }; 406 407 overwriteProtocol = mkOption { 408 type = types.nullOr (types.enum [ "http" "https" ]); 409 default = null; 410 example = "https"; 411 412 description = lib.mdDoc '' 413 Force Nextcloud to always use HTTPS i.e. for link generation. Nextcloud 414 uses the currently used protocol by default, but when behind a reverse-proxy, 415 it may use `http` for everything although Nextcloud 416 may be served via HTTPS. 417 ''; 418 }; 419 420 defaultPhoneRegion = mkOption { 421 default = null; 422 type = types.nullOr types.str; 423 example = "DE"; 424 description = lib.mdDoc '' 425 ::: {.warning} 426 This option exists since Nextcloud 21! If older versions are used, 427 this will throw an eval-error! 428 ::: 429 430 [ISO 3611-1](https://www.iso.org/iso-3166-country-codes.html) 431 country codes for automatic phone-number detection without a country code. 432 433 With e.g. `DE` set, the `+49` can be omitted for 434 phone-numbers. 435 ''; 436 }; 437 438 objectstore = { 439 s3 = { 440 enable = mkEnableOption (lib.mdDoc '' 441 S3 object storage as primary storage. 442 443 This mounts a bucket on an Amazon S3 object storage or compatible 444 implementation into the virtual filesystem. 445 446 Further details about this feature can be found in the 447 [upstream documentation](https://docs.nextcloud.com/server/22/admin_manual/configuration_files/primary_storage.html). 448 ''); 449 bucket = mkOption { 450 type = types.str; 451 example = "nextcloud"; 452 description = lib.mdDoc '' 453 The name of the S3 bucket. 454 ''; 455 }; 456 autocreate = mkOption { 457 type = types.bool; 458 description = lib.mdDoc '' 459 Create the objectstore if it does not exist. 460 ''; 461 }; 462 key = mkOption { 463 type = types.str; 464 example = "EJ39ITYZEUH5BGWDRUFY"; 465 description = lib.mdDoc '' 466 The access key for the S3 bucket. 467 ''; 468 }; 469 secretFile = mkOption { 470 type = types.str; 471 example = "/var/nextcloud-objectstore-s3-secret"; 472 description = lib.mdDoc '' 473 The full path to a file that contains the access secret. Must be 474 readable by user `nextcloud`. 475 ''; 476 }; 477 hostname = mkOption { 478 type = types.nullOr types.str; 479 default = null; 480 example = "example.com"; 481 description = lib.mdDoc '' 482 Required for some non-Amazon implementations. 483 ''; 484 }; 485 port = mkOption { 486 type = types.nullOr types.port; 487 default = null; 488 description = lib.mdDoc '' 489 Required for some non-Amazon implementations. 490 ''; 491 }; 492 useSsl = mkOption { 493 type = types.bool; 494 default = true; 495 description = lib.mdDoc '' 496 Use SSL for objectstore access. 497 ''; 498 }; 499 region = mkOption { 500 type = types.nullOr types.str; 501 default = null; 502 example = "REGION"; 503 description = lib.mdDoc '' 504 Required for some non-Amazon implementations. 505 ''; 506 }; 507 usePathStyle = mkOption { 508 type = types.bool; 509 default = false; 510 description = lib.mdDoc '' 511 Required for some non-Amazon S3 implementations. 512 513 Ordinarily, requests will be made with 514 `http://bucket.hostname.domain/`, but with path style 515 enabled requests are made with 516 `http://hostname.domain/bucket` instead. 517 ''; 518 }; 519 sseCKeyFile = mkOption { 520 type = types.nullOr types.path; 521 default = null; 522 example = "/var/nextcloud-objectstore-s3-sse-c-key"; 523 description = lib.mdDoc '' 524 If provided this is the full path to a file that contains the key 525 to enable [server-side encryption with customer-provided keys][1] 526 (SSE-C). 527 528 The file must contain a random 32-byte key encoded as a base64 529 string, e.g. generated with the command 530 531 ``` 532 openssl rand 32 | base64 533 ``` 534 535 Must be readable by user `nextcloud`. 536 537 [1]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerSideEncryptionCustomerKeys.html 538 ''; 539 }; 540 }; 541 }; 542 }; 543 544 enableImagemagick = mkEnableOption (lib.mdDoc '' 545 the ImageMagick module for PHP. 546 This is used by the theming app and for generating previews of certain images (e.g. SVG and HEIF). 547 You may want to disable it for increased security. In that case, previews will still be available 548 for some images (e.g. JPEG and PNG). 549 See <https://github.com/nextcloud/server/issues/13099>. 550 '') // { 551 default = true; 552 }; 553 554 configureRedis = lib.mkOption { 555 type = lib.types.bool; 556 default = config.services.nextcloud.notify_push.enable; 557 defaultText = literalExpression "config.services.nextcloud.notify_push.enable"; 558 description = lib.mdDoc '' 559 Whether to configure nextcloud to use the recommended redis settings for small instances. 560 561 ::: {.note} 562 The `notify_push` app requires redis to be configured. If this option is turned off, this must be configured manually. 563 ::: 564 ''; 565 }; 566 567 caching = { 568 apcu = mkOption { 569 type = types.bool; 570 default = true; 571 description = lib.mdDoc '' 572 Whether to load the APCu module into PHP. 573 ''; 574 }; 575 redis = mkOption { 576 type = types.bool; 577 default = false; 578 description = lib.mdDoc '' 579 Whether to load the Redis module into PHP. 580 You still need to enable Redis in your config.php. 581 See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html 582 ''; 583 }; 584 memcached = mkOption { 585 type = types.bool; 586 default = false; 587 description = lib.mdDoc '' 588 Whether to load the Memcached module into PHP. 589 You still need to enable Memcached in your config.php. 590 See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html 591 ''; 592 }; 593 }; 594 autoUpdateApps = { 595 enable = mkOption { 596 type = types.bool; 597 default = false; 598 description = lib.mdDoc '' 599 Run regular auto update of all apps installed from the nextcloud app store. 600 ''; 601 }; 602 startAt = mkOption { 603 type = with types; either str (listOf str); 604 default = "05:00:00"; 605 example = "Sun 14:00:00"; 606 description = lib.mdDoc '' 607 When to run the update. See `systemd.services.<name>.startAt`. 608 ''; 609 }; 610 }; 611 occ = mkOption { 612 type = types.package; 613 default = occ; 614 defaultText = literalMD "generated script"; 615 internal = true; 616 description = lib.mdDoc '' 617 The nextcloud-occ program preconfigured to target this Nextcloud instance. 618 ''; 619 }; 620 globalProfiles = mkEnableOption (lib.mdDoc "global profiles") // { 621 description = lib.mdDoc '' 622 Makes user-profiles globally available under `nextcloud.tld/u/user.name`. 623 Even though it's enabled by default in Nextcloud, it must be explicitly enabled 624 here because it has the side-effect that personal information is even accessible to 625 unauthenticated users by default. 626 627 By default, the following properties are set to Show to everyone 628 if this flag is enabled: 629 - About 630 - Full name 631 - Headline 632 - Organisation 633 - Profile picture 634 - Role 635 - Twitter 636 - Website 637 638 Only has an effect in Nextcloud 23 and later. 639 ''; 640 }; 641 642 extraOptions = mkOption { 643 type = jsonFormat.type; 644 default = {}; 645 description = lib.mdDoc '' 646 Extra options which should be appended to nextcloud's config.php file. 647 ''; 648 example = literalExpression '' { 649 redis = { 650 host = "/run/redis/redis.sock"; 651 port = 0; 652 dbindex = 0; 653 password = "secret"; 654 timeout = 1.5; 655 }; 656 } ''; 657 }; 658 659 secretFile = mkOption { 660 type = types.nullOr types.str; 661 default = null; 662 description = lib.mdDoc '' 663 Secret options which will be appended to nextcloud's config.php file (written as JSON, in the same 664 form as the [](#opt-services.nextcloud.extraOptions) option), for example 665 `{"redis":{"password":"secret"}}`. 666 ''; 667 }; 668 669 nginx = { 670 recommendedHttpHeaders = mkOption { 671 type = types.bool; 672 default = true; 673 description = lib.mdDoc "Enable additional recommended HTTP response headers"; 674 }; 675 hstsMaxAge = mkOption { 676 type = types.ints.positive; 677 default = 15552000; 678 description = lib.mdDoc '' 679 Value for the `max-age` directive of the HTTP 680 `Strict-Transport-Security` header. 681 682 See section 6.1.1 of IETF RFC 6797 for detailed information on this 683 directive and header. 684 ''; 685 }; 686 }; 687 }; 688 689 config = mkIf cfg.enable (mkMerge [ 690 { warnings = let 691 latest = 26; 692 upgradeWarning = major: nixos: 693 '' 694 A legacy Nextcloud install (from before NixOS ${nixos}) may be installed. 695 696 After nextcloud${toString major} is installed successfully, you can safely upgrade 697 to ${toString (major + 1)}. The latest version available is nextcloud${toString latest}. 698 699 Please note that Nextcloud doesn't support upgrades across multiple major versions 700 (i.e. an upgrade from 16 is possible to 17, but not 16 to 18). 701 702 The package can be upgraded by explicitly declaring the service-option 703 `services.nextcloud.package`. 704 ''; 705 706 in (optional (cfg.poolConfig != null) '' 707 Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release. 708 Please migrate your configuration to config.services.nextcloud.poolSettings. 709 '') 710 ++ (optional (versionOlder cfg.package.version "23") (upgradeWarning 22 "22.05")) 711 ++ (optional (versionOlder cfg.package.version "24") (upgradeWarning 23 "22.05")) 712 ++ (optional (versionOlder cfg.package.version "25") (upgradeWarning 24 "22.11")) 713 ++ (optional (versionOlder cfg.package.version "26") (upgradeWarning 25 "23.05")) 714 ++ (optional cfg.enableBrokenCiphersForSSE '' 715 You're using PHP's openssl extension built against OpenSSL 1.1 for Nextcloud. 716 This is only necessary if you're using Nextcloud's server-side encryption. 717 Please keep in mind that it's using the broken RC4 cipher. 718 719 If you don't use that feature, you can switch to OpenSSL 3 and get 720 rid of this warning by declaring 721 722 services.nextcloud.enableBrokenCiphersForSSE = false; 723 724 If you need to use server-side encryption you can ignore this warning. 725 Otherwise you'd have to disable server-side encryption first in order 726 to be able to safely disable this option and get rid of this warning. 727 See <https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html#disabling-encryption> on how to achieve this. 728 729 For more context, here is the implementing pull request: https://github.com/NixOS/nixpkgs/pull/198470 730 '') 731 ++ (optional (cfg.enableBrokenCiphersForSSE && versionAtLeast cfg.package.version "26") '' 732 Nextcloud26 supports RC4 without requiring legacy OpenSSL, so 733 `services.nextcloud.enableBrokenCiphersForSSE` can be set to `false`. 734 ''); 735 736 services.nextcloud.package = with pkgs; 737 mkDefault ( 738 if pkgs ? nextcloud 739 then throw '' 740 The `pkgs.nextcloud`-attribute has been removed. If it's supposed to be the default 741 nextcloud defined in an overlay, please set `services.nextcloud.package` to 742 `pkgs.nextcloud`. 743 '' 744 else if versionOlder stateVersion "22.11" then nextcloud24 745 else if versionOlder stateVersion "23.05" then nextcloud25 746 else nextcloud26 747 ); 748 749 services.nextcloud.phpPackage = 750 if versionOlder cfg.package.version "26" then pkgs.php81 751 else pkgs.php82; 752 } 753 754 { assertions = [ 755 { assertion = cfg.database.createLocally -> cfg.config.dbpassFile == null; 756 message = '' 757 Using `services.nextcloud.database.createLocally` with database 758 password authentication is no longer supported. 759 760 If you use an external database (or want to use password auth for any 761 other reason), set `services.nextcloud.database.createLocally` to 762 `false`. The database won't be managed for you (use `services.mysql` 763 if you want to set it up). 764 765 If you want this module to manage your nextcloud database for you, 766 unset `services.nextcloud.config.dbpassFile` and 767 `services.nextcloud.config.dbhost` to use socket authentication 768 instead of password. 769 ''; 770 } 771 ]; } 772 773 { systemd.timers.nextcloud-cron = { 774 wantedBy = [ "timers.target" ]; 775 after = [ "nextcloud-setup.service" ]; 776 timerConfig.OnBootSec = "5m"; 777 timerConfig.OnUnitActiveSec = "5m"; 778 timerConfig.Unit = "nextcloud-cron.service"; 779 }; 780 781 systemd.tmpfiles.rules = ["d ${cfg.home} 0750 nextcloud nextcloud"]; 782 783 systemd.services = { 784 # When upgrading the Nextcloud package, Nextcloud can report errors such as 785 # "The files of the app [all apps in /var/lib/nextcloud/apps] were not replaced correctly" 786 # Restarting phpfpm on Nextcloud package update fixes these issues (but this is a workaround). 787 phpfpm-nextcloud.restartTriggers = [ cfg.package ]; 788 789 nextcloud-setup = let 790 c = cfg.config; 791 writePhpArray = a: "[${concatMapStringsSep "," (val: ''"${toString val}"'') a}]"; 792 requiresReadSecretFunction = c.dbpassFile != null || c.objectstore.s3.enable; 793 objectstoreConfig = let s3 = c.objectstore.s3; in optionalString s3.enable '' 794 'objectstore' => [ 795 'class' => '\\OC\\Files\\ObjectStore\\S3', 796 'arguments' => [ 797 'bucket' => '${s3.bucket}', 798 'autocreate' => ${boolToString s3.autocreate}, 799 'key' => '${s3.key}', 800 'secret' => nix_read_secret('${s3.secretFile}'), 801 ${optionalString (s3.hostname != null) "'hostname' => '${s3.hostname}',"} 802 ${optionalString (s3.port != null) "'port' => ${toString s3.port},"} 803 'use_ssl' => ${boolToString s3.useSsl}, 804 ${optionalString (s3.region != null) "'region' => '${s3.region}',"} 805 'use_path_style' => ${boolToString s3.usePathStyle}, 806 ${optionalString (s3.sseCKeyFile != null) "'sse_c_key' => nix_read_secret('${s3.sseCKeyFile}'),"} 807 ], 808 ] 809 ''; 810 811 showAppStoreSetting = cfg.appstoreEnable != null || cfg.extraApps != {}; 812 renderedAppStoreSetting = 813 let 814 x = cfg.appstoreEnable; 815 in 816 if x == null then "false" 817 else boolToString x; 818 819 nextcloudGreaterOrEqualThan = req: versionAtLeast cfg.package.version req; 820 821 overrideConfig = pkgs.writeText "nextcloud-config.php" '' 822 <?php 823 ${optionalString requiresReadSecretFunction '' 824 function nix_read_secret($file) { 825 if (!file_exists($file)) { 826 throw new \RuntimeException(sprintf( 827 "Cannot start Nextcloud, secret file %s set by NixOS doesn't seem to " 828 . "exist! Please make sure that the file exists and has appropriate " 829 . "permissions for user & group 'nextcloud'!", 830 $file 831 )); 832 } 833 return trim(file_get_contents($file)); 834 }''} 835 function nix_decode_json_file($file, $error) { 836 if (!file_exists($file)) { 837 throw new \RuntimeException(sprintf($error, $file)); 838 } 839 $decoded = json_decode(file_get_contents($file), true); 840 841 if (json_last_error() !== JSON_ERROR_NONE) { 842 throw new \RuntimeException(sprintf("Cannot decode %s, because: %s", $file, json_last_error_msg())); 843 } 844 845 return $decoded; 846 } 847 $CONFIG = [ 848 'apps_paths' => [ 849 ${optionalString (cfg.extraApps != { }) "[ 'path' => '${cfg.home}/nix-apps', 'url' => '/nix-apps', 'writable' => false ],"} 850 [ 'path' => '${cfg.home}/apps', 'url' => '/apps', 'writable' => false ], 851 [ 'path' => '${cfg.home}/store-apps', 'url' => '/store-apps', 'writable' => true ], 852 ], 853 ${optionalString (showAppStoreSetting) "'appstoreenabled' => ${renderedAppStoreSetting},"} 854 'datadirectory' => '${datadir}/data', 855 'skeletondirectory' => '${cfg.skeletonDirectory}', 856 ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"} 857 'log_type' => '${cfg.logType}', 858 'loglevel' => '${builtins.toString cfg.logLevel}', 859 ${optionalString (c.overwriteProtocol != null) "'overwriteprotocol' => '${c.overwriteProtocol}',"} 860 ${optionalString (c.dbname != null) "'dbname' => '${c.dbname}',"} 861 ${optionalString (c.dbhost != null) "'dbhost' => '${c.dbhost}',"} 862 ${optionalString (c.dbport != null) "'dbport' => '${toString c.dbport}',"} 863 ${optionalString (c.dbuser != null) "'dbuser' => '${c.dbuser}',"} 864 ${optionalString (c.dbtableprefix != null) "'dbtableprefix' => '${toString c.dbtableprefix}',"} 865 ${optionalString (c.dbpassFile != null) '' 866 'dbpassword' => nix_read_secret( 867 "${c.dbpassFile}" 868 ), 869 '' 870 } 871 'dbtype' => '${c.dbtype}', 872 'trusted_domains' => ${writePhpArray ([ cfg.hostName ] ++ c.extraTrustedDomains)}, 873 'trusted_proxies' => ${writePhpArray (c.trustedProxies)}, 874 ${optionalString (c.defaultPhoneRegion != null) "'default_phone_region' => '${c.defaultPhoneRegion}',"} 875 ${optionalString (nextcloudGreaterOrEqualThan "23") "'profile.enabled' => ${boolToString cfg.globalProfiles},"} 876 ${objectstoreConfig} 877 ]; 878 879 $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file( 880 "${jsonFormat.generate "nextcloud-extraOptions.json" cfg.extraOptions}", 881 "impossible: this should never happen (decoding generated extraOptions file %s failed)" 882 )); 883 884 ${optionalString (cfg.secretFile != null) '' 885 $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file( 886 "${cfg.secretFile}", 887 "Cannot start Nextcloud, secrets file %s set by NixOS doesn't exist!" 888 )); 889 ''} 890 ''; 891 occInstallCmd = let 892 mkExport = { arg, value }: "export ${arg}=${value}"; 893 dbpass = { 894 arg = "DBPASS"; 895 value = if c.dbpassFile != null 896 then ''"$(<"${toString c.dbpassFile}")"'' 897 else ''""''; 898 }; 899 adminpass = { 900 arg = "ADMINPASS"; 901 value = ''"$(<"${toString c.adminpassFile}")"''; 902 }; 903 installFlags = concatStringsSep " \\\n " 904 (mapAttrsToList (k: v: "${k} ${toString v}") { 905 "--database" = ''"${c.dbtype}"''; 906 # The following attributes are optional depending on the type of 907 # database. Those that evaluate to null on the left hand side 908 # will be omitted. 909 ${if c.dbname != null then "--database-name" else null} = ''"${c.dbname}"''; 910 ${if c.dbhost != null then "--database-host" else null} = ''"${c.dbhost}"''; 911 ${if c.dbport != null then "--database-port" else null} = ''"${toString c.dbport}"''; 912 ${if c.dbuser != null then "--database-user" else null} = ''"${c.dbuser}"''; 913 "--database-pass" = "\"\$${dbpass.arg}\""; 914 "--admin-user" = ''"${c.adminuser}"''; 915 "--admin-pass" = "\"\$${adminpass.arg}\""; 916 "--data-dir" = ''"${datadir}/data"''; 917 }); 918 in '' 919 ${mkExport dbpass} 920 ${mkExport adminpass} 921 ${occ}/bin/nextcloud-occ maintenance:install \ 922 ${installFlags} 923 ''; 924 occSetTrustedDomainsCmd = concatStringsSep "\n" (imap0 925 (i: v: '' 926 ${occ}/bin/nextcloud-occ config:system:set trusted_domains \ 927 ${toString i} --value="${toString v}" 928 '') ([ cfg.hostName ] ++ cfg.config.extraTrustedDomains)); 929 930 in { 931 wantedBy = [ "multi-user.target" ]; 932 before = [ "phpfpm-nextcloud.service" ]; 933 after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; 934 requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; 935 path = [ occ ]; 936 script = '' 937 ${optionalString (c.dbpassFile != null) '' 938 if [ ! -r "${c.dbpassFile}" ]; then 939 echo "dbpassFile ${c.dbpassFile} is not readable by nextcloud:nextcloud! Aborting..." 940 exit 1 941 fi 942 if [ -z "$(<${c.dbpassFile})" ]; then 943 echo "dbpassFile ${c.dbpassFile} is empty!" 944 exit 1 945 fi 946 ''} 947 if [ ! -r "${c.adminpassFile}" ]; then 948 echo "adminpassFile ${c.adminpassFile} is not readable by nextcloud:nextcloud! Aborting..." 949 exit 1 950 fi 951 if [ -z "$(<${c.adminpassFile})" ]; then 952 echo "adminpassFile ${c.adminpassFile} is empty!" 953 exit 1 954 fi 955 956 ln -sf ${cfg.package}/apps ${cfg.home}/ 957 958 # Install extra apps 959 ln -sfT \ 960 ${pkgs.linkFarm "nix-apps" 961 (mapAttrsToList (name: path: { inherit name path; }) cfg.extraApps)} \ 962 ${cfg.home}/nix-apps 963 964 # create nextcloud directories. 965 # if the directories exist already with wrong permissions, we fix that 966 for dir in ${datadir}/config ${datadir}/data ${cfg.home}/store-apps ${cfg.home}/nix-apps; do 967 if [ ! -e $dir ]; then 968 install -o nextcloud -g nextcloud -d $dir 969 elif [ $(stat -c "%G" $dir) != "nextcloud" ]; then 970 chgrp -R nextcloud $dir 971 fi 972 done 973 974 ln -sf ${overrideConfig} ${datadir}/config/override.config.php 975 976 # Do not install if already installed 977 if [[ ! -e ${datadir}/config/config.php ]]; then 978 ${occInstallCmd} 979 fi 980 981 ${occ}/bin/nextcloud-occ upgrade 982 983 ${occ}/bin/nextcloud-occ config:system:delete trusted_domains 984 985 ${optionalString (cfg.extraAppsEnable && cfg.extraApps != { }) '' 986 # Try to enable apps 987 ${occ}/bin/nextcloud-occ app:enable ${concatStringsSep " " (attrNames cfg.extraApps)} 988 ''} 989 990 ${occSetTrustedDomainsCmd} 991 ''; 992 serviceConfig.Type = "oneshot"; 993 serviceConfig.User = "nextcloud"; 994 # On Nextcloud ≥ 26, it is not necessary to patch the database files to prevent 995 # an automatic creation of the database user. 996 environment.NC_setup_create_db_user = lib.mkIf (nextcloudGreaterOrEqualThan "26") "false"; 997 }; 998 nextcloud-cron = { 999 after = [ "nextcloud-setup.service" ]; 1000 environment.NEXTCLOUD_CONFIG_DIR = "${datadir}/config"; 1001 serviceConfig.Type = "oneshot"; 1002 serviceConfig.User = "nextcloud"; 1003 serviceConfig.ExecStart = "${phpPackage}/bin/php -f ${cfg.package}/cron.php"; 1004 }; 1005 nextcloud-update-plugins = mkIf cfg.autoUpdateApps.enable { 1006 after = [ "nextcloud-setup.service" ]; 1007 serviceConfig.Type = "oneshot"; 1008 serviceConfig.ExecStart = "${occ}/bin/nextcloud-occ app:update --all"; 1009 serviceConfig.User = "nextcloud"; 1010 startAt = cfg.autoUpdateApps.startAt; 1011 }; 1012 }; 1013 1014 services.phpfpm = { 1015 pools.nextcloud = { 1016 user = "nextcloud"; 1017 group = "nextcloud"; 1018 phpPackage = phpPackage; 1019 phpEnv = { 1020 NEXTCLOUD_CONFIG_DIR = "${datadir}/config"; 1021 PATH = "/run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin"; 1022 }; 1023 settings = mapAttrs (name: mkDefault) { 1024 "listen.owner" = config.services.nginx.user; 1025 "listen.group" = config.services.nginx.group; 1026 } // cfg.poolSettings; 1027 extraConfig = cfg.poolConfig; 1028 }; 1029 }; 1030 1031 users.users.nextcloud = { 1032 home = "${cfg.home}"; 1033 group = "nextcloud"; 1034 isSystemUser = true; 1035 }; 1036 users.groups.nextcloud.members = [ "nextcloud" config.services.nginx.user ]; 1037 1038 environment.systemPackages = [ occ ]; 1039 1040 services.mysql = lib.mkIf mysqlLocal { 1041 enable = true; 1042 package = lib.mkDefault pkgs.mariadb; 1043 ensureDatabases = [ cfg.config.dbname ]; 1044 ensureUsers = [{ 1045 name = cfg.config.dbuser; 1046 ensurePermissions = { "${cfg.config.dbname}.*" = "ALL PRIVILEGES"; }; 1047 }]; 1048 }; 1049 1050 services.postgresql = mkIf pgsqlLocal { 1051 enable = true; 1052 ensureDatabases = [ cfg.config.dbname ]; 1053 ensureUsers = [{ 1054 name = cfg.config.dbuser; 1055 ensurePermissions = { "DATABASE ${cfg.config.dbname}" = "ALL PRIVILEGES"; }; 1056 }]; 1057 }; 1058 1059 services.redis.servers.nextcloud = lib.mkIf cfg.configureRedis { 1060 enable = true; 1061 user = "nextcloud"; 1062 }; 1063 1064 services.nextcloud = lib.mkIf cfg.configureRedis { 1065 caching.redis = true; 1066 extraOptions = { 1067 memcache = { 1068 distributed = ''\OC\Memcache\Redis''; 1069 locking = ''\OC\Memcache\Redis''; 1070 }; 1071 redis = { 1072 host = config.services.redis.servers.nextcloud.unixSocket; 1073 port = 0; 1074 }; 1075 }; 1076 }; 1077 1078 services.nginx.enable = mkDefault true; 1079 1080 services.nginx.virtualHosts.${cfg.hostName} = { 1081 root = cfg.package; 1082 locations = { 1083 "= /robots.txt" = { 1084 priority = 100; 1085 extraConfig = '' 1086 allow all; 1087 access_log off; 1088 ''; 1089 }; 1090 "= /" = { 1091 priority = 100; 1092 extraConfig = '' 1093 if ( $http_user_agent ~ ^DavClnt ) { 1094 return 302 /remote.php/webdav/$is_args$args; 1095 } 1096 ''; 1097 }; 1098 "/" = { 1099 priority = 900; 1100 extraConfig = "rewrite ^ /index.php;"; 1101 }; 1102 "~ ^/store-apps" = { 1103 priority = 201; 1104 extraConfig = "root ${cfg.home};"; 1105 }; 1106 "~ ^/nix-apps" = { 1107 priority = 201; 1108 extraConfig = "root ${cfg.home};"; 1109 }; 1110 "^~ /.well-known" = { 1111 priority = 210; 1112 extraConfig = '' 1113 absolute_redirect off; 1114 location = /.well-known/carddav { 1115 return 301 /remote.php/dav; 1116 } 1117 location = /.well-known/caldav { 1118 return 301 /remote.php/dav; 1119 } 1120 location ~ ^/\.well-known/(?!acme-challenge|pki-validation) { 1121 return 301 /index.php$request_uri; 1122 } 1123 try_files $uri $uri/ =404; 1124 ''; 1125 }; 1126 "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)".extraConfig = '' 1127 return 404; 1128 ''; 1129 "~ ^/(?:\\.(?!well-known)|autotest|occ|issue|indie|db_|console)".extraConfig = '' 1130 return 404; 1131 ''; 1132 "~ ^\\/(?:index|remote|public|cron|core\\/ajax\\/update|status|ocs\\/v[12]|updater\\/.+|oc[ms]-provider\\/.+|.+\\/richdocumentscode\\/proxy)\\.php(?:$|\\/)" = { 1133 priority = 500; 1134 extraConfig = '' 1135 include ${config.services.nginx.package}/conf/fastcgi.conf; 1136 fastcgi_split_path_info ^(.+?\.php)(\\/.*)$; 1137 set $path_info $fastcgi_path_info; 1138 try_files $fastcgi_script_name =404; 1139 fastcgi_param PATH_INFO $path_info; 1140 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 1141 fastcgi_param HTTPS ${if cfg.https then "on" else "off"}; 1142 fastcgi_param modHeadersAvailable true; 1143 fastcgi_param front_controller_active true; 1144 fastcgi_pass unix:${fpm.socket}; 1145 fastcgi_intercept_errors on; 1146 fastcgi_request_buffering off; 1147 fastcgi_read_timeout ${builtins.toString cfg.fastcgiTimeout}s; 1148 ''; 1149 }; 1150 "~ \\.(?:css|js|woff2?|svg|gif|map)$".extraConfig = '' 1151 try_files $uri /index.php$request_uri; 1152 expires 6M; 1153 access_log off; 1154 ''; 1155 "~ ^\\/(?:updater|ocs-provider|ocm-provider)(?:$|\\/)".extraConfig = '' 1156 try_files $uri/ =404; 1157 index index.php; 1158 ''; 1159 "~ \\.(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$".extraConfig = '' 1160 try_files $uri /index.php$request_uri; 1161 access_log off; 1162 ''; 1163 }; 1164 extraConfig = '' 1165 index index.php index.html /index.php$request_uri; 1166 ${optionalString (cfg.nginx.recommendedHttpHeaders) '' 1167 add_header X-Content-Type-Options nosniff; 1168 add_header X-XSS-Protection "1; mode=block"; 1169 add_header X-Robots-Tag "noindex, nofollow"; 1170 add_header X-Download-Options noopen; 1171 add_header X-Permitted-Cross-Domain-Policies none; 1172 add_header X-Frame-Options sameorigin; 1173 add_header Referrer-Policy no-referrer; 1174 ''} 1175 ${optionalString (cfg.https) '' 1176 add_header Strict-Transport-Security "max-age=${toString cfg.nginx.hstsMaxAge}; includeSubDomains" always; 1177 ''} 1178 client_max_body_size ${cfg.maxUploadSize}; 1179 fastcgi_buffers 64 4K; 1180 fastcgi_hide_header X-Powered-By; 1181 gzip on; 1182 gzip_vary on; 1183 gzip_comp_level 4; 1184 gzip_min_length 256; 1185 gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; 1186 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; 1187 1188 ${optionalString cfg.webfinger '' 1189 rewrite ^/.well-known/host-meta /public.php?service=host-meta last; 1190 rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last; 1191 ''} 1192 ''; 1193 }; 1194 } 1195 ]); 1196 1197 meta.doc = ./nextcloud.md; 1198}