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]fd9eb16b24
[2]ca916e8cb3
This commit is contained in:
parent
cdcd061e7f
commit
90787dbe89
@ -160,6 +160,8 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
|
||||
- `services.zfs.zed.enableMail` now uses the global `sendmail` wrapper defined by an email module
|
||||
(such as msmtp or Postfix). It no longer requires using a special ZFS build with email support.
|
||||
|
||||
- `nextcloud-setup.service` no longer changes the group of each file & directory inside `/var/lib/nextcloud/{config,data,store-apps}` if one of these directories has the wrong owner group. This was part of transitioning the group used for `/var/lib/nextcloud`, but isn't necessary anymore.
|
||||
|
||||
- The `krb5` module has been rewritten and moved to `security.krb5`, moving all options but `security.krb5.enable` and `security.krb5.package` into `security.krb5.settings`.
|
||||
|
||||
- Gitea 1.21 upgrade has several breaking changes, including:
|
||||
|
@ -99,11 +99,101 @@ let
|
||||
mysqlLocal = cfg.database.createLocally && cfg.config.dbtype == "mysql";
|
||||
pgsqlLocal = cfg.database.createLocally && cfg.config.dbtype == "pgsql";
|
||||
|
||||
# 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");
|
||||
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" ''
|
||||
<?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}
|
||||
|
Loading…
Reference in New Issue
Block a user