Add auto-interconnect service for *arr stack
Systemd oneshot that runs after all services start and configures: - Prowlarr → Sonarr (TV indexers, full sync) - Prowlarr → Radarr (movie indexers, full sync) - Sonarr → qBittorrent (download client, category: tv-sonarr) - Radarr → qBittorrent (download client, category: radarr) - Bazarr → Sonarr (subtitle management for TV) - Bazarr → Radarr (subtitle management for movies) Fully idempotent — checks for existing connections before creating. API keys extracted from each app's config files at runtime. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d878d3b20c
commit
f59fce5087
2 changed files with 294 additions and 0 deletions
|
|
@ -31,6 +31,7 @@
|
|||
./services/cloudflare-ddns.nix
|
||||
./services/fail2ban.nix
|
||||
./services/homepage.nix
|
||||
./services/arr-interconnect.nix
|
||||
];
|
||||
|
||||
### Make build time quicker
|
||||
|
|
|
|||
293
services/arr-interconnect.nix
Normal file
293
services/arr-interconnect.nix
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
interconnectScript = pkgs.writeShellScript "arr-interconnect" ''
|
||||
set -euo pipefail
|
||||
PATH="${lib.makeBinPath [ pkgs.curl pkgs.jq pkgs.gnused pkgs.gnugrep pkgs.coreutils ]}:$PATH"
|
||||
|
||||
BASE="http://127.0.0.1"
|
||||
|
||||
# --- Extract API keys ---
|
||||
extract_arr_key() {
|
||||
if [ -f "$1" ]; then
|
||||
sed -n 's/.*<ApiKey>\(.*\)<\/ApiKey>.*/\1/p' "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
SONARR_KEY=$(extract_arr_key "/var/lib/sonarr/config.xml")
|
||||
RADARR_KEY=$(extract_arr_key "/var/lib/radarr/config.xml")
|
||||
PROWLARR_KEY=$(extract_arr_key "/var/lib/prowlarr/config.xml")
|
||||
|
||||
BAZARR_KEY=""
|
||||
if [ -f "/var/lib/bazarr/data/config/config.ini" ]; then
|
||||
BAZARR_KEY=$(grep -oP '(?<=apikey = ).*' /var/lib/bazarr/data/config/config.ini || true)
|
||||
fi
|
||||
|
||||
# --- Helpers ---
|
||||
wait_for() {
|
||||
local name="$1" url="$2" key="$3"
|
||||
echo "Waiting for $name..."
|
||||
for i in $(seq 1 30); do
|
||||
if curl -sf -o /dev/null -H "X-Api-Key: $key" "$url"; then
|
||||
echo "$name is ready"
|
||||
return 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
echo "WARNING: $name not ready after 60s, skipping"
|
||||
return 1
|
||||
}
|
||||
|
||||
exists_by_name() {
|
||||
local url="$1" key="$2" name="$3"
|
||||
local count
|
||||
count=$(curl -sf -H "X-Api-Key: $key" "$url" | jq --arg n "$name" '[.[] | select(.name == $n)] | length')
|
||||
[ "$count" -gt "0" ]
|
||||
}
|
||||
|
||||
# --- Wait for services ---
|
||||
wait_for "Sonarr" "$BASE:8989/api/v3/system/status" "$SONARR_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
|
||||
|
||||
##########################################################################
|
||||
# Prowlarr → Sonarr (push indexers for TV)
|
||||
##########################################################################
|
||||
if [ -n "$PROWLARR_KEY" ] && [ -n "$SONARR_KEY" ]; then
|
||||
if ! exists_by_name "$BASE:9696/api/v1/applications" "$PROWLARR_KEY" "Sonarr"; then
|
||||
echo "Adding Sonarr to Prowlarr..."
|
||||
curl -sf -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Api-Key: $PROWLARR_KEY" \
|
||||
"$BASE:9696/api/v1/applications" \
|
||||
-d "$(jq -n --arg key "$SONARR_KEY" '{
|
||||
name: "Sonarr",
|
||||
syncLevel: "fullSync",
|
||||
implementation: "Sonarr",
|
||||
configContract: "SonarrSettings",
|
||||
implementationName: "Sonarr",
|
||||
fields: [
|
||||
{name: "prowlarrUrl", value: "http://localhost:9696"},
|
||||
{name: "baseUrl", value: "http://localhost:8989"},
|
||||
{name: "apiKey", value: $key},
|
||||
{name: "syncCategories", value: [5000,5010,5020,5030,5040,5045,5050,5060,5070,5080]}
|
||||
],
|
||||
tags: []
|
||||
}')" > /dev/null && echo " done" || echo " failed"
|
||||
else
|
||||
echo "Prowlarr → Sonarr already configured"
|
||||
fi
|
||||
fi
|
||||
|
||||
##########################################################################
|
||||
# Prowlarr → Radarr (push indexers for movies)
|
||||
##########################################################################
|
||||
if [ -n "$PROWLARR_KEY" ] && [ -n "$RADARR_KEY" ]; then
|
||||
if ! exists_by_name "$BASE:9696/api/v1/applications" "$PROWLARR_KEY" "Radarr"; then
|
||||
echo "Adding Radarr to Prowlarr..."
|
||||
curl -sf -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Api-Key: $PROWLARR_KEY" \
|
||||
"$BASE:9696/api/v1/applications" \
|
||||
-d "$(jq -n --arg key "$RADARR_KEY" '{
|
||||
name: "Radarr",
|
||||
syncLevel: "fullSync",
|
||||
implementation: "Radarr",
|
||||
configContract: "RadarrSettings",
|
||||
implementationName: "Radarr",
|
||||
fields: [
|
||||
{name: "prowlarrUrl", value: "http://localhost:9696"},
|
||||
{name: "baseUrl", value: "http://localhost:7878"},
|
||||
{name: "apiKey", value: $key},
|
||||
{name: "syncCategories", value: [2000,2010,2020,2030,2040,2045,2050,2060,2070,2080]}
|
||||
],
|
||||
tags: []
|
||||
}')" > /dev/null && echo " done" || echo " failed"
|
||||
else
|
||||
echo "Prowlarr → Radarr already configured"
|
||||
fi
|
||||
fi
|
||||
|
||||
##########################################################################
|
||||
# Sonarr → qBittorrent (download client for TV)
|
||||
##########################################################################
|
||||
if [ -n "$SONARR_KEY" ]; then
|
||||
if ! exists_by_name "$BASE:8989/api/v3/downloadclient" "$SONARR_KEY" "qBittorrent"; then
|
||||
echo "Adding qBittorrent to Sonarr..."
|
||||
curl -sf -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Api-Key: $SONARR_KEY" \
|
||||
"$BASE:8989/api/v3/downloadclient" \
|
||||
-d '{
|
||||
"enable": true,
|
||||
"protocol": "torrent",
|
||||
"priority": 1,
|
||||
"removeCompletedDownloads": true,
|
||||
"removeFailedDownloads": true,
|
||||
"name": "qBittorrent",
|
||||
"implementation": "QBittorrent",
|
||||
"configContract": "QBittorrentSettings",
|
||||
"implementationName": "qBittorrent",
|
||||
"fields": [
|
||||
{"name": "host", "value": "localhost"},
|
||||
{"name": "port", "value": 8080},
|
||||
{"name": "useSsl", "value": false},
|
||||
{"name": "urlBase", "value": ""},
|
||||
{"name": "username", "value": ""},
|
||||
{"name": "password", "value": ""},
|
||||
{"name": "category", "value": "tv-sonarr"},
|
||||
{"name": "recentPriority", "value": 0},
|
||||
{"name": "olderPriority", "value": 0},
|
||||
{"name": "initialState", "value": 0},
|
||||
{"name": "sequentialOrder", "value": false},
|
||||
{"name": "firstAndLastFirst", "value": false}
|
||||
],
|
||||
"tags": []
|
||||
}' > /dev/null && echo " done" || echo " failed"
|
||||
else
|
||||
echo "Sonarr → qBittorrent already configured"
|
||||
fi
|
||||
fi
|
||||
|
||||
##########################################################################
|
||||
# Radarr → qBittorrent (download client for movies)
|
||||
##########################################################################
|
||||
if [ -n "$RADARR_KEY" ]; then
|
||||
if ! exists_by_name "$BASE:7878/api/v3/downloadclient" "$RADARR_KEY" "qBittorrent"; then
|
||||
echo "Adding qBittorrent to Radarr..."
|
||||
curl -sf -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Api-Key: $RADARR_KEY" \
|
||||
"$BASE:7878/api/v3/downloadclient" \
|
||||
-d '{
|
||||
"enable": true,
|
||||
"protocol": "torrent",
|
||||
"priority": 1,
|
||||
"removeCompletedDownloads": true,
|
||||
"removeFailedDownloads": true,
|
||||
"name": "qBittorrent",
|
||||
"implementation": "QBittorrent",
|
||||
"configContract": "QBittorrentSettings",
|
||||
"implementationName": "qBittorrent",
|
||||
"fields": [
|
||||
{"name": "host", "value": "localhost"},
|
||||
{"name": "port", "value": 8080},
|
||||
{"name": "useSsl", "value": false},
|
||||
{"name": "urlBase", "value": ""},
|
||||
{"name": "username", "value": ""},
|
||||
{"name": "password", "value": ""},
|
||||
{"name": "category", "value": "radarr"},
|
||||
{"name": "recentPriority", "value": 0},
|
||||
{"name": "olderPriority", "value": 0},
|
||||
{"name": "initialState", "value": 0},
|
||||
{"name": "sequentialOrder", "value": false},
|
||||
{"name": "firstAndLastFirst", "value": false}
|
||||
],
|
||||
"tags": []
|
||||
}' > /dev/null && echo " done" || echo " failed"
|
||||
else
|
||||
echo "Radarr → qBittorrent already configured"
|
||||
fi
|
||||
fi
|
||||
|
||||
##########################################################################
|
||||
# Bazarr → Sonarr (subtitle management for TV)
|
||||
##########################################################################
|
||||
if [ -n "$BAZARR_KEY" ] && [ -n "$SONARR_KEY" ]; then
|
||||
# Check if Sonarr is already configured in Bazarr
|
||||
CURRENT_SONARR_KEY=$(curl -sf -H "X-API-KEY: $BAZARR_KEY" \
|
||||
"$BASE:6767/api/system/settings" | jq -r '.data.settings.sonarr.apikey // empty' 2>/dev/null || true)
|
||||
|
||||
if [ -z "$CURRENT_SONARR_KEY" ]; then
|
||||
echo "Configuring Sonarr in Bazarr..."
|
||||
curl -sf -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-KEY: $BAZARR_KEY" \
|
||||
"$BASE:6767/api/system/settings" \
|
||||
-d "$(jq -n --arg key "$SONARR_KEY" '{
|
||||
settings: {
|
||||
sonarr: {
|
||||
ip: "127.0.0.1",
|
||||
port: "8989",
|
||||
base_url: "/",
|
||||
ssl: "false",
|
||||
apikey: $key,
|
||||
full_update: "Daily",
|
||||
only_monitored: "false",
|
||||
series_sync: "60",
|
||||
episodes_sync: "60"
|
||||
}
|
||||
}
|
||||
}')" > /dev/null && echo " done" || echo " failed"
|
||||
else
|
||||
echo "Bazarr → Sonarr already configured"
|
||||
fi
|
||||
fi
|
||||
|
||||
##########################################################################
|
||||
# Bazarr → Radarr (subtitle management for movies)
|
||||
##########################################################################
|
||||
if [ -n "$BAZARR_KEY" ] && [ -n "$RADARR_KEY" ]; then
|
||||
CURRENT_RADARR_KEY=$(curl -sf -H "X-API-KEY: $BAZARR_KEY" \
|
||||
"$BASE:6767/api/system/settings" | jq -r '.data.settings.radarr.apikey // empty' 2>/dev/null || true)
|
||||
|
||||
if [ -z "$CURRENT_RADARR_KEY" ]; then
|
||||
echo "Configuring Radarr in Bazarr..."
|
||||
curl -sf -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-KEY: $BAZARR_KEY" \
|
||||
"$BASE:6767/api/system/settings" \
|
||||
-d "$(jq -n --arg key "$RADARR_KEY" '{
|
||||
settings: {
|
||||
radarr: {
|
||||
ip: "127.0.0.1",
|
||||
port: "7878",
|
||||
base_url: "/",
|
||||
ssl: "false",
|
||||
apikey: $key,
|
||||
full_update: "Daily",
|
||||
only_monitored: "false",
|
||||
movies_sync: "60"
|
||||
}
|
||||
}
|
||||
}')" > /dev/null && echo " done" || echo " failed"
|
||||
else
|
||||
echo "Bazarr → Radarr already configured"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Interconnect setup complete."
|
||||
'';
|
||||
in
|
||||
{
|
||||
config = lib.mkIf (config.networking.hostName == "FredOS-Mediaserver") {
|
||||
|
||||
systemd.services.arr-interconnect = {
|
||||
description = "Auto-configure connections between *arr services";
|
||||
after = [
|
||||
"sonarr.service"
|
||||
"radarr.service"
|
||||
"prowlarr.service"
|
||||
"bazarr.service"
|
||||
"qbittorrent-nox.service"
|
||||
];
|
||||
wants = [
|
||||
"sonarr.service"
|
||||
"radarr.service"
|
||||
"prowlarr.service"
|
||||
"bazarr.service"
|
||||
"qbittorrent-nox.service"
|
||||
];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = interconnectScript;
|
||||
RemainAfterExit = true;
|
||||
# Retry once if services weren't ready
|
||||
Restart = "on-failure";
|
||||
RestartSec = "30s";
|
||||
StartLimitBurst = 3;
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue