arr: search for missing and cutoff-unmet media
This commit is contained in:
@@ -39,6 +39,7 @@
|
||||
./services/arr/bazarr.nix
|
||||
./services/arr/jellyseerr.nix
|
||||
./services/arr/recyclarr.nix
|
||||
./services/arr/arr-search.nix
|
||||
./services/arr/init.nix
|
||||
|
||||
./services/soulseek.nix
|
||||
|
||||
115
services/arr/arr-search.nix
Normal file
115
services/arr/arr-search.nix
Normal file
@@ -0,0 +1,115 @@
|
||||
{
|
||||
pkgs,
|
||||
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.radarr}";
|
||||
sonarrUrl = "http://localhost:${builtins.toString service_configs.ports.sonarr}";
|
||||
|
||||
curl = "${pkgs.curl}/bin/curl";
|
||||
jq = "${pkgs.jq}/bin/jq";
|
||||
grep = "${pkgs.gnugrep}/bin/grep";
|
||||
|
||||
# Max items to search per cycle per category (missing + cutoff) per app
|
||||
maxPerCycle = 5;
|
||||
|
||||
searchScript = pkgs.writeShellScript "arr-search" ''
|
||||
set -euo pipefail
|
||||
|
||||
RADARR_KEY=$(${grep} -oP '(?<=<ApiKey>)[^<]+' ${radarrConfig})
|
||||
SONARR_KEY=$(${grep} -oP '(?<=<ApiKey>)[^<]+' ${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";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -58,6 +58,7 @@ in
|
||||
upgrade = {
|
||||
allowed = true;
|
||||
until_quality = "Remux-2160p";
|
||||
until_score = 0;
|
||||
};
|
||||
qualities = [
|
||||
{ name = "Remux-2160p"; }
|
||||
@@ -132,6 +133,7 @@ in
|
||||
upgrade = {
|
||||
allowed = true;
|
||||
until_quality = "WEB 2160p";
|
||||
until_score = 0;
|
||||
};
|
||||
qualities = [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user