Add storage keepalive (upholds) across services, Nextcloud cron, disable Nextcloud PHP JIT
Requires= doesn't restart a service once its required mount comes back after a USB disconnect/remount, so a dedicated storage-keepalive unit now Upholds= every storage-dependent service to bring it back automatically. Also adds the missing Nextcloud cron.php timer (background jobs were never triggered), and disables opcache.jit in the Nextcloud container — PHP 8.4's JIT was miscompiling OC\Config\UserConfig::getValueBool(), crashing every session-bootstrapping request with a bogus strtolower() TypeError. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -222,6 +222,30 @@
|
||||
};
|
||||
};
|
||||
|
||||
# Nextcloud's "cron" backgroundjobs_mode (config:app:get core backgroundjobs_mode)
|
||||
# doesn't schedule itself — something external must call cron.php periodically.
|
||||
# Nothing did, so background jobs (including the News app's feed fetcher)
|
||||
# went stale. Nextcloud recommends every 5 minutes.
|
||||
systemd.services.nextcloud-cron = {
|
||||
description = "Nextcloud background job runner (cron.php)";
|
||||
after = [ "podman-nextcloud.service" ];
|
||||
requires = [ "podman-nextcloud.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${pkgs.podman}/bin/podman exec -u www-data nextcloud php -f cron.php";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.nextcloud-cron = {
|
||||
description = "Run Nextcloud cron.php every 5 minutes";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnBootSec = "5min";
|
||||
OnUnitActiveSec = "5min";
|
||||
Persistent = true;
|
||||
};
|
||||
};
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Local DNS overrides (optional — makes LAN clients hit the Pi directly
|
||||
# instead of going through Cloudflare for *.zakobar.com)
|
||||
|
||||
@@ -103,6 +103,8 @@ in
|
||||
# -------------------------------------------------------------------------
|
||||
# Storage directory
|
||||
# -------------------------------------------------------------------------
|
||||
homey.storage.upholds = [ "podman-aim-server.service" "podman-aim-ui.service" ];
|
||||
|
||||
homey.storage.extraDirs = [
|
||||
{ path = "aim"; mode = "0755"; }
|
||||
];
|
||||
|
||||
@@ -123,6 +123,8 @@ in
|
||||
# -----------------------------------------------------------------------
|
||||
# Storage directories (not backed up — no backup.extraPaths entry)
|
||||
# -----------------------------------------------------------------------
|
||||
homey.storage.upholds = [ "podman-attic.service" ];
|
||||
|
||||
homey.storage.extraDirs = [
|
||||
{ path = "attic"; }
|
||||
{ path = "attic/cache"; mode = "0755"; }
|
||||
|
||||
@@ -252,6 +252,8 @@ in
|
||||
# -----------------------------------------------------------------------
|
||||
# Storage directories
|
||||
# -----------------------------------------------------------------------
|
||||
homey.storage.upholds = [ "podman-authelia.service" ];
|
||||
|
||||
homey.storage.extraDirs = [
|
||||
{ path = "authelia"; }
|
||||
{ path = "authelia/config"; }
|
||||
|
||||
@@ -108,6 +108,8 @@ in
|
||||
# -------------------------------------------------------------------------
|
||||
# Storage directories (owned by the service user)
|
||||
# -------------------------------------------------------------------------
|
||||
homey.storage.upholds = [ "emulatorjs.service" ];
|
||||
|
||||
homey.storage.extraDirs = [
|
||||
{ path = "emulatorjs"; user = "emulatorjs"; group = "emulatorjs"; }
|
||||
{ path = "emulatorjs/saves"; user = "emulatorjs"; group = "emulatorjs"; }
|
||||
|
||||
@@ -211,6 +211,8 @@ in
|
||||
# -----------------------------------------------------------------------
|
||||
# Storage directories (UID 1000 = Gitea's internal user)
|
||||
# -----------------------------------------------------------------------
|
||||
homey.storage.upholds = [ "podman-gitea.service" ];
|
||||
|
||||
homey.storage.extraDirs = [
|
||||
{ path = "gitea"; user = "1000"; group = "1000"; }
|
||||
{ path = "gitea/data"; user = "1000"; group = "1000"; }
|
||||
|
||||
@@ -157,6 +157,8 @@ in
|
||||
# -------------------------------------------------------------------------
|
||||
# Storage directories
|
||||
# -------------------------------------------------------------------------
|
||||
homey.storage.upholds = [ "podman-home-assistant.service" ];
|
||||
|
||||
homey.storage.extraDirs = [
|
||||
{ path = "home-assistant"; }
|
||||
{ path = "home-assistant/config"; mode = "0755"; }
|
||||
|
||||
@@ -145,6 +145,8 @@ EOF
|
||||
# -----------------------------------------------------------------------
|
||||
# Storage directories
|
||||
# -----------------------------------------------------------------------
|
||||
homey.storage.upholds = [ "podman-jellyfin.service" ];
|
||||
|
||||
homey.storage.extraDirs = [
|
||||
{ path = "jellyfin"; }
|
||||
{ path = "jellyfin/config"; }
|
||||
|
||||
@@ -113,6 +113,8 @@ in
|
||||
# -----------------------------------------------------------------------
|
||||
# Storage directories
|
||||
# -----------------------------------------------------------------------
|
||||
homey.storage.upholds = [ "podman-mealie.service" ];
|
||||
|
||||
homey.storage.extraDirs = [
|
||||
{ path = "mealie"; }
|
||||
{ path = "mealie/data"; mode = "0755"; }
|
||||
|
||||
@@ -118,6 +118,8 @@ in
|
||||
# -------------------------------------------------------------------------
|
||||
# Storage directories on external HD
|
||||
# -------------------------------------------------------------------------
|
||||
homey.storage.upholds = [ "podman-mlflow.service" ];
|
||||
|
||||
homey.storage.extraDirs = [
|
||||
{ path = "mlflow"; }
|
||||
{ path = "mlflow/data"; mode = "0750"; }
|
||||
|
||||
@@ -49,6 +49,19 @@ let
|
||||
MaxConnectionsPerChild 500
|
||||
</IfModule>
|
||||
'';
|
||||
|
||||
# PHP 8.4's JIT miscompiles OC\Config\UserConfig::getValueBool() on this
|
||||
# image, corrupting the type of the value passed to strtolower() and
|
||||
# throwing "Argument #1 ($string) must be of type string, string given"
|
||||
# on every request that bootstraps a user session (confirmed by disabling
|
||||
# JIT live on 2026-10-08: the crash stopped immediately). Not an app or
|
||||
# data bug — getTypedValue()'s `: string` return type and
|
||||
# decryptSensitiveValue()'s `string &$value` by-ref param both independently
|
||||
# guarantee the value is already a real string under strict_types=1 by the
|
||||
# time strtolower() rejects it.
|
||||
phpDisableJitConfig = pkgs.writeText "zzz-disable-jit.ini" ''
|
||||
opcache.jit=0
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.homey.nextcloud = {
|
||||
@@ -158,6 +171,8 @@ in
|
||||
"${nextcloudCustomConfig}:/var/www/html/config/zakobar.config.php:ro"
|
||||
# Apache MPM limits (caps concurrent PHP processes / disk writers)
|
||||
"${apacheMpmConfig}:/etc/apache2/mods-available/mpm_prefork.conf:ro"
|
||||
# Disable JIT — see phpDisableJitConfig comment above for why.
|
||||
"${phpDisableJitConfig}:/usr/local/etc/php/conf.d/zzz-disable-jit.ini:ro"
|
||||
];
|
||||
|
||||
extraOptions = [
|
||||
@@ -210,6 +225,8 @@ in
|
||||
# UID 33 = www-data in the Nextcloud container
|
||||
# UID 999 = postgres — must own the db dir (creates files directly in it)
|
||||
# -----------------------------------------------------------------------
|
||||
homey.storage.upholds = [ "podman-nextcloud-postgres.service" "podman-nextcloud.service" ];
|
||||
|
||||
homey.storage.extraDirs = [
|
||||
{ path = "nextcloud"; }
|
||||
{ path = "nextcloud/html"; user = "33"; group = "33"; }
|
||||
|
||||
@@ -198,6 +198,8 @@ in
|
||||
# -----------------------------------------------------------------------
|
||||
# Storage directories (owned by the ntfy-sh system user)
|
||||
# -----------------------------------------------------------------------
|
||||
homey.storage.upholds = [ "ntfy-sh.service" ];
|
||||
|
||||
homey.storage.extraDirs = [
|
||||
{ path = "ntfy"; user = "ntfy-sh"; group = "ntfy-sh"; }
|
||||
{ path = "ntfy/attachments"; user = "ntfy-sh"; group = "ntfy-sh"; }
|
||||
|
||||
@@ -117,6 +117,8 @@ in
|
||||
# -----------------------------------------------------------------------
|
||||
# Storage directories
|
||||
# -----------------------------------------------------------------------
|
||||
homey.storage.upholds = [ "podman-openldap.service" ];
|
||||
|
||||
homey.storage.extraDirs = [
|
||||
{ path = "openldap"; }
|
||||
{ path = "openldap/etc-ldap-slapd.d"; }
|
||||
|
||||
@@ -152,6 +152,8 @@ in
|
||||
# -----------------------------------------------------------------------
|
||||
# Storage directories (UID 1000 = USERMAP_UID in container)
|
||||
# -----------------------------------------------------------------------
|
||||
homey.storage.upholds = [ "podman-paperless-redis.service" "podman-paperless.service" ];
|
||||
|
||||
homey.storage.extraDirs = [
|
||||
{ path = "paperless"; }
|
||||
{ path = "paperless/data"; user = "1000"; group = "1000"; }
|
||||
|
||||
@@ -72,6 +72,8 @@ in
|
||||
# -----------------------------------------------------------------------
|
||||
# Storage directories
|
||||
# -----------------------------------------------------------------------
|
||||
homey.storage.upholds = [ "podman-stirling-pdf.service" ];
|
||||
|
||||
homey.storage.extraDirs = [
|
||||
{ path = "stirling-pdf"; }
|
||||
{ path = "stirling-pdf/configs"; mode = "0755"; }
|
||||
|
||||
@@ -87,6 +87,8 @@ in
|
||||
# -----------------------------------------------------------------------
|
||||
# Storage directories
|
||||
# -----------------------------------------------------------------------
|
||||
homey.storage.upholds = [ "podman-transmission.service" ];
|
||||
|
||||
homey.storage.extraDirs = [
|
||||
{ path = "transmission"; user = "1000"; group = "1000"; mode = "0755"; }
|
||||
{ path = "transmission/config"; user = "1000"; group = "1000"; mode = "0755"; }
|
||||
|
||||
@@ -305,6 +305,8 @@ in
|
||||
# -----------------------------------------------------------------------
|
||||
# Storage directories
|
||||
# -----------------------------------------------------------------------
|
||||
homey.storage.upholds = [ "podman-uptime-kuma.service" ];
|
||||
|
||||
homey.storage.extraDirs = [
|
||||
{ path = "uptime-kuma"; }
|
||||
];
|
||||
|
||||
@@ -79,6 +79,28 @@ in
|
||||
default = [];
|
||||
description = "Per-service directories to create under mountPoint. Each service module contributes its own entries.";
|
||||
};
|
||||
|
||||
upholds = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Systemd unit names (e.g. "podman-openldap.service") that must come
|
||||
back up on their own after the external drive remounts.
|
||||
|
||||
These units already have Requires=mnt-data.mount, so they correctly
|
||||
stop when the drive disappears. The problem is the reverse direction:
|
||||
a plain Requires= dependency does NOT restart when the required unit
|
||||
becomes available again — systemd treats a Requires-propagated stop
|
||||
the same as a deliberate "systemctl stop", so Restart= policies on
|
||||
the unit itself never fire either. Verified empirically on 2026-10-02
|
||||
after a USB disconnect/remount left 11 services dead for ~4 hours.
|
||||
|
||||
The fix is a dedicated keepalive unit bound to the mount (below) that
|
||||
Upholds= every unit listed here — Upholds continuously (re)starts its
|
||||
targets for as long as the upholding unit is active, which is exactly
|
||||
the "come back when the mount comes back" behavior Requires= lacks.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (cfg.device != "") {
|
||||
@@ -95,6 +117,23 @@ in
|
||||
];
|
||||
};
|
||||
|
||||
# See the `upholds` option doc above for why this unit exists.
|
||||
systemd.services.storage-keepalive =
|
||||
let
|
||||
mountUnit = "${lib.replaceStrings [ "/" ] [ "-" ] (lib.removePrefix "/" cfg.mountPoint)}.mount";
|
||||
in {
|
||||
description = "Keeps storage-dependent services running whenever ${cfg.mountPoint} is mounted";
|
||||
bindsTo = [ mountUnit ];
|
||||
after = [ mountUnit ];
|
||||
wantedBy = [ mountUnit ];
|
||||
unitConfig.Upholds = cfg.upholds;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = "${pkgs.coreutils}/bin/true";
|
||||
};
|
||||
};
|
||||
|
||||
# Mount point root + shared infrastructure dirs (restic cache, shared media).
|
||||
# Per-service dirs are contributed via homey.storage.extraDirs by each
|
||||
# service module, so adding a new service only requires editing that module.
|
||||
|
||||
Reference in New Issue
Block a user