diff options
author | Maximilian Bosch <maximilian@mbosch.me> | 2024-01-12 20:27:58 +0100 |
---|---|---|
committer | Maximilian Bosch <maximilian@mbosch.me> | 2024-01-12 22:11:36 +0100 |
commit | 90787dbe89db86f661ff0b2b71a752518fa014de (patch) | |
tree | 5f89a7170606bbdc046ad21a6a7f1fd2055bdb87 /nixos/modules/services | |
parent | cdcd061e7f312304de5391bf2a01d8c705206eaf (diff) | |
download | nixlib-90787dbe89db86f661ff0b2b71a752518fa014de.tar nixlib-90787dbe89db86f661ff0b2b71a752518fa014de.tar.gz nixlib-90787dbe89db86f661ff0b2b71a752518fa014de.tar.bz2 nixlib-90787dbe89db86f661ff0b2b71a752518fa014de.tar.lz nixlib-90787dbe89db86f661ff0b2b71a752518fa014de.tar.xz nixlib-90787dbe89db86f661ff0b2b71a752518fa014de.tar.zst nixlib-90787dbe89db86f661ff0b2b71a752518fa014de.zip |
nixos/nextcloud: set up base directories & override.config.php with tmpfiles
Closes #169733 The issue is that Nextcloud fails to start up after a GC because the symlink from `override.config.php` is stale. I'm relatively certain that this is not a bug in the Nix GC - that would've popped up somewhere else already in the past years - and one of the reporters seems to confirm that: when they restarted `nextcloud-setup.service` after the issue appeared, an `override.config.php` pointing to a different hash was there. This hints that on a deploy `nextcloud-setup` wasn't restarted properly and thus replacing the symlink update was missed. This is relatively hard to trigger due to the nature of the bug unfortunately (you usually keep system generations for a few weeks and you'll need to change the configuration - or stdenv - to get a different `override.config.php`), so getting pointers from folks who are affected is rather complicated. So I decided to work around this by using systemd-tmpfiles which a lot of other modules already utilize for this use-case. Now, `override.config.php` and the directory structure aren't created by `nextcloud-setup`, but by `systemd-tmpfiles`. With that, the structure is guaranteed to exist * on boot, since tmpfiles are always created/applied then * on config activation, since this is done before services are (re)started which covers the case for new installations and existing ones. Also, the recursive `chgrp` was used as transition tool when we switched from `nginx` as owning group to a dedicated `nextcloud` group[1][2], but this was several releases ago, so I don't consider this relevant anymore. [1] fd9eb16b249aad1d5e231b8329035abfab5fc0eb [2] ca916e8cb3220ba43a43d10f72ccb4b88077a461
Diffstat (limited to 'nixos/modules/services')
-rw-r--r-- | nixos/modules/services/web-apps/nextcloud.nix | 211 |
1 files changed, 103 insertions, 108 deletions
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix index 39f4e8f11620..dedd1a4cffd5 100644 --- a/nixos/modules/services/web-apps/nextcloud.nix +++ b/nixos/modules/services/web-apps/nextcloud.nix @@ -99,11 +99,101 @@ let 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 = versionAtLeast cfg.package.version "27.1.2" - || (versionOlder cfg.package.version "27.0.0" - && versionAtLeast cfg.package.version "26.0.8"); + 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" '' + <?php + ${optionalString requiresReadSecretFunction '' + function nix_read_secret($file) { + if (!file_exists($file)) { + throw new \RuntimeException(sprintf( + "Cannot start Nextcloud, secret file %s set by NixOS doesn't seem to " + . "exist! Please make sure that the file exists and has appropriate " + . "permissions for user & group 'nextcloud'!", + $file + )); + } + return trim(file_get_contents($file)); + }''} + function nix_decode_json_file($file, $error) { + if (!file_exists($file)) { + throw new \RuntimeException(sprintf($error, $file)); + } + $decoded = json_decode(file_get_contents($file), true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \RuntimeException(sprintf("Cannot decode %s, because: %s", $file, json_last_error_msg())); + } + return $decoded; + } + $CONFIG = [ + 'apps_paths' => [ + ${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-extraOptions.json" cfg.extraOptions}", + "impossible: this should never happen (decoding generated extraOptions 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 = [ @@ -808,107 +898,23 @@ in { timerConfig.Unit = "nextcloud-cron.service"; }; - systemd.tmpfiles.rules = ["d ${cfg.home} 0750 nextcloud nextcloud"]; + 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 ]; + phpfpm-nextcloud.restartTriggers = [ webroot overrideConfig ]; nextcloud-setup = 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; - - nextcloudGreaterOrEqualThan = req: versionAtLeast cfg.package.version req; - - mkAppStoreConfig = name: { enabled, writable, ... }: optionalString enabled '' - [ 'path' => '${webroot}/${name}', 'url' => '/${name}', 'writable' => ${boolToString writable} ], - ''; - - overrideConfig = pkgs.writeText "nextcloud-config.php" '' - <?php - ${optionalString requiresReadSecretFunction '' - function nix_read_secret($file) { - if (!file_exists($file)) { - throw new \RuntimeException(sprintf( - "Cannot start Nextcloud, secret file %s set by NixOS doesn't seem to " - . "exist! Please make sure that the file exists and has appropriate " - . "permissions for user & group 'nextcloud'!", - $file - )); - } - return trim(file_get_contents($file)); - }''} - function nix_decode_json_file($file, $error) { - if (!file_exists($file)) { - throw new \RuntimeException(sprintf($error, $file)); - } - $decoded = json_decode(file_get_contents($file), true); - - if (json_last_error() !== JSON_ERROR_NONE) { - throw new \RuntimeException(sprintf("Cannot decode %s, because: %s", $file, json_last_error_msg())); - } - - return $decoded; - } - $CONFIG = [ - 'apps_paths' => [ - ${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-extraOptions.json" cfg.extraOptions}", - "impossible: this should never happen (decoding generated extraOptions 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!" - )); - ''} - ''; occInstallCmd = let mkExport = { arg, value }: "export ${arg}=${value}"; dbpass = { @@ -953,6 +959,7 @@ in { 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 @@ -980,18 +987,6 @@ in { fi '') [ "nix-apps" "apps" ]} - # create nextcloud directories. - # if the directories exist already with wrong permissions, we fix that - for dir in ${datadir}/config ${datadir}/data ${cfg.home}/store-apps; do - if [ ! -e $dir ]; then - install -o nextcloud -g nextcloud -d $dir - elif [ $(stat -c "%G" $dir) != "nextcloud" ]; then - chgrp -R nextcloud $dir - fi - done - - ln -sf ${overrideConfig} ${datadir}/config/override.config.php - # Do not install if already installed if [[ ! -e ${datadir}/config/config.php ]]; then ${occInstallCmd} |