{ config, lib, pkgs, ... }: let jfCfg = config.services.jellyfin; ntfyCfg = config.services.ntfyAlerts; in lib.mkIf (jfCfg.enable && ntfyCfg.enable) { systemd.services.jellyfin-failure-alert = { description = "Monitor Jellyfin logs for client playback failures and alert via ntfy"; after = [ "network.target" "jellyfin.service" ]; wants = [ "jellyfin.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "simple"; ExecStart = pkgs.writeShellScript "jellyfin-failure-alert-start" '' set -euo pipefail export NTFY_TOPIC=$(cat "$CREDENTIALS_DIRECTORY/ntfy-topic" | tr -d '[:space:]') ${lib.optionalString (ntfyCfg.tokenFile != null) '' export NTFY_TOKEN_FILE="$CREDENTIALS_DIRECTORY/ntfy-token" ''} exec ${pkgs.python3}/bin/python ${./jellyfin-failure-alert.py} ''; Restart = "always"; RestartSec = "10s"; # Security hardening DynamicUser = true; NoNewPrivileges = true; ProtectSystem = "strict"; ProtectHome = true; ProtectKernelTunables = true; ProtectKernelModules = true; ProtectControlGroups = true; MemoryDenyWriteExecute = true; RestrictRealtime = true; RestrictSUIDSGID = true; RemoveIPC = true; # DynamicUser needs jellyfin group to read 0700 log dir SupplementaryGroups = [ jfCfg.group ]; # Load credentials from agenix secrets LoadCredential = [ "ntfy-topic:${ntfyCfg.topicFile}" ] ++ lib.optional (ntfyCfg.tokenFile != null) "ntfy-token:${ntfyCfg.tokenFile}"; }; environment = { JELLYFIN_LOG_DIR = "${jfCfg.dataDir}/log"; NTFY_SERVER_URL = ntfyCfg.serverUrl; HOSTNAME = config.networking.hostName; POLL_INTERVAL = "15"; DEDUP_WINDOW = "300"; }; }; }