arr-interconnect: declarative root folders for radarr/sonarr
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
384c6918ff
commit
81d1b80a53
1 changed files with 67 additions and 3 deletions
|
|
@ -1,5 +1,15 @@
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
let
|
let
|
||||||
|
# Declarative root folders. The script reconciles against these: adds any
|
||||||
|
# that are missing, removes any that aren't listed. Removing a root folder
|
||||||
|
# only unregisters the path in Radarr/Sonarr — it never touches media files.
|
||||||
|
# Download-client staging dirs (/mnt/storage/usenet/downloads, the torrent
|
||||||
|
# downloads dir) must never appear here; the *arrs would import from library.
|
||||||
|
rootFolders = {
|
||||||
|
radarr = [ "/mnt/storage/torrents/movies" ];
|
||||||
|
sonarr = [ "/mnt/storage/torrents/shows" ];
|
||||||
|
};
|
||||||
|
|
||||||
interconnectScript = pkgs.writeShellScript "arr-interconnect" ''
|
interconnectScript = pkgs.writeShellScript "arr-interconnect" ''
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
PATH="${lib.makeBinPath [ pkgs.curl pkgs.jq pkgs.gnused pkgs.gnugrep pkgs.coreutils pkgs.systemd pkgs.sqlite pkgs.docker ]}:$PATH"
|
PATH="${lib.makeBinPath [ pkgs.curl pkgs.jq pkgs.gnused pkgs.gnugrep pkgs.coreutils pkgs.systemd pkgs.sqlite pkgs.docker ]}:$PATH"
|
||||||
|
|
@ -73,6 +83,59 @@ let
|
||||||
wait_for "Radarr" "$BASE:7878/api/v3/system/status" "$RADARR_KEY" || true
|
wait_for "Radarr" "$BASE:7878/api/v3/system/status" "$RADARR_KEY" || true
|
||||||
wait_for "Prowlarr" "$BASE:9696/api/v1/system/status" "$PROWLARR_KEY" || true
|
wait_for "Prowlarr" "$BASE:9696/api/v1/system/status" "$PROWLARR_KEY" || true
|
||||||
|
|
||||||
|
##########################################################################
|
||||||
|
# Root folders — reconcile against the declarative list above.
|
||||||
|
#
|
||||||
|
# Runs before the Seerr blocks on purpose: they read rootfolder[0] to pick
|
||||||
|
# activeDirectory, which is only unambiguous once this has pruned the
|
||||||
|
# strays. Deleting a root folder unregisters the path only; existing
|
||||||
|
# movies/series keep their absolute paths and no files are touched.
|
||||||
|
##########################################################################
|
||||||
|
reconcile_root_folders() {
|
||||||
|
local name="$1" port="$2" key="$3" desired="$4" current
|
||||||
|
|
||||||
|
[ -n "$key" ] || return 0
|
||||||
|
|
||||||
|
current=$(curl -sf -H "X-Api-Key: $key" "$BASE:$port/api/v3/rootfolder" || true)
|
||||||
|
if [ -z "$current" ]; then
|
||||||
|
echo "$name not reachable, skipping root folders"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Refuse to prune against an empty list — that would unregister every
|
||||||
|
# root folder the moment the Nix attrset is mistyped.
|
||||||
|
if [ "$(echo "$desired" | jq 'length')" = "0" ]; then
|
||||||
|
echo "$name has no declared root folders, skipping"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$desired" | jq -r '.[]' | while read -r path; do
|
||||||
|
if ! echo "$current" | jq -e --arg p "$path" 'any(.[]; .path == $p)' > /dev/null; then
|
||||||
|
echo "Adding root folder to $name: $path"
|
||||||
|
curl -sf -X POST \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "X-Api-Key: $key" \
|
||||||
|
"$BASE:$port/api/v3/rootfolder" \
|
||||||
|
-d "$(jq -n --arg p "$path" '{path: $p}')" > /dev/null \
|
||||||
|
&& echo " done" || echo " failed"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Tab-separated: root folder paths routinely contain spaces and brackets.
|
||||||
|
echo "$current" \
|
||||||
|
| jq -r --argjson d "$desired" \
|
||||||
|
'.[] | select(.path as $p | ($d | index($p)) == null) | "\(.id)\t\(.path)"' \
|
||||||
|
| while IFS=$'\t' read -r id path; do
|
||||||
|
echo "Removing stray root folder from $name: $path"
|
||||||
|
curl -sf -X DELETE -H "X-Api-Key: $key" \
|
||||||
|
"$BASE:$port/api/v3/rootfolder/$id" > /dev/null \
|
||||||
|
&& echo " done" || echo " failed"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
reconcile_root_folders "Radarr" 7878 "$RADARR_KEY" '${builtins.toJSON rootFolders.radarr}'
|
||||||
|
reconcile_root_folders "Sonarr" 8989 "$SONARR_KEY" '${builtins.toJSON rootFolders.sonarr}'
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# Prowlarr → Sonarr (push indexers for TV)
|
# Prowlarr → Sonarr (push indexers for TV)
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
@ -519,9 +582,10 @@ RUBY
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Radarr. activeProfileId/activeDirectory are required and have no
|
# --- Radarr. activeProfileId/activeDirectory are required and have no
|
||||||
# --- sane default, so pull them from Radarr itself.
|
# --- sane default, so pull them from Radarr itself. rootfolder[0] is
|
||||||
# ponytail: takes Radarr's first quality profile and root folder; pick
|
# --- unambiguous because reconcile_root_folders ran first.
|
||||||
# them explicitly here if you ever run more than one of either.
|
# ponytail: still takes the first quality profile; name it explicitly
|
||||||
|
# here if you ever run more than one.
|
||||||
if [ -n "$RADARR_KEY" ] && [ "$(seerr_get /settings/radarr | jq 'length')" = "0" ]; then
|
if [ -n "$RADARR_KEY" ] && [ "$(seerr_get /settings/radarr | jq 'length')" = "0" ]; then
|
||||||
# || true on every assignment: set -e aborts the whole job on a bare
|
# || true on every assignment: set -e aborts the whole job on a bare
|
||||||
# failing substitution, and Radarr being briefly down must not take
|
# failing substitution, and Radarr being briefly down must not take
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue