116 lines
3.4 KiB
Nix
116 lines
3.4 KiB
Nix
{
|
|
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";
|
|
};
|
|
};
|
|
}
|