697 lines
30 KiB
Nix
697 lines
30 KiB
Nix
{ 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 pkgs.systemd pkgs.sqlite pkgs.docker ]}:$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/config/config.yaml" ]; then
|
|
BAZARR_KEY=$(${pkgs.yq-go}/bin/yq '.auth.apikey' /var/lib/bazarr/config/config.yaml || true)
|
|
fi
|
|
|
|
# SAB writes its api_key into [misc] of sabnzbd.ini on first run; until
|
|
# the user opens the UI once and the key materialises, the SAB blocks
|
|
# below silently skip.
|
|
SABNZBD_KEY=""
|
|
if [ -f "/var/lib/sabnzbd/sabnzbd.ini" ]; then
|
|
SABNZBD_KEY=$(grep -oP '^api_key\s*=\s*\K\S+' /var/lib/sabnzbd/sabnzbd.ini | head -n1 || true)
|
|
fi
|
|
|
|
# Jellyfin has no config.xml api key; any AccessToken in its db works as
|
|
# an API key. Reuse the first one (create one in the Jellyfin UI once if
|
|
# the table is empty — same first-run caveat as SAB above).
|
|
JELLYFIN_KEY=""
|
|
if [ -f "/var/lib/jellyfin/data/jellyfin.db" ]; then
|
|
JELLYFIN_KEY=$(sqlite3 /var/lib/jellyfin/data/jellyfin.db "SELECT AccessToken FROM ApiKeys LIMIT 1;" 2>/dev/null || true)
|
|
fi
|
|
|
|
# Seerr generates main.apiKey into settings.json on first boot, before the
|
|
# setup wizard runs, so the key alone doesn't mean it's configured.
|
|
SEERR_SETTINGS=/var/lib/jellyseerr/config/settings.json
|
|
SEERR_KEY=""
|
|
if [ -f "$SEERR_SETTINGS" ]; then
|
|
SEERR_KEY=$(jq -r '.main.apiKey // empty' "$SEERR_SETTINGS" 2>/dev/null || 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": false,
|
|
"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": false,
|
|
"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
|
|
|
|
##########################################################################
|
|
# Sonarr → SABnzbd (usenet download client for TV)
|
|
##########################################################################
|
|
if [ -n "$SONARR_KEY" ] && [ -n "$SABNZBD_KEY" ]; then
|
|
if ! exists_by_name "$BASE:8989/api/v3/downloadclient" "$SONARR_KEY" "SABnzbd"; then
|
|
echo "Adding SABnzbd to Sonarr..."
|
|
curl -sf -X POST \
|
|
-H "Content-Type: application/json" \
|
|
-H "X-Api-Key: $SONARR_KEY" \
|
|
"$BASE:8989/api/v3/downloadclient" \
|
|
-d "$(jq -n --arg key "$SABNZBD_KEY" '{
|
|
enable: true,
|
|
protocol: "usenet",
|
|
priority: 1,
|
|
removeCompletedDownloads: true,
|
|
removeFailedDownloads: true,
|
|
name: "SABnzbd",
|
|
implementation: "Sabnzbd",
|
|
configContract: "SabnzbdSettings",
|
|
implementationName: "SABnzbd",
|
|
fields: [
|
|
{name: "host", value: "localhost"},
|
|
{name: "port", value: 8085},
|
|
{name: "useSsl", value: false},
|
|
{name: "urlBase", value: ""},
|
|
{name: "apiKey", value: $key},
|
|
{name: "username", value: ""},
|
|
{name: "password", value: ""},
|
|
{name: "tvCategory", value: "tv-sonarr"},
|
|
{name: "recentTvPriority", value: -100},
|
|
{name: "olderTvPriority", value: -100}
|
|
],
|
|
tags: []
|
|
}')" > /dev/null && echo " done" || echo " failed"
|
|
else
|
|
echo "Sonarr → SABnzbd already configured"
|
|
fi
|
|
fi
|
|
|
|
##########################################################################
|
|
# Radarr → SABnzbd (usenet download client for movies)
|
|
##########################################################################
|
|
if [ -n "$RADARR_KEY" ] && [ -n "$SABNZBD_KEY" ]; then
|
|
if ! exists_by_name "$BASE:7878/api/v3/downloadclient" "$RADARR_KEY" "SABnzbd"; then
|
|
echo "Adding SABnzbd to Radarr..."
|
|
curl -sf -X POST \
|
|
-H "Content-Type: application/json" \
|
|
-H "X-Api-Key: $RADARR_KEY" \
|
|
"$BASE:7878/api/v3/downloadclient" \
|
|
-d "$(jq -n --arg key "$SABNZBD_KEY" '{
|
|
enable: true,
|
|
protocol: "usenet",
|
|
priority: 1,
|
|
removeCompletedDownloads: true,
|
|
removeFailedDownloads: true,
|
|
name: "SABnzbd",
|
|
implementation: "Sabnzbd",
|
|
configContract: "SabnzbdSettings",
|
|
implementationName: "SABnzbd",
|
|
fields: [
|
|
{name: "host", value: "localhost"},
|
|
{name: "port", value: 8085},
|
|
{name: "useSsl", value: false},
|
|
{name: "urlBase", value: ""},
|
|
{name: "apiKey", value: $key},
|
|
{name: "username", value: ""},
|
|
{name: "password", value: ""},
|
|
{name: "movieCategory", value: "radarr"},
|
|
{name: "recentMoviePriority", value: -100},
|
|
{name: "olderMoviePriority", value: -100}
|
|
],
|
|
tags: []
|
|
}')" > /dev/null && echo " done" || echo " failed"
|
|
else
|
|
echo "Radarr → SABnzbd already configured"
|
|
fi
|
|
fi
|
|
|
|
##########################################################################
|
|
# Shelfarr → Prowlarr + SABnzbd (audiobook indexer + download client)
|
|
#
|
|
# Shelfarr's own settings/download-client config lives behind session
|
|
# auth in its admin UI, not a public REST API, so this shells into the
|
|
# container and drives ActiveRecord directly via `rails runner`. That
|
|
# needs two secrets Shelfarr generates on first boot and writes into its
|
|
# storage volume — .encryption_keys (Setting values aren't encrypted,
|
|
# but DownloadClient#api_key/#password are) and .secret_key_base
|
|
# (Rails always requires this to boot at all). Both only exist after the
|
|
# container has started once.
|
|
##########################################################################
|
|
if [ -f /var/lib/shelfarr/.encryption_keys ] && [ -f /var/lib/shelfarr/.secret_key_base ]; then
|
|
wait_for "Shelfarr" "$BASE:5056/up" "" || true
|
|
|
|
SHELFARR_SKB=$(cat /var/lib/shelfarr/.secret_key_base)
|
|
docker exec -i \
|
|
-e SECRET_KEY_BASE="$SHELFARR_SKB" \
|
|
-e ARR_PROWLARR_URL="$BASE:9696" \
|
|
-e ARR_PROWLARR_KEY="$PROWLARR_KEY" \
|
|
-e ARR_SABNZBD_URL="$BASE:8085" \
|
|
-e ARR_SABNZBD_KEY="$SABNZBD_KEY" \
|
|
shelfarr sh -c '. /rails/storage/.encryption_keys && bin/rails runner -' <<'RUBY' && echo " done" || echo " failed"
|
|
if ENV["ARR_PROWLARR_URL"].to_s != "" && ENV["ARR_PROWLARR_KEY"].to_s != "" && !SettingsService.prowlarr_configured?
|
|
SettingsService.set(:prowlarr_url, ENV["ARR_PROWLARR_URL"])
|
|
SettingsService.set(:prowlarr_api_key, ENV["ARR_PROWLARR_KEY"])
|
|
SettingsService.set(:indexer_provider, "prowlarr")
|
|
puts "Prowlarr configured in Shelfarr"
|
|
else
|
|
puts "Shelfarr → Prowlarr already configured, or key missing"
|
|
end
|
|
|
|
if ENV["ARR_SABNZBD_URL"].to_s != "" && ENV["ARR_SABNZBD_KEY"].to_s != "" && !DownloadClient.exists?(name: "SABnzbd")
|
|
DownloadClient.create!(
|
|
name: "SABnzbd",
|
|
client_type: "sabnzbd",
|
|
url: ENV["ARR_SABNZBD_URL"],
|
|
api_key: ENV["ARR_SABNZBD_KEY"],
|
|
category: "shelfarr",
|
|
enabled: true,
|
|
priority: 0
|
|
)
|
|
puts "SABnzbd added to Shelfarr"
|
|
else
|
|
puts "Shelfarr → SABnzbd already configured, or key missing"
|
|
end
|
|
RUBY
|
|
else
|
|
echo "Shelfarr hasn't completed its first boot yet (no encryption keys), skipping"
|
|
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
|
|
|
|
##########################################################################
|
|
# Sonarr → Jellyfin (refresh library on import so new shows appear
|
|
# without waiting for Jellyfin's flaky filesystem watcher / full scan)
|
|
##########################################################################
|
|
if [ -n "$SONARR_KEY" ] && [ -n "$JELLYFIN_KEY" ]; then
|
|
if ! exists_by_name "$BASE:8989/api/v3/notification" "$SONARR_KEY" "Jellyfin"; then
|
|
echo "Adding Jellyfin notification to Sonarr..."
|
|
curl -sf -X POST \
|
|
-H "Content-Type: application/json" \
|
|
-H "X-Api-Key: $SONARR_KEY" \
|
|
"$BASE:8989/api/v3/notification" \
|
|
-d "$(jq -n --arg key "$JELLYFIN_KEY" '{
|
|
name: "Jellyfin",
|
|
implementation: "MediaBrowser",
|
|
configContract: "MediaBrowserSettings",
|
|
implementationName: "Emby / Jellyfin",
|
|
onDownload: true,
|
|
onUpgrade: true,
|
|
onRename: true,
|
|
onSeriesDelete: true,
|
|
onEpisodeFileDelete: true,
|
|
onEpisodeFileDeleteForUpgrade: true,
|
|
fields: [
|
|
{name: "host", value: "localhost"},
|
|
{name: "port", value: 8096},
|
|
{name: "useSsl", value: false},
|
|
{name: "apiKey", value: $key},
|
|
{name: "notify", value: false},
|
|
{name: "updateLibrary", value: true}
|
|
],
|
|
tags: []
|
|
}')" > /dev/null && echo " done" || echo " failed"
|
|
else
|
|
echo "Sonarr → Jellyfin already configured"
|
|
fi
|
|
fi
|
|
|
|
##########################################################################
|
|
# Radarr → Jellyfin (refresh library on import)
|
|
##########################################################################
|
|
if [ -n "$RADARR_KEY" ] && [ -n "$JELLYFIN_KEY" ]; then
|
|
if ! exists_by_name "$BASE:7878/api/v3/notification" "$RADARR_KEY" "Jellyfin"; then
|
|
echo "Adding Jellyfin notification to Radarr..."
|
|
curl -sf -X POST \
|
|
-H "Content-Type: application/json" \
|
|
-H "X-Api-Key: $RADARR_KEY" \
|
|
"$BASE:7878/api/v3/notification" \
|
|
-d "$(jq -n --arg key "$JELLYFIN_KEY" '{
|
|
name: "Jellyfin",
|
|
implementation: "MediaBrowser",
|
|
configContract: "MediaBrowserSettings",
|
|
implementationName: "Emby / Jellyfin",
|
|
onDownload: true,
|
|
onUpgrade: true,
|
|
onRename: true,
|
|
onMovieDelete: true,
|
|
onMovieFileDelete: true,
|
|
onMovieFileDeleteForUpgrade: true,
|
|
fields: [
|
|
{name: "host", value: "localhost"},
|
|
{name: "port", value: 8096},
|
|
{name: "useSsl", value: false},
|
|
{name: "apiKey", value: $key},
|
|
{name: "notify", value: false},
|
|
{name: "updateLibrary", value: true}
|
|
],
|
|
tags: []
|
|
}')" > /dev/null && echo " done" || echo " failed"
|
|
else
|
|
echo "Radarr → Jellyfin already configured"
|
|
fi
|
|
fi
|
|
|
|
##########################################################################
|
|
# Seerr → Jellyfin / Radarr / Sonarr, plus auto-approve for all users
|
|
#
|
|
# Every /api/v1/settings/* route resolves X-Api-Key to user id 1 and 403s
|
|
# if that row is missing. User 1 is only created when someone completes
|
|
# the setup wizard by signing in with Jellyfin admin credentials, which we
|
|
# deliberately don't automate — it would mean parking the Jellyfin admin
|
|
# password in a runtime secrets file for a job that runs once. So probe
|
|
# for the admin first and skip the whole block until the wizard is done.
|
|
##########################################################################
|
|
if [ -n "$SEERR_KEY" ]; then
|
|
wait_for "Seerr" "$BASE:5055/api/v1/status" "" || true
|
|
|
|
if ! curl -sf -o /dev/null -H "X-Api-Key: $SEERR_KEY" "$BASE:5055/api/v1/settings/main"; then
|
|
echo "Seerr setup wizard not completed yet (no admin user), skipping Seerr config"
|
|
else
|
|
seerr_get() { curl -sf -H "X-Api-Key: $SEERR_KEY" "$BASE:5055/api/v1$1"; }
|
|
seerr_post() { curl -sf -X POST -H "Content-Type: application/json" \
|
|
-H "X-Api-Key: $SEERR_KEY" "$BASE:5055/api/v1$1" -d "$2"; }
|
|
|
|
# --- Jellyfin: wizard sets ip/port/serverId but leaves apiKey empty,
|
|
# --- which the library scan and user import both need.
|
|
if [ -n "$JELLYFIN_KEY" ]; then
|
|
if [ -z "$(seerr_get /settings/jellyfin | jq -r '.apiKey // empty')" ]; then
|
|
echo "Configuring Jellyfin in Seerr..."
|
|
seerr_post /settings/jellyfin "$(jq -n --arg key "$JELLYFIN_KEY" '{
|
|
ip: "127.0.0.1",
|
|
port: 8096,
|
|
useSsl: false,
|
|
urlBase: "",
|
|
apiKey: $key
|
|
}')" > /dev/null && echo " done" || echo " failed"
|
|
else
|
|
echo "Seerr → Jellyfin already configured"
|
|
fi
|
|
fi
|
|
|
|
# --- Radarr. activeProfileId/activeDirectory are required and have no
|
|
# --- sane default, so pull them from Radarr itself.
|
|
# ponytail: takes Radarr's first quality profile and root folder; pick
|
|
# them explicitly here if you ever run more than one of either.
|
|
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
|
|
# failing substitution, and Radarr being briefly down must not take
|
|
# the rest of the interconnect with it.
|
|
R_PROFILE=$(curl -sf -H "X-Api-Key: $RADARR_KEY" "$BASE:7878/api/v3/qualityprofile" | jq '.[0]' || true)
|
|
R_ROOT=$(curl -sf -H "X-Api-Key: $RADARR_KEY" "$BASE:7878/api/v3/rootfolder" | jq -r '.[0].path' || true)
|
|
|
|
if [ -n "$R_PROFILE" ] && [ "$R_PROFILE" != "null" ] && [ -n "$R_ROOT" ]; then
|
|
echo "Adding Radarr to Seerr..."
|
|
seerr_post /settings/radarr "$(jq -n \
|
|
--arg key "$RADARR_KEY" --arg root "$R_ROOT" \
|
|
--argjson pid "$(echo "$R_PROFILE" | jq '.id')" \
|
|
--arg pname "$(echo "$R_PROFILE" | jq -r '.name')" '{
|
|
name: "Radarr",
|
|
hostname: "localhost",
|
|
port: 7878,
|
|
apiKey: $key,
|
|
useSsl: false,
|
|
baseUrl: "",
|
|
activeProfileId: $pid,
|
|
activeProfileName: $pname,
|
|
activeDirectory: $root,
|
|
is4k: false,
|
|
minimumAvailability: "released",
|
|
isDefault: true,
|
|
externalUrl: "https://radarr.nordhammer.it",
|
|
syncEnabled: true,
|
|
preventSearch: false,
|
|
tagRequests: false,
|
|
tags: []
|
|
}')" > /dev/null && echo " done" || echo " failed"
|
|
else
|
|
echo "Radarr has no quality profile or root folder yet, skipping"
|
|
fi
|
|
else
|
|
echo "Seerr → Radarr already configured, or Radarr key missing"
|
|
fi
|
|
|
|
# --- Sonarr. Anime slots point at the same profile/folder as the
|
|
# --- regular ones; split them in the UI if you ever separate anime.
|
|
if [ -n "$SONARR_KEY" ] && [ "$(seerr_get /settings/sonarr | jq 'length')" = "0" ]; then
|
|
S_PROFILE=$(curl -sf -H "X-Api-Key: $SONARR_KEY" "$BASE:8989/api/v3/qualityprofile" | jq '.[0]' || true)
|
|
S_ROOT=$(curl -sf -H "X-Api-Key: $SONARR_KEY" "$BASE:8989/api/v3/rootfolder" | jq -r '.[0].path' || true)
|
|
|
|
if [ -n "$S_PROFILE" ] && [ "$S_PROFILE" != "null" ] && [ -n "$S_ROOT" ]; then
|
|
echo "Adding Sonarr to Seerr..."
|
|
seerr_post /settings/sonarr "$(jq -n \
|
|
--arg key "$SONARR_KEY" --arg root "$S_ROOT" \
|
|
--argjson pid "$(echo "$S_PROFILE" | jq '.id')" \
|
|
--arg pname "$(echo "$S_PROFILE" | jq -r '.name')" '{
|
|
name: "Sonarr",
|
|
hostname: "localhost",
|
|
port: 8989,
|
|
apiKey: $key,
|
|
useSsl: false,
|
|
baseUrl: "",
|
|
activeProfileId: $pid,
|
|
activeProfileName: $pname,
|
|
activeDirectory: $root,
|
|
activeAnimeProfileId: $pid,
|
|
activeAnimeProfileName: $pname,
|
|
activeAnimeDirectory: $root,
|
|
activeLanguageProfileId: 1,
|
|
activeAnimeLanguageProfileId: 1,
|
|
is4k: false,
|
|
isDefault: true,
|
|
enableSeasonFolders: true,
|
|
externalUrl: "https://sonarr.nordhammer.it",
|
|
syncEnabled: true,
|
|
preventSearch: false,
|
|
tagRequests: false,
|
|
tags: [],
|
|
animeTags: []
|
|
}')" > /dev/null && echo " done" || echo " failed"
|
|
else
|
|
echo "Sonarr has no quality profile or root folder yet, skipping"
|
|
fi
|
|
else
|
|
echo "Seerr → Sonarr already configured, or Sonarr key missing"
|
|
fi
|
|
|
|
######################################################################
|
|
# Auto-approve. REQUEST(32)|AUTO_APPROVE(128) = 160. AUTO_APPROVE is
|
|
# checked with an 'or' against the movie/TV variants, so the single
|
|
# bit covers both. defaultPermissions only lands on users imported
|
|
# *after* it's set, so existing ones get a bulk update too.
|
|
######################################################################
|
|
AUTO_APPROVE_PERMS=160
|
|
|
|
if [ "$(seerr_get /settings/main | jq '.defaultPermissions')" != "$AUTO_APPROVE_PERMS" ]; then
|
|
echo "Setting Seerr default permissions to request + auto-approve..."
|
|
seerr_post /settings/main "$(jq -n --argjson p "$AUTO_APPROVE_PERMS" \
|
|
'{defaultPermissions: $p}')" > /dev/null && echo " done" || echo " failed"
|
|
else
|
|
echo "Seerr default permissions already request + auto-approve"
|
|
fi
|
|
|
|
# Admin (id 1) is excluded — it holds ADMIN and would be downgraded.
|
|
EXISTING_IDS=$(seerr_get "/user?take=1000" \
|
|
| jq -c --argjson p "$AUTO_APPROVE_PERMS" \
|
|
'[.results[] | select(.id != 1 and .permissions != $p) | .id]' || true)
|
|
|
|
if [ -n "$EXISTING_IDS" ] && [ "$EXISTING_IDS" != "[]" ]; then
|
|
echo "Granting auto-approve to existing Seerr users: $EXISTING_IDS"
|
|
curl -sf -X PUT -H "Content-Type: application/json" \
|
|
-H "X-Api-Key: $SEERR_KEY" "$BASE:5055/api/v1/user" \
|
|
-d "$(jq -n --argjson ids "$EXISTING_IDS" --argjson p "$AUTO_APPROVE_PERMS" \
|
|
'{ids: $ids, permissions: $p}')" > /dev/null && echo " done" || echo " failed"
|
|
else
|
|
echo "All existing Seerr users already have auto-approve"
|
|
fi
|
|
fi
|
|
else
|
|
echo "Seerr hasn't written settings.json yet, skipping"
|
|
fi
|
|
|
|
##########################################################################
|
|
# Prowlarr auth — trust localhost so Authelia is the only gate. Other
|
|
# *arr apps default to this; Prowlarr does not.
|
|
##########################################################################
|
|
PROWLARR_CONFIG=/var/lib/prowlarr/config.xml
|
|
if [ -f "$PROWLARR_CONFIG" ]; then
|
|
if grep -q "<AuthenticationRequired>Enabled</AuthenticationRequired>" "$PROWLARR_CONFIG"; then
|
|
echo "Prowlarr auth: switching to DisabledForLocalAddresses..."
|
|
sed -i 's|<AuthenticationRequired>Enabled</AuthenticationRequired>|<AuthenticationRequired>DisabledForLocalAddresses</AuthenticationRequired>|' "$PROWLARR_CONFIG"
|
|
systemctl restart prowlarr
|
|
else
|
|
echo "Prowlarr auth: already DisabledForLocalAddresses"
|
|
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"
|
|
"sabnzbd.service"
|
|
"docker-shelfarr.service"
|
|
"seerr.service"
|
|
];
|
|
wants = [
|
|
"sonarr.service"
|
|
"radarr.service"
|
|
"prowlarr.service"
|
|
"bazarr.service"
|
|
"qbittorrent-nox.service"
|
|
"sabnzbd.service"
|
|
"docker-shelfarr.service"
|
|
"seerr.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;
|
|
};
|
|
};
|
|
|
|
};
|
|
}
|