From 9ef938967284a7b400ff8967aa5ec26878ba4517 Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Mon, 4 May 2026 20:25:04 -0400 Subject: [PATCH] *arr: fix (?) --- lib/default.nix | 79 +++++++++++++++++++++++++++++++++++++++++ services/arr/radarr.nix | 9 +++++ services/arr/sonarr.nix | 11 ++++++ 3 files changed, 99 insertions(+) diff --git a/lib/default.nix b/lib/default.nix index 5ec3f1e..2b88b16 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -285,5 +285,84 @@ lib.extend ( # Returns a string suitable for $() command substitution in shell scripts. extractArrApiKey = configXmlPath: "${lib.getExe pkgs.gnugrep} -oP '(?<=)[^<]+' ${configXmlPath}"; + + # Creates a NixOS-managed *arr settings sync service. + # Spawns a oneshot systemd unit that PUTs the requested values into the *arr API + # whenever the *arr service starts (or when the desired settings change). The + # API key is read from the *arr's own config.xml at runtime. + # + # `settings` is keyed by API endpoint under /api/v3/config/, with one nested + # attrset of field-name -> value pairs per endpoint. Idempotent: it skips the + # PUT when the live config already matches. + mkArrSettingsService = + { + name, + port, + dataDir, + settings, + }: + { pkgs, config, ... }: + let + configXml = "${dataDir}/config.xml"; + endpoints = lib.attrNames settings; + applyOne = + endpoint: + let + jqFilter = lib.concatStringsSep " | " ( + lib.mapAttrsToList (k: v: ".${k} = ${builtins.toJSON v}") settings.${endpoint} + ); + in + '' + echo ":: ${name} config/${endpoint}" + CFG=$(curl -fsS -H "X-Api-Key: $KEY" "$BASE/config/${endpoint}") + NEW=$(jq '${jqFilter}' <<<"$CFG") + if [ "$CFG" = "$NEW" ]; then + echo " unchanged" + else + ID=$(jq -r .id <<<"$NEW") + curl -fsS -X PUT \ + -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \ + --data "$NEW" "$BASE/config/${endpoint}/$ID" >/dev/null + echo ${final.escapeShellArg " applied: ${jqFilter}"} + fi + ''; + applyScript = pkgs.writeShellApplication { + name = "${name}-apply-settings"; + runtimeInputs = with pkgs; [ + curl + jq + gnugrep + ]; + text = '' + set -euo pipefail + KEY=$(grep -oP '(?<=)[^<]+' ${configXml}) + BASE="http://localhost:${toString port}/api/v3" + + # Wait up to 60s for the API to come online. + for _ in $(seq 1 30); do + if curl -fsS --max-time 2 -H "X-Api-Key: $KEY" "$BASE/system/status" >/dev/null 2>&1; then + break + fi + sleep 2 + done + + ${lib.concatStringsSep "\n" (map applyOne endpoints)} + ''; + }; + in + { + systemd.services."${name}-apply-settings" = { + description = "Apply NixOS-managed ${name} settings via API"; + after = [ "${name}.service" ]; + wantedBy = [ "${name}.service" ]; + restartTriggers = [ applyScript ]; + serviceConfig = { + Type = "oneshot"; + User = config.services.${name}.user; + Group = config.services.${name}.group; + ExecStart = lib.getExe applyScript; + }; + }; + }; } ) diff --git a/services/arr/radarr.nix b/services/arr/radarr.nix index 7a523c3..4681702 100644 --- a/services/arr/radarr.nix +++ b/services/arr/radarr.nix @@ -21,6 +21,15 @@ port = service_configs.ports.private.radarr.port; auth = true; }) + (lib.mkArrSettingsService { + name = "radarr"; + port = service_configs.ports.private.radarr.port; + dataDir = service_configs.radarr.dataDir; + # See services/arr/sonarr.nix for the rationale -- Radarr exhibits the same + # bug, and has already imported ~10 FGT junk PROPER 2160p Remuxes (CF -9995) + # over good releases because PreferAndUpgrade ignored the CF score. + settings.mediamanagement.downloadPropersAndRepacks = "doNotPrefer"; + }) ]; services.radarr = { diff --git a/services/arr/sonarr.nix b/services/arr/sonarr.nix index 6200dc3..ef669c4 100644 --- a/services/arr/sonarr.nix +++ b/services/arr/sonarr.nix @@ -21,6 +21,17 @@ port = service_configs.ports.private.sonarr.port; auth = true; }) + (lib.mkArrSettingsService { + name = "sonarr"; + port = service_configs.ports.private.sonarr.port; + dataDir = service_configs.sonarr.dataDir; + # Stop Sonarr from auto-upgrading to PROPER/REPACK releases purely on + # revision. Repack/Proper +5 in the recyclarr custom formats already gives + # them a small preference; PreferAndUpgrade made revision override CF score + # entirely, which silently swapped scored 4K WEB-DL files for low-scored + # 1080p Bluray Remux PROPERs. + settings.mediamanagement.downloadPropersAndRepacks = "doNotPrefer"; + }) ]; systemd.tmpfiles.rules = [