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