From f97c7e1cd7a995e476cc81f36083dfb21b02e588 Mon Sep 17 00:00:00 2001 From: Jonas Heinrich Date: Thu, 18 Jul 2024 12:50:10 +0200 Subject: [PATCH] add extraOCCCommands option --- flake.lock | 6 +- nextcloud-extras.nix | 17 + nextcloud.nix | 1257 ------------------------------------------ vm-nextcloud.nix | 13 +- 4 files changed, 25 insertions(+), 1268 deletions(-) delete mode 100644 nextcloud.nix diff --git a/flake.lock b/flake.lock index c4b4a0c..f439a91 100644 --- a/flake.lock +++ b/flake.lock @@ -35,11 +35,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1719426051, - "narHash": "sha256-yJL9VYQhaRM7xs0M867ZFxwaONB9T2Q4LnGo1WovuR4=", + "lastModified": 1720954236, + "narHash": "sha256-1mEKHp4m9brvfQ0rjCca8P1WHpymK3TOr3v34ydv9bs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "89c49874fb15f4124bf71ca5f42a04f2ee5825fd", + "rev": "53e81e790209e41f0c1efa9ff26ff2fd7ab35e27", "type": "github" }, "original": { diff --git a/nextcloud-extras.nix b/nextcloud-extras.nix index 52ad3ca..5f7d708 100644 --- a/nextcloud-extras.nix +++ b/nextcloud-extras.nix @@ -73,6 +73,14 @@ in { ''; }; + extraOCCCommands = mkOption { + default = ""; + type = types.lines; + example = "nextcloud-occ app:enable cleanup"; + description = lib.mdDoc '' + Extra OCC commands which get executed after setup. + ''; + }; }; }; @@ -97,6 +105,15 @@ in { after = ["nextcloud-setup.service"]; }; + systemd.services.nextcloud-extra-occ-commands = { + enable = true; + script = '' + ${cfg.extraOCCCommands} + ''; + wantedBy = [ "multi-user.target" ]; + after = ["nextcloud-setup.service"]; + }; + services.phpfpm.pools.nextcloud.settings = { "listen.owner" = webserver.user; "listen.group" = webserver.group; diff --git a/nextcloud.nix b/nextcloud.nix deleted file mode 100644 index 28d5226..0000000 --- a/nextcloud.nix +++ /dev/null @@ -1,1257 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let - cfg = config.services.nextcloud; - fpm = config.services.phpfpm.pools.nextcloud; - - jsonFormat = pkgs.formats.json {}; - - defaultPHPSettings = { - output_buffering = "0"; - short_open_tag = "Off"; - expose_php = "Off"; - error_reporting = "E_ALL & ~E_DEPRECATED & ~E_STRICT"; - display_errors = "stderr"; - "opcache.interned_strings_buffer" = "8"; - "opcache.max_accelerated_files" = "10000"; - "opcache.memory_consumption" = "128"; - "opcache.revalidate_freq" = "1"; - "opcache.fast_shutdown" = "1"; - "openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt"; - catch_workers_output = "yes"; - }; - - appStores = { - # default apps bundled with pkgs.nextcloudXX, e.g. files, contacts - apps = { - enabled = true; - writable = false; - }; - # apps installed via cfg.extraApps - nix-apps = { - enabled = cfg.extraApps != { }; - linkTarget = pkgs.linkFarm "nix-apps" - (mapAttrsToList (name: path: { inherit name path; }) cfg.extraApps); - writable = false; - }; - # apps installed via the app store. - store-apps = { - enabled = cfg.appstoreEnable == null || cfg.appstoreEnable; - linkTarget = "${cfg.home}/store-apps"; - writable = true; - }; - }; - - webroot = pkgs.runCommandLocal - "${cfg.package.name or "nextcloud"}-with-apps" - { } - '' - mkdir $out - ln -sfv "${cfg.package}"/* "$out" - ${concatStrings - (mapAttrsToList (name: store: optionalString (store.enabled && store?linkTarget) '' - if [ -e "$out"/${name} ]; then - echo "Didn't expect ${name} already in $out!" - exit 1 - fi - ln -sfTv ${store.linkTarget} "$out"/${name} - '') appStores)} - ''; - - inherit (cfg) datadir; - - phpPackage = cfg.phpPackage.buildEnv { - extensions = { enabled, all }: - (with all; enabled - ++ [ bz2 intl sodium ] # recommended - ++ optional cfg.enableImagemagick imagick - # Optionally enabled depending on caching settings - ++ optional cfg.caching.apcu apcu - ++ optional cfg.caching.redis redis - ++ optional cfg.caching.memcached memcached - ) - ++ cfg.phpExtraExtensions all; # Enabled by user - extraConfig = toKeyValue cfg.phpOptions; - }; - - toKeyValue = generators.toKeyValue { - mkKeyValue = generators.mkKeyValueDefault {} " = "; - }; - - phpCli = concatStringsSep " " ([ - "${getExe phpPackage}" - ] ++ optionals (cfg.cli.memoryLimit != null) [ - "-dmemory_limit=${cfg.cli.memoryLimit}" - ]); - - occ = pkgs.writeScriptBin "nextcloud-occ" '' - #! ${pkgs.runtimeShell} - cd ${webroot} - sudo=exec - if [[ "$USER" != nextcloud ]]; then - sudo='exec /run/wrappers/bin/sudo -u nextcloud --preserve-env=NEXTCLOUD_CONFIG_DIR --preserve-env=OC_PASS' - fi - export NEXTCLOUD_CONFIG_DIR="${datadir}/config" - $sudo \ - ${phpCli} \ - occ "$@" - ''; - - inherit (config.system) stateVersion; - - mysqlLocal = cfg.database.createLocally && cfg.config.dbtype == "mysql"; - pgsqlLocal = cfg.database.createLocally && cfg.config.dbtype == "pgsql"; - - nextcloudGreaterOrEqualThan = versionAtLeast cfg.package.version; - nextcloudOlderThan = versionOlder cfg.package.version; - - # https://github.com/nextcloud/documentation/pull/11179 - ocmProviderIsNotAStaticDirAnymore = nextcloudGreaterOrEqualThan "27.1.2" - || (nextcloudOlderThan "27.0.0" && nextcloudGreaterOrEqualThan "26.0.8"); - - overrideConfig = let - c = cfg.config; - requiresReadSecretFunction = c.dbpassFile != null || c.objectstore.s3.enable; - objectstoreConfig = let s3 = c.objectstore.s3; in optionalString s3.enable '' - 'objectstore' => [ - 'class' => '\\OC\\Files\\ObjectStore\\S3', - 'arguments' => [ - 'bucket' => '${s3.bucket}', - 'autocreate' => ${boolToString s3.autocreate}, - 'key' => '${s3.key}', - 'secret' => nix_read_secret('${s3.secretFile}'), - ${optionalString (s3.hostname != null) "'hostname' => '${s3.hostname}',"} - ${optionalString (s3.port != null) "'port' => ${toString s3.port},"} - 'use_ssl' => ${boolToString s3.useSsl}, - ${optionalString (s3.region != null) "'region' => '${s3.region}',"} - 'use_path_style' => ${boolToString s3.usePathStyle}, - ${optionalString (s3.sseCKeyFile != null) "'sse_c_key' => nix_read_secret('${s3.sseCKeyFile}'),"} - ], - ] - ''; - showAppStoreSetting = cfg.appstoreEnable != null || cfg.extraApps != {}; - renderedAppStoreSetting = - let - x = cfg.appstoreEnable; - in - if x == null then "false" - else boolToString x; - mkAppStoreConfig = name: { enabled, writable, ... }: optionalString enabled '' - [ 'path' => '${webroot}/${name}', 'url' => '/${name}', 'writable' => ${boolToString writable} ], - ''; - in pkgs.writeText "nextcloud-config.php" '' - [ - ${concatStrings (mapAttrsToList mkAppStoreConfig appStores)} - ], - ${optionalString (showAppStoreSetting) "'appstoreenabled' => ${renderedAppStoreSetting},"} - ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"} - ${optionalString (c.dbname != null) "'dbname' => '${c.dbname}',"} - ${optionalString (c.dbhost != null) "'dbhost' => '${c.dbhost}',"} - ${optionalString (c.dbuser != null) "'dbuser' => '${c.dbuser}',"} - ${optionalString (c.dbtableprefix != null) "'dbtableprefix' => '${toString c.dbtableprefix}',"} - ${optionalString (c.dbpassFile != null) '' - 'dbpassword' => nix_read_secret( - "${c.dbpassFile}" - ), - '' - } - 'dbtype' => '${c.dbtype}', - ${objectstoreConfig} - ]; - - $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file( - "${jsonFormat.generate "nextcloud-settings.json" cfg.settings}", - "impossible: this should never happen (decoding generated settings file %s failed)" - )); - - ${optionalString (cfg.secretFile != null) '' - $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file( - "${cfg.secretFile}", - "Cannot start Nextcloud, secrets file %s set by NixOS doesn't exist!" - )); - ''} - ''; -in { - - imports = [ - (mkRenamedOptionModule - [ "services" "nextcloud" "cron" "memoryLimit" ] - [ "services" "nextcloud" "cli" "memoryLimit" ]) - (mkRemovedOptionModule [ "services" "nextcloud" "enableBrokenCiphersForSSE" ] '' - This option has no effect since there's no supported Nextcloud version packaged here - using OpenSSL for RC4 SSE. - '') - (mkRemovedOptionModule [ "services" "nextcloud" "config" "dbport" ] '' - Add port to services.nextcloud.config.dbhost instead. - '') - (mkRenamedOptionModule - [ "services" "nextcloud" "logLevel" ] [ "services" "nextcloud" "settings" "loglevel" ]) - (mkRenamedOptionModule - [ "services" "nextcloud" "logType" ] [ "services" "nextcloud" "settings" "log_type" ]) - (mkRenamedOptionModule - [ "services" "nextcloud" "config" "defaultPhoneRegion" ] [ "services" "nextcloud" "settings" "default_phone_region" ]) - (mkRenamedOptionModule - [ "services" "nextcloud" "config" "overwriteProtocol" ] [ "services" "nextcloud" "settings" "overwriteprotocol" ]) - (mkRenamedOptionModule - [ "services" "nextcloud" "skeletonDirectory" ] [ "services" "nextcloud" "settings" "skeletondirectory" ]) - (mkRenamedOptionModule - [ "services" "nextcloud" "globalProfiles" ] [ "services" "nextcloud" "settings" "profile.enabled" ]) - (mkRenamedOptionModule - [ "services" "nextcloud" "config" "extraTrustedDomains" ] [ "services" "nextcloud" "settings" "trusted_domains" ]) - (mkRenamedOptionModule - [ "services" "nextcloud" "config" "trustedProxies" ] [ "services" "nextcloud" "settings" "trusted_proxies" ]) - (mkRenamedOptionModule ["services" "nextcloud" "extraOptions" ] [ "services" "nextcloud" "settings" ]) - ]; - - options.services.nextcloud = { - enable = mkEnableOption "nextcloud"; - - hostName = mkOption { - type = types.str; - description = "FQDN for the nextcloud instance."; - }; - home = mkOption { - type = types.str; - default = "/var/lib/nextcloud"; - description = "Storage path of nextcloud."; - }; - datadir = mkOption { - type = types.str; - default = config.services.nextcloud.home; - defaultText = literalExpression "config.services.nextcloud.home"; - description = '' - Nextcloud's data storage path. Will be [](#opt-services.nextcloud.home) by default. - This folder will be populated with a config.php file and a data folder which contains the state of the instance (excluding the database)."; - ''; - example = "/mnt/nextcloud-file"; - }; - extraApps = mkOption { - type = types.attrsOf types.package; - default = { }; - description = '' - Extra apps to install. Should be an attrSet of appid to packages generated by fetchNextcloudApp. - The appid must be identical to the "id" value in the apps appinfo/info.xml. - Using this will disable the appstore to prevent Nextcloud from updating these apps (see [](#opt-services.nextcloud.appstoreEnable)). - ''; - example = literalExpression '' - { - inherit (pkgs.nextcloud25Packages.apps) mail calendar contact; - phonetrack = pkgs.fetchNextcloudApp { - name = "phonetrack"; - sha256 = "0qf366vbahyl27p9mshfma1as4nvql6w75zy2zk5xwwbp343vsbc"; - url = "https://gitlab.com/eneiluj/phonetrack-oc/-/wikis/uploads/931aaaf8dca24bf31a7e169a83c17235/phonetrack-0.6.9.tar.gz"; - version = "0.6.9"; - }; - } - ''; - }; - extraAppsEnable = mkOption { - type = types.bool; - default = true; - description = '' - Automatically enable the apps in [](#opt-services.nextcloud.extraApps) every time Nextcloud starts. - If set to false, apps need to be enabled in the Nextcloud web user interface or with `nextcloud-occ app:enable`. - ''; - }; - appstoreEnable = mkOption { - type = types.nullOr types.bool; - default = null; - example = true; - description = '' - Allow the installation and updating of apps from the Nextcloud appstore. - Enabled by default unless there are packages in [](#opt-services.nextcloud.extraApps). - Set this to true to force enable the store even if [](#opt-services.nextcloud.extraApps) is used. - Set this to false to disable the installation of apps from the global appstore. App management is always enabled regardless of this setting. - ''; - }; - https = mkOption { - type = types.bool; - default = false; - description = "Use HTTPS for generated links."; - }; - package = mkOption { - type = types.package; - description = "Which package to use for the Nextcloud instance."; - relatedPackages = [ "nextcloud28" "nextcloud29" ]; - }; - phpPackage = mkPackageOption pkgs "php" { - example = "php82"; - }; - - maxUploadSize = mkOption { - default = "512M"; - type = types.str; - description = '' - The upload limit for files. This changes the relevant options - in php.ini and nginx if enabled. - ''; - }; - - webfinger = mkOption { - type = types.bool; - default = false; - description = '' - Enable this option if you plan on using the webfinger plugin. - The appropriate nginx rewrite rules will be added to your configuration. - ''; - }; - - phpExtraExtensions = mkOption { - type = with types; functionTo (listOf package); - default = all: []; - defaultText = literalExpression "all: []"; - description = '' - Additional PHP extensions to use for Nextcloud. - By default, only extensions necessary for a vanilla Nextcloud installation are enabled, - but you may choose from the list of available extensions and add further ones. - This is sometimes necessary to be able to install a certain Nextcloud app that has additional requirements. - ''; - example = literalExpression '' - all: [ all.pdlib all.bz2 ] - ''; - }; - - phpOptions = mkOption { - type = with types; attrsOf (oneOf [ str int ]); - defaultText = literalExpression (generators.toPretty { } defaultPHPSettings); - description = '' - Options for PHP's php.ini file for nextcloud. - - Please note that this option is _additive_ on purpose while the - attribute values inside the default are option defaults: that means that - - ```nix - { - services.nextcloud.phpOptions."opcache.interned_strings_buffer" = "23"; - } - ``` - - will override the `php.ini` option `opcache.interned_strings_buffer` without - discarding the rest of the defaults. - - Overriding all of `phpOptions` (including `upload_max_filesize`, `post_max_size` - and `memory_limit` which all point to [](#opt-services.nextcloud.maxUploadSize) - by default) can be done like this: - - ```nix - { - services.nextcloud.phpOptions = lib.mkForce { - /* ... */ - }; - } - ``` - ''; - }; - - poolSettings = mkOption { - type = with types; attrsOf (oneOf [ str int bool ]); - default = { - "pm" = "dynamic"; - "pm.max_children" = "32"; - "pm.start_servers" = "2"; - "pm.min_spare_servers" = "2"; - "pm.max_spare_servers" = "4"; - "pm.max_requests" = "500"; - }; - description = '' - Options for nextcloud's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives. - ''; - }; - - poolConfig = mkOption { - type = types.nullOr types.lines; - default = null; - description = '' - Options for Nextcloud's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives. - ''; - }; - - fastcgiTimeout = mkOption { - type = types.int; - default = 120; - description = '' - FastCGI timeout for database connection in seconds. - ''; - }; - - database = { - - createLocally = mkOption { - type = types.bool; - default = false; - description = '' - Whether to create the database and database user locally. - ''; - }; - - }; - - config = { - dbtype = mkOption { - type = types.enum [ "sqlite" "pgsql" "mysql" ]; - default = "sqlite"; - description = "Database type."; - }; - dbname = mkOption { - type = types.nullOr types.str; - default = "nextcloud"; - description = "Database name."; - }; - dbuser = mkOption { - type = types.nullOr types.str; - default = "nextcloud"; - description = "Database user."; - }; - dbpassFile = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - The full path to a file that contains the database password. - ''; - }; - dbhost = mkOption { - type = types.nullOr types.str; - default = - if pgsqlLocal then "/run/postgresql" - else if mysqlLocal then "localhost:/run/mysqld/mysqld.sock" - else "localhost"; - defaultText = "localhost"; - example = "localhost:5000"; - description = '' - Database host (+port) or socket path. - If [](#opt-services.nextcloud.database.createLocally) is true and - [](#opt-services.nextcloud.config.dbtype) is either `pgsql` or `mysql`, - defaults to the correct Unix socket instead. - ''; - }; - dbtableprefix = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - Table prefix in Nextcloud's database. - - __Note:__ since Nextcloud 20 it's not an option anymore to create a database - schema with a custom table prefix. This option only exists for backwards compatibility - with installations that were originally provisioned with Nextcloud <20. - ''; - }; - adminuser = mkOption { - type = types.str; - default = "root"; - description = '' - Username for the admin account. The username is only set during the - initial setup of Nextcloud! Since the username also acts as unique - ID internally, it cannot be changed later! - ''; - }; - adminpassFile = mkOption { - type = types.str; - description = '' - The full path to a file that contains the admin's password. Must be - readable by user `nextcloud`. The password is set only in the initial - setup of Nextcloud by the systemd service `nextcloud-setup.service`. - ''; - }; - objectstore = { - s3 = { - enable = mkEnableOption '' - S3 object storage as primary storage. - - This mounts a bucket on an Amazon S3 object storage or compatible - implementation into the virtual filesystem. - - Further details about this feature can be found in the - [upstream documentation](https://docs.nextcloud.com/server/22/admin_manual/configuration_files/primary_storage.html) - ''; - bucket = mkOption { - type = types.str; - example = "nextcloud"; - description = '' - The name of the S3 bucket. - ''; - }; - autocreate = mkOption { - type = types.bool; - description = '' - Create the objectstore if it does not exist. - ''; - }; - key = mkOption { - type = types.str; - example = "EJ39ITYZEUH5BGWDRUFY"; - description = '' - The access key for the S3 bucket. - ''; - }; - secretFile = mkOption { - type = types.str; - example = "/var/nextcloud-objectstore-s3-secret"; - description = '' - The full path to a file that contains the access secret. Must be - readable by user `nextcloud`. - ''; - }; - hostname = mkOption { - type = types.nullOr types.str; - default = null; - example = "example.com"; - description = '' - Required for some non-Amazon implementations. - ''; - }; - port = mkOption { - type = types.nullOr types.port; - default = null; - description = '' - Required for some non-Amazon implementations. - ''; - }; - useSsl = mkOption { - type = types.bool; - default = true; - description = '' - Use SSL for objectstore access. - ''; - }; - region = mkOption { - type = types.nullOr types.str; - default = null; - example = "REGION"; - description = '' - Required for some non-Amazon implementations. - ''; - }; - usePathStyle = mkOption { - type = types.bool; - default = false; - description = '' - Required for some non-Amazon S3 implementations. - - Ordinarily, requests will be made with - `http://bucket.hostname.domain/`, but with path style - enabled requests are made with - `http://hostname.domain/bucket` instead. - ''; - }; - sseCKeyFile = mkOption { - type = types.nullOr types.path; - default = null; - example = "/var/nextcloud-objectstore-s3-sse-c-key"; - description = '' - If provided this is the full path to a file that contains the key - to enable [server-side encryption with customer-provided keys][1] - (SSE-C). - - The file must contain a random 32-byte key encoded as a base64 - string, e.g. generated with the command - - ``` - openssl rand 32 | base64 - ``` - - Must be readable by user `nextcloud`. - - [1]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerSideEncryptionCustomerKeys.html - ''; - }; - }; - }; - }; - - enableImagemagick = mkEnableOption '' - the ImageMagick module for PHP. - This is used by the theming app and for generating previews of certain images (e.g. SVG and HEIF). - You may want to disable it for increased security. In that case, previews will still be available - for some images (e.g. JPEG and PNG). - See - '' // { - default = true; - }; - - configureRedis = lib.mkOption { - type = lib.types.bool; - default = config.services.nextcloud.notify_push.enable; - defaultText = literalExpression "config.services.nextcloud.notify_push.enable"; - description = '' - Whether to configure Nextcloud to use the recommended Redis settings for small instances. - - ::: {.note} - The `notify_push` app requires Redis to be configured. If this option is turned off, this must be configured manually. - ::: - ''; - }; - - caching = { - apcu = mkOption { - type = types.bool; - default = true; - description = '' - Whether to load the APCu module into PHP. - ''; - }; - redis = mkOption { - type = types.bool; - default = false; - description = '' - Whether to load the Redis module into PHP. - You still need to enable Redis in your config.php. - See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html - ''; - }; - memcached = mkOption { - type = types.bool; - default = false; - description = '' - Whether to load the Memcached module into PHP. - You still need to enable Memcached in your config.php. - See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html - ''; - }; - }; - autoUpdateApps = { - enable = mkOption { - type = types.bool; - default = false; - description = '' - Run a regular auto-update of all apps installed from the Nextcloud app store. - ''; - }; - startAt = mkOption { - type = with types; either str (listOf str); - default = "05:00:00"; - example = "Sun 14:00:00"; - description = '' - When to run the update. See `systemd.services..startAt`. - ''; - }; - }; - occ = mkOption { - type = types.package; - default = occ; - defaultText = literalMD "generated script"; - description = '' - The nextcloud-occ program preconfigured to target this Nextcloud instance. - ''; - }; - - settings = mkOption { - type = types.submodule { - freeformType = jsonFormat.type; - options = { - - loglevel = mkOption { - type = types.ints.between 0 4; - default = 2; - description = '' - Log level value between 0 (DEBUG) and 4 (FATAL). - - - 0 (debug): Log all activity. - - - 1 (info): Log activity such as user logins and file activities, plus warnings, errors, and fatal errors. - - - 2 (warn): Log successful operations, as well as warnings of potential problems, errors and fatal errors. - - - 3 (error): Log failed operations and fatal errors. - - - 4 (fatal): Log only fatal errors that cause the server to stop. - ''; - }; - log_type = mkOption { - type = types.enum [ "errorlog" "file" "syslog" "systemd" ]; - default = "syslog"; - description = '' - Logging backend to use. - systemd requires the php-systemd package to be added to services.nextcloud.phpExtraExtensions. - See the [nextcloud documentation](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/logging_configuration.html) for details. - ''; - }; - skeletondirectory = mkOption { - default = ""; - type = types.str; - description = '' - The directory where the skeleton files are located. These files will be - copied to the data directory of new users. Leave empty to not copy any - skeleton files. - ''; - }; - trusted_domains = mkOption { - type = types.listOf types.str; - default = []; - description = '' - Trusted domains, from which the nextcloud installation will be - accessible. You don't need to add - `services.nextcloud.hostname` here. - ''; - }; - trusted_proxies = mkOption { - type = types.listOf types.str; - default = []; - description = '' - Trusted proxies, to provide if the nextcloud installation is being - proxied to secure against e.g. spoofing. - ''; - }; - overwriteprotocol = mkOption { - type = types.enum [ "" "http" "https" ]; - default = ""; - example = "https"; - description = '' - Force Nextcloud to always use HTTP or HTTPS i.e. for link generation. - Nextcloud uses the currently used protocol by default, but when - behind a reverse-proxy, it may use `http` for everything although - Nextcloud may be served via HTTPS. - ''; - }; - default_phone_region = mkOption { - default = ""; - type = types.str; - example = "DE"; - description = '' - An [ISO 3166-1](https://www.iso.org/iso-3166-country-codes.html) - country code which replaces automatic phone-number detection - without a country code. - - As an example, with `DE` set as the default phone region, - the `+49` prefix can be omitted for phone numbers. - ''; - }; - "profile.enabled" = mkEnableOption "global profiles" // { - description = '' - Makes user-profiles globally available under `nextcloud.tld/u/user.name`. - Even though it's enabled by default in Nextcloud, it must be explicitly enabled - here because it has the side-effect that personal information is even accessible to - unauthenticated users by default. - By default, the following properties are set to “Show to everyone” - if this flag is enabled: - - About - - Full name - - Headline - - Organisation - - Profile picture - - Role - - Twitter - - Website - Only has an effect in Nextcloud 23 and later. - ''; - }; - }; - }; - default = {}; - description = '' - Extra options which should be appended to Nextcloud's config.php file. - ''; - example = literalExpression '' { - redis = { - host = "/run/redis/redis.sock"; - port = 0; - dbindex = 0; - password = "secret"; - timeout = 1.5; - }; - } ''; - }; - - secretFile = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - Secret options which will be appended to Nextcloud's config.php file (written as JSON, in the same - form as the [](#opt-services.nextcloud.settings) option), for example - `{"redis":{"password":"secret"}}`. - ''; - }; - - nginx = { - recommendedHttpHeaders = mkOption { - type = types.bool; - default = true; - description = "Enable additional recommended HTTP response headers"; - }; - hstsMaxAge = mkOption { - type = types.ints.positive; - default = 15552000; - description = '' - Value for the `max-age` directive of the HTTP - `Strict-Transport-Security` header. - - See section 6.1.1 of IETF RFC 6797 for detailed information on this - directive and header. - ''; - }; - }; - - cli.memoryLimit = mkOption { - type = types.nullOr types.str; - default = null; - example = "1G"; - description = '' - The `memory_limit` of PHP is equal to [](#opt-services.nextcloud.maxUploadSize). - The value can be customized for `nextcloud-cron.service` using this option. - ''; - }; - }; - - config = mkIf cfg.enable (mkMerge [ - { warnings = let - latest = 29; - upgradeWarning = major: nixos: - '' - A legacy Nextcloud install (from before NixOS ${nixos}) may be installed. - - After nextcloud${toString major} is installed successfully, you can safely upgrade - to ${toString (major + 1)}. The latest version available is Nextcloud${toString latest}. - - Please note that Nextcloud doesn't support upgrades across multiple major versions - (i.e. an upgrade from 16 is possible to 17, but not 16 to 18). - - The package can be upgraded by explicitly declaring the service-option - `services.nextcloud.package`. - ''; - - in (optional (cfg.poolConfig != null) '' - Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release. - Please migrate your configuration to config.services.nextcloud.poolSettings. - '') - ++ (optional (cfg.config.dbtableprefix != null) '' - Using `services.nextcloud.config.dbtableprefix` is deprecated. Fresh installations with this - option set are not allowed anymore since v20. - - If you have an existing installation with a custom table prefix, make sure it is - set correctly in `config.php` and remove the option from your NixOS config. - '') - ++ (optional (versionOlder cfg.package.version "25") (upgradeWarning 24 "22.11")) - ++ (optional (versionOlder cfg.package.version "26") (upgradeWarning 25 "23.05")) - ++ (optional (versionOlder cfg.package.version "27") (upgradeWarning 26 "23.11")) - ++ (optional (versionOlder cfg.package.version "28") (upgradeWarning 27 "24.05")) - ++ (optional (versionOlder cfg.package.version "29") (upgradeWarning 28 "24.11")); - - services.nextcloud.package = with pkgs; - mkDefault ( - if pkgs ? nextcloud - then throw '' - The `pkgs.nextcloud`-attribute has been removed. If it's supposed to be the default - nextcloud defined in an overlay, please set `services.nextcloud.package` to - `pkgs.nextcloud`. - '' - else if versionOlder stateVersion "24.05" then nextcloud27 - else nextcloud29 - ); - - services.nextcloud.phpPackage = - if versionOlder cfg.package.version "29" then pkgs.php82 - else pkgs.php83; - - services.nextcloud.phpOptions = mkMerge [ - (mapAttrs (const mkOptionDefault) defaultPHPSettings) - { - upload_max_filesize = cfg.maxUploadSize; - post_max_size = cfg.maxUploadSize; - memory_limit = cfg.maxUploadSize; - } - (mkIf cfg.caching.apcu { - "apc.enable_cli" = "1"; - }) - ]; - } - - { assertions = [ - { assertion = cfg.database.createLocally -> cfg.config.dbpassFile == null; - message = '' - Using `services.nextcloud.database.createLocally` with database - password authentication is no longer supported. - - If you use an external database (or want to use password auth for any - other reason), set `services.nextcloud.database.createLocally` to - `false`. The database won't be managed for you (use `services.mysql` - if you want to set it up). - - If you want this module to manage your nextcloud database for you, - unset `services.nextcloud.config.dbpassFile` and - `services.nextcloud.config.dbhost` to use socket authentication - instead of password. - ''; - } - ]; } - - { systemd.timers.nextcloud-cron = { - wantedBy = [ "timers.target" ]; - after = [ "nextcloud-setup.service" ]; - timerConfig = { - OnBootSec = "5m"; - OnUnitActiveSec = "5m"; - Unit = "nextcloud-cron.service"; - }; - }; - - systemd.tmpfiles.rules = map (dir: "d ${dir} 0750 nextcloud nextcloud - -") [ - "${cfg.home}" - "${datadir}/config" - "${datadir}/data" - "${cfg.home}/store-apps" - ] ++ [ - "L+ ${datadir}/config/override.config.php - - - - ${overrideConfig}" - ]; - - systemd.services = { - # When upgrading the Nextcloud package, Nextcloud can report errors such as - # "The files of the app [all apps in /var/lib/nextcloud/apps] were not replaced correctly" - # Restarting phpfpm on Nextcloud package update fixes these issues (but this is a workaround). - phpfpm-nextcloud.restartTriggers = [ webroot overrideConfig ]; - - nextcloud-setup = let - c = cfg.config; - occInstallCmd = let - mkExport = { arg, value }: "export ${arg}=${value}"; - dbpass = { - arg = "DBPASS"; - value = if c.dbpassFile != null - then ''"$(<"${toString c.dbpassFile}")"'' - else ''""''; - }; - adminpass = { - arg = "ADMINPASS"; - value = ''"$(<"${toString c.adminpassFile}")"''; - }; - installFlags = concatStringsSep " \\\n " - (mapAttrsToList (k: v: "${k} ${toString v}") { - "--database" = ''"${c.dbtype}"''; - # The following attributes are optional depending on the type of - # database. Those that evaluate to null on the left hand side - # will be omitted. - ${if c.dbname != null then "--database-name" else null} = ''"${c.dbname}"''; - ${if c.dbhost != null then "--database-host" else null} = ''"${c.dbhost}"''; - ${if c.dbuser != null then "--database-user" else null} = ''"${c.dbuser}"''; - "--database-pass" = "\"\$${dbpass.arg}\""; - "--admin-user" = ''"${c.adminuser}"''; - "--admin-pass" = "\"\$${adminpass.arg}\""; - "--data-dir" = ''"${datadir}/data"''; - }); - in '' - ${mkExport dbpass} - ${mkExport adminpass} - ${occ}/bin/nextcloud-occ maintenance:install \ - ${installFlags} - ''; - occSetTrustedDomainsCmd = concatStringsSep "\n" (imap0 - (i: v: '' - ${occ}/bin/nextcloud-occ config:system:set trusted_domains \ - ${toString i} --value="${toString v}" - '') ([ cfg.hostName ] ++ cfg.settings.trusted_domains)); - - in { - wantedBy = [ "multi-user.target" ]; - wants = [ "nextcloud-update-db.service" ]; - before = [ "phpfpm-nextcloud.service" ]; - after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; - requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; - path = [ occ ]; - restartTriggers = [ overrideConfig ]; - script = '' - ${optionalString (c.dbpassFile != null) '' - if [ ! -r "${c.dbpassFile}" ]; then - echo "dbpassFile ${c.dbpassFile} is not readable by nextcloud:nextcloud! Aborting..." - exit 1 - fi - if [ -z "$(<${c.dbpassFile})" ]; then - echo "dbpassFile ${c.dbpassFile} is empty!" - exit 1 - fi - ''} - if [ ! -r "${c.adminpassFile}" ]; then - echo "adminpassFile ${c.adminpassFile} is not readable by nextcloud:nextcloud! Aborting..." - exit 1 - fi - if [ -z "$(<${c.adminpassFile})" ]; then - echo "adminpassFile ${c.adminpassFile} is empty!" - exit 1 - fi - - ${concatMapStrings (name: '' - if [ -d "${cfg.home}"/${name} ]; then - echo "Cleaning up ${name}; these are now bundled in the webroot store-path!" - rm -r "${cfg.home}"/${name} - fi - '') [ "nix-apps" "apps" ]} - - # Do not install if already installed - if [[ ! -e ${datadir}/config/config.php ]]; then - ${occInstallCmd} - fi - - ${occ}/bin/nextcloud-occ upgrade - - ${occ}/bin/nextcloud-occ config:system:delete trusted_domains - - ${optionalString (cfg.extraAppsEnable && cfg.extraApps != { }) '' - # Try to enable apps - ${occ}/bin/nextcloud-occ app:enable ${concatStringsSep " " (attrNames cfg.extraApps)} - ''} - - ${occSetTrustedDomainsCmd} - ''; - serviceConfig.Type = "oneshot"; - serviceConfig.User = "nextcloud"; - # On Nextcloud ≥ 26, it is not necessary to patch the database files to prevent - # an automatic creation of the database user. - environment.NC_setup_create_db_user = lib.mkIf (nextcloudGreaterOrEqualThan "26") "false"; - }; - nextcloud-cron = { - after = [ "nextcloud-setup.service" ]; - environment.NEXTCLOUD_CONFIG_DIR = "${datadir}/config"; - serviceConfig = { - Type = "exec"; - User = "nextcloud"; - ExecCondition = "${phpCli} -f ${webroot}/occ status -e"; - ExecStart = "${phpCli} -f ${webroot}/cron.php"; - KillMode = "process"; - }; - }; - nextcloud-update-plugins = mkIf cfg.autoUpdateApps.enable { - after = [ "nextcloud-setup.service" ]; - serviceConfig = { - Type = "oneshot"; - ExecStart = "${occ}/bin/nextcloud-occ app:update --all"; - User = "nextcloud"; - }; - startAt = cfg.autoUpdateApps.startAt; - }; - nextcloud-update-db = { - after = [ "nextcloud-setup.service" ]; - environment.NEXTCLOUD_CONFIG_DIR = "${datadir}/config"; - script = '' - ${occ}/bin/nextcloud-occ db:add-missing-columns - ${occ}/bin/nextcloud-occ db:add-missing-indices - ${occ}/bin/nextcloud-occ db:add-missing-primary-keys - ''; - serviceConfig = { - Type = "exec"; - User = "nextcloud"; - ExecCondition = "${phpCli} -f ${webroot}/occ status -e"; - }; - }; - }; - - services.phpfpm = { - pools.nextcloud = { - user = "nextcloud"; - group = "nextcloud"; - phpPackage = phpPackage; - phpEnv = { - NEXTCLOUD_CONFIG_DIR = "${datadir}/config"; - PATH = "/run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin"; - }; - settings = mapAttrs (name: mkDefault) { - "listen.owner" = config.services.nginx.user; - "listen.group" = config.services.nginx.group; - } // cfg.poolSettings; - extraConfig = cfg.poolConfig; - }; - }; - - users.users.nextcloud = { - home = "${cfg.home}"; - group = "nextcloud"; - isSystemUser = true; - }; - users.groups.nextcloud.members = [ "nextcloud" config.services.nginx.user ]; - - environment.systemPackages = [ occ ]; - - services.mysql = lib.mkIf mysqlLocal { - enable = true; - package = lib.mkDefault pkgs.mariadb; - ensureDatabases = [ cfg.config.dbname ]; - ensureUsers = [{ - name = cfg.config.dbuser; - ensurePermissions = { "${cfg.config.dbname}.*" = "ALL PRIVILEGES"; }; - }]; - }; - - services.postgresql = mkIf pgsqlLocal { - enable = true; - ensureDatabases = [ cfg.config.dbname ]; - ensureUsers = [{ - name = cfg.config.dbuser; - ensureDBOwnership = true; - }]; - }; - - services.redis.servers.nextcloud = lib.mkIf cfg.configureRedis { - enable = true; - user = "nextcloud"; - }; - - services.nextcloud = { - caching.redis = lib.mkIf cfg.configureRedis true; - settings = mkMerge [({ - datadirectory = lib.mkDefault "${datadir}/data"; - trusted_domains = [ cfg.hostName ]; - }) (lib.mkIf cfg.configureRedis { - "memcache.distributed" = ''\OC\Memcache\Redis''; - "memcache.locking" = ''\OC\Memcache\Redis''; - redis = { - host = config.services.redis.servers.nextcloud.unixSocket; - port = 0; - }; - })]; - }; - - services.nginx.enable = mkDefault true; - - services.nginx.virtualHosts.${cfg.hostName} = { - root = webroot; - locations = { - "= /robots.txt" = { - priority = 100; - extraConfig = '' - allow all; - access_log off; - ''; - }; - "= /" = { - priority = 100; - extraConfig = '' - if ( $http_user_agent ~ ^DavClnt ) { - return 302 /remote.php/webdav/$is_args$args; - } - ''; - }; - "^~ /.well-known" = { - priority = 210; - extraConfig = '' - absolute_redirect off; - location = /.well-known/carddav { - return 301 /remote.php/dav/; - } - location = /.well-known/caldav { - return 301 /remote.php/dav/; - } - location ~ ^/\.well-known/(?!acme-challenge|pki-validation) { - return 301 /index.php$request_uri; - } - try_files $uri $uri/ =404; - ''; - }; - "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)" = { - priority = 450; - extraConfig = '' - return 404; - ''; - }; - "~ ^/(?:\\.|autotest|occ|issue|indie|db_|console)" = { - priority = 450; - extraConfig = '' - return 404; - ''; - }; - "~ \\.php(?:$|/)" = { - priority = 500; - extraConfig = '' - # legacy support (i.e. static files and directories in cfg.package) - rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[s${optionalString (!ocmProviderIsNotAStaticDirAnymore) "m"}]-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri; - include ${config.services.nginx.package}/conf/fastcgi.conf; - fastcgi_split_path_info ^(.+?\.php)(\\/.*)$; - set $path_info $fastcgi_path_info; - try_files $fastcgi_script_name =404; - fastcgi_param PATH_INFO $path_info; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param HTTPS ${if cfg.https then "on" else "off"}; - fastcgi_param modHeadersAvailable true; - fastcgi_param front_controller_active false; - fastcgi_pass unix:${fpm.socket}; - fastcgi_intercept_errors on; - fastcgi_request_buffering off; - fastcgi_read_timeout ${builtins.toString cfg.fastcgiTimeout}s; - ''; - }; - "~ \\.(?:css|js|mjs|svg|gif|png|jpg|jpeg|ico|wasm|tflite|map|html|ttf|bcmap|mp4|webm|ogg|flac)$".extraConfig = '' - try_files $uri /index.php$request_uri; - expires 6M; - access_log off; - location ~ \.mjs$ { - default_type text/javascript; - } - location ~ \.wasm$ { - default_type application/wasm; - } - ''; - "~ ^\\/(?:updater|ocs-provider${optionalString (!ocmProviderIsNotAStaticDirAnymore) "|ocm-provider"})(?:$|\\/)".extraConfig = '' - try_files $uri/ =404; - index index.php; - ''; - "/remote" = { - priority = 1500; - extraConfig = '' - return 301 /remote.php$request_uri; - ''; - }; - "/" = { - priority = 1600; - extraConfig = '' - try_files $uri $uri/ /index.php$request_uri; - ''; - }; - }; - extraConfig = '' - index index.php index.html /index.php$request_uri; - ${optionalString (cfg.nginx.recommendedHttpHeaders) '' - add_header X-Content-Type-Options nosniff; - add_header X-XSS-Protection "1; mode=block"; - add_header X-Robots-Tag "noindex, nofollow"; - add_header X-Download-Options noopen; - add_header X-Permitted-Cross-Domain-Policies none; - add_header X-Frame-Options sameorigin; - add_header Referrer-Policy no-referrer; - ''} - ${optionalString (cfg.https) '' - add_header Strict-Transport-Security "max-age=${toString cfg.nginx.hstsMaxAge}; includeSubDomains" always; - ''} - client_max_body_size ${cfg.maxUploadSize}; - fastcgi_buffers 64 4K; - fastcgi_hide_header X-Powered-By; - gzip on; - gzip_vary on; - gzip_comp_level 4; - gzip_min_length 256; - gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; - 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; - - ${optionalString cfg.webfinger '' - rewrite ^/.well-known/host-meta /public.php?service=host-meta last; - rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last; - ''} - ''; - }; - } - ]); - - meta.doc = ./nextcloud.md; -} diff --git a/vm-nextcloud.nix b/vm-nextcloud.nix index 4dd2666..fdc0007 100644 --- a/vm-nextcloud.nix +++ b/vm-nextcloud.nix @@ -5,14 +5,7 @@ cores = 4; }; - # Workaround for bug, disable pretty urls - # https://github.com/nextcloud/server/issues/44685 - disabledModules = [ - "services/web-apps/nextcloud.nix" - ]; - imports = [ - ./nextcloud.nix ./nextcloud-extras.nix ]; @@ -93,8 +86,12 @@ }; appstoreEnable = true; configureRedis = true; + extraOCCCommands = '' + ${config.services.nextcloud.occ}/bin/nextcloud-occ app:enable cleanup + ''; settings = { - log_type = "syslog"; + log_type = "file"; + loglevel = 1; mail_smtpmode = "sendmail"; mail_sendmailmode = "pipe"; trusted_domains = [ "10.100.100.1" ];