{ pkgs, lib, service_configs, ... }: let radarrConfig = "${service_configs.radarr.dataDir}/config.xml"; sonarrConfig = "${service_configs.sonarr.dataDir}/config.xml"; radarrUrl = "http://localhost:${builtins.toString service_configs.ports.private.radarr.port}"; sonarrUrl = "http://localhost:${builtins.toString service_configs.ports.private.sonarr.port}"; curl = "${pkgs.curl}/bin/curl"; jq = "${pkgs.jq}/bin/jq"; # Max items to search per cycle per category (missing + cutoff) per app maxPerCycle = 5; searchScript = pkgs.writeShellScript "arr-search" '' set -euo pipefail RADARR_KEY=$(${lib.extractArrApiKey radarrConfig}) SONARR_KEY=$(${lib.extractArrApiKey sonarrConfig}) search_radarr() { local endpoint="$1" local label="$2" local ids ids=$(${curl} -sf --max-time 30 \ -H "X-Api-Key: $RADARR_KEY" \ "${radarrUrl}/api/v3/wanted/$endpoint?page=1&pageSize=${builtins.toString maxPerCycle}&monitored=true&sortKey=title&sortDirection=ascending" \ | ${jq} -r '.records[].id // empty') if [ -z "$ids" ]; then echo "radarr: no $label items" return fi local id_array id_array=$(echo "$ids" | ${jq} -Rs '[split("\n") | .[] | select(. != "") | tonumber]') echo "radarr: searching $label: $id_array" ${curl} -sf --max-time 60 \ -H "X-Api-Key: $RADARR_KEY" \ -H "Content-Type: application/json" \ -X POST "${radarrUrl}/api/v3/command" \ -d "{\"name\": \"MoviesSearch\", \"movieIds\": $id_array}" > /dev/null } search_sonarr() { local endpoint="$1" local label="$2" local series_ids series_ids=$(${curl} -sf --max-time 30 \ -H "X-Api-Key: $SONARR_KEY" \ "${sonarrUrl}/api/v3/wanted/$endpoint?page=1&pageSize=${builtins.toString maxPerCycle}&monitored=true&sortKey=title&sortDirection=ascending&includeSeries=true" \ | ${jq} -r '[.records[].seriesId] | unique | .[] // empty') if [ -z "$series_ids" ]; then echo "sonarr: no $label items" return fi # search per series (sonarr searches by series, not episode) for sid in $series_ids; do echo "sonarr: searching $label series $sid" ${curl} -sf --max-time 60 \ -H "X-Api-Key: $SONARR_KEY" \ -H "Content-Type: application/json" \ -X POST "${sonarrUrl}/api/v3/command" \ -d "{\"name\": \"SeriesSearch\", \"seriesId\": $sid}" > /dev/null done } echo "=== arr-search $(date -Iseconds) ===" search_radarr "missing" "missing" search_radarr "cutoff" "cutoff-unmet" search_sonarr "missing" "missing" search_sonarr "cutoff" "cutoff-unmet" echo "=== done ===" ''; in { systemd.services.arr-search = { description = "Search for missing and cutoff-unmet media in Radarr/Sonarr"; after = [ "network-online.target" "radarr.service" "sonarr.service" ]; wants = [ "network-online.target" ]; serviceConfig = { Type = "oneshot"; ExecStart = "+${searchScript}"; # + prefix: runs as root to read API keys from config.xml TimeoutSec = 300; }; }; systemd.timers.arr-search = { description = "Periodically search for missing and cutoff-unmet media"; wantedBy = [ "timers.target" ]; timerConfig = { OnCalendar = "*-*-* 03:00:00"; # daily at 3 AM Persistent = true; # run on boot if missed RandomizedDelaySec = "30m"; }; }; }