arr-interconnect: wire seerr to jellyfin/radarr/sonarr + auto-approve

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-07-28 09:15:44 +01:00
parent 71c9bfac77
commit 384c6918ff

View file

@ -38,6 +38,14 @@ let
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"
@ -473,6 +481,163 @@ RUBY
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.
@ -504,6 +669,7 @@ in
"qbittorrent-nox.service"
"sabnzbd.service"
"docker-shelfarr.service"
"seerr.service"
];
wants = [
"sonarr.service"
@ -513,6 +679,7 @@ in
"qbittorrent-nox.service"
"sabnzbd.service"
"docker-shelfarr.service"
"seerr.service"
];
wantedBy = [ "multi-user.target" ];
serviceConfig = {