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
|
- `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.
|
(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`.
|
- 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:
|
- Gitea 1.21 upgrade has several breaking changes, including:
|
||||||
|
|
|
@ -99,11 +99,101 @@ let
|
||||||
mysqlLocal = cfg.database.createLocally && cfg.config.dbtype == "mysql";
|
mysqlLocal = cfg.database.createLocally && cfg.config.dbtype == "mysql";
|
||||||
pgsqlLocal = cfg.database.createLocally && cfg.config.dbtype == "pgsql";
|
pgsqlLocal = cfg.database.createLocally && cfg.config.dbtype == "pgsql";
|
||||||
|
|
||||||
# https://github.com/nextcloud/documentation/pull/11179
|
nextcloudGreaterOrEqualThan = versionAtLeast cfg.package.version;
|
||||||
ocmProviderIsNotAStaticDirAnymore = versionAtLeast cfg.package.version "27.1.2"
|
nextcloudOlderThan = versionOlder cfg.package.version;
|
||||||
|| (versionOlder cfg.package.version "27.0.0"
|
|
||||||
&& versionAtLeast cfg.package.version "26.0.8");
|
|
||||||
|
|
||||||
|
# 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 {
|
in {
|
||||||
|
|
||||||
imports = [
|
imports = [
|
||||||
|
@ -808,107 +898,23 @@ in {
|
||||||
timerConfig.Unit = "nextcloud-cron.service";
|
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 = {
|
systemd.services = {
|
||||||
# When upgrading the Nextcloud package, Nextcloud can report errors such as
|
# 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"
|
# "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).
|
# 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
|
nextcloud-setup = let
|
||||||
c = cfg.config;
|
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
|
occInstallCmd = let
|
||||||
mkExport = { arg, value }: "export ${arg}=${value}";
|
mkExport = { arg, value }: "export ${arg}=${value}";
|
||||||
dbpass = {
|
dbpass = {
|
||||||
|
@ -953,6 +959,7 @@ in {
|
||||||
after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
|
after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
|
||||||
requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
|
requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
|
||||||
path = [ occ ];
|
path = [ occ ];
|
||||||
|
restartTriggers = [ overrideConfig ];
|
||||||
script = ''
|
script = ''
|
||||||
${optionalString (c.dbpassFile != null) ''
|
${optionalString (c.dbpassFile != null) ''
|
||||||
if [ ! -r "${c.dbpassFile}" ]; then
|
if [ ! -r "${c.dbpassFile}" ]; then
|
||||||
|
@ -980,18 +987,6 @@ in {
|
||||||
fi
|
fi
|
||||||
'') [ "nix-apps" "apps" ]}
|
'') [ "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
|
# Do not install if already installed
|
||||||
if [[ ! -e ${datadir}/config/config.php ]]; then
|
if [[ ! -e ${datadir}/config/config.php ]]; then
|
||||||
${occInstallCmd}
|
${occInstallCmd}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user