mirror of
https://github.com/soulis-1256/assemblrr.git
synced 2026-08-28 17:11:40 +00:00
Express asks the same question as manual. The pick is stored as QUALITY_TIER and applied to movies and TV, including Seerr. Compose up hides sidecar Dockerfile output behind a spinner.
475 lines
17 KiB
Bash
475 lines
17 KiB
Bash
#!/bin/bash
|
|
# assemblrr Seerr & Recyclarr configuration — sourced by config.sh
|
|
# Provides: configure_seerr, configure_recyclarr
|
|
# Requires: lib/core.sh (logging), lib/api.sh (api_get/post helpers),
|
|
# lib/quality.sh (tier + names), lib/arr.sh (lookups),
|
|
# config.sh (_cfg_log_info, log_step, log_step_fail, etc.)
|
|
|
|
set -euo pipefail
|
|
|
|
_seerr_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# shellcheck source=/dev/null
|
|
[ -f "$_seerr_dir/quality.sh" ] && source "$_seerr_dir/quality.sh"
|
|
|
|
# --- Shared helper: connect or update a service (Radarr/Sonarr) in Seerr ---
|
|
# Usage: seerr_connect_service <service_type> <port> <api_key> <profile_func> <active_dir> <is_4k> <cookie_jar> [existing_id]
|
|
# With existing_id: PUT update; without: POST create.
|
|
# Example: seerr_connect_service "radarr" 7878 "$RADARR_API_KEY" "lookup_radarr_profile" "/data/media/movies" "true" "$cookie_jar"
|
|
seerr_connect_service() {
|
|
local service_type="$1"
|
|
local service_port="$2"
|
|
local api_key="$3"
|
|
local profile_func="$4"
|
|
local active_dir="$5"
|
|
local is_4k="$6"
|
|
local cookie_jar="$7"
|
|
local existing_id="${8:-}"
|
|
|
|
if [ -z "$api_key" ]; then
|
|
log_step_fail "Seerr: ${service_type} API key not provided"
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "$cookie_jar" ] || [ ! -f "$cookie_jar" ]; then
|
|
log_step_fail "Seerr: cookie jar not found for ${service_type} connection"
|
|
return 1
|
|
fi
|
|
|
|
local profile
|
|
profile=$($profile_func "$api_key")
|
|
local profile_id="${profile%%:*}"
|
|
local profile_name="${profile##*:}"
|
|
|
|
# id is read-only on PUT — omit it from the body
|
|
local payload
|
|
payload=$(cat <<EOF
|
|
{
|
|
"name": "${service_type^}",
|
|
"hostname": "${service_type}",
|
|
"port": ${service_port},
|
|
"apiKey": "${api_key}",
|
|
"useSsl": false,
|
|
"baseUrl": "",
|
|
"isDefault": true,
|
|
"is4k": ${is_4k},
|
|
"minimumAvailability": "released",
|
|
"syncEnabled": true,
|
|
"activeProfileId": ${profile_id},
|
|
"activeProfileName": "${profile_name}",
|
|
"activeDirectory": "${active_dir}",
|
|
"tags": []
|
|
}
|
|
EOF
|
|
)
|
|
|
|
# Special handling for Sonarr (needs enableSeasonFolders)
|
|
if [ "$service_type" = "sonarr" ]; then
|
|
payload=$(echo "$payload" | jq '.enableSeasonFolders = true' 2>/dev/null || echo "$payload")
|
|
fi
|
|
|
|
local http_code
|
|
if [ -n "$existing_id" ]; then
|
|
http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 -X PUT -b "$cookie_jar" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$payload" \
|
|
"http://${API_HOST}:5055/api/v1/settings/${service_type}/${existing_id}" 2>/dev/null || echo "000")
|
|
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
|
|
log_step "Seerr: updated ${service_type^} profile → ${profile_name}"
|
|
return 0
|
|
else
|
|
log_step_fail "Seerr: failed to update ${service_type} (HTTP $http_code)"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# POST new connection
|
|
http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 -X POST -b "$cookie_jar" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$payload" \
|
|
"http://${API_HOST}:5055/api/v1/settings/${service_type}" 2>/dev/null || echo "000")
|
|
|
|
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
|
|
log_step "Seerr: connected ${service_type^} (profile: ${profile_name})"
|
|
return 0
|
|
else
|
|
log_step_fail "Seerr: failed to connect ${service_type} (HTTP $http_code)"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# --- Shared helper: check if a service is already connected to Seerr ---
|
|
# Usage: seerr_service_connected <service_type> <cookie_jar>
|
|
# Returns 0 and prints count if connected, or 1 on failure
|
|
seerr_service_connected() {
|
|
local service_type="$1"
|
|
local cookie_jar="$2"
|
|
|
|
local svc_list
|
|
svc_list=$(curl -sf --connect-timeout 5 -b "$cookie_jar" \
|
|
"http://${API_HOST}:5055/api/v1/service/${service_type}" 2>/dev/null || echo "")
|
|
|
|
if [ -z "$svc_list" ]; then
|
|
return 1
|
|
fi
|
|
|
|
local count
|
|
count=$(echo "$svc_list" | jq 'length' 2>/dev/null || echo "0")
|
|
echo "$count"
|
|
return 0
|
|
}
|
|
|
|
# --- Shared helper: first existing Seerr server id for a service (or empty) ---
|
|
# Usage: seerr_service_id <service_type> <cookie_jar>
|
|
seerr_service_id() {
|
|
local service_type="$1"
|
|
local cookie_jar="$2"
|
|
|
|
local settings
|
|
settings=$(curl -sf --connect-timeout 5 -b "$cookie_jar" \
|
|
"http://${API_HOST}:5055/api/v1/settings/${service_type}" 2>/dev/null || echo "")
|
|
|
|
if [ -z "$settings" ] || [ "$settings" = "[]" ]; then
|
|
echo ""
|
|
return 1
|
|
fi
|
|
|
|
echo "$settings" | jq -r '.[0].id // empty' 2>/dev/null || echo ""
|
|
}
|
|
|
|
# --- Shared helper: get Seerr session cookie ---
|
|
# Usage: seerr_get_cookie <cookie_jar> <include_hostname>
|
|
# Returns 0 on success, cookie file will be populated
|
|
seerr_get_cookie() {
|
|
local cookie_jar="$1"
|
|
local include_hostname="$2" # "true" or "false"
|
|
|
|
local payload
|
|
if [ "$include_hostname" = "true" ]; then
|
|
# Full auth with hostname (for initial setup)
|
|
local media_host="jellyfin"
|
|
case "${MEDIA_SERVICE:-jellyfin}" in
|
|
jellyfin) media_host="jellyfin"; media_port=8096; media_type=2 ;;
|
|
emby) media_host="emby"; media_port=8096; media_type=3 ;;
|
|
plex) media_host="host.docker.internal"; media_port=32400; media_type=1 ;;
|
|
esac
|
|
|
|
payload=$(jq -nc \
|
|
--arg user "$AUTH_USERNAME" \
|
|
--arg pass "$AUTH_PASSWORD" \
|
|
--arg host "$media_host" \
|
|
--argjson port "$media_port" \
|
|
--argjson stype "$media_type" \
|
|
'{username:$user,password:$pass,hostname:$host,port:$port,useSsl:false,urlBase:"",email:"",serverType:$stype}')
|
|
else
|
|
# Login-only (for re-runs when media server is already configured)
|
|
payload=$(jq -nc --arg user "$AUTH_USERNAME" --arg pass "$AUTH_PASSWORD" \
|
|
'{username:$user,password:$pass}')
|
|
fi
|
|
|
|
local auth_endpoint="jellyfin"
|
|
curl -s --connect-timeout 5 -c "$cookie_jar" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$payload" \
|
|
"http://${API_HOST}:5055/api/v1/auth/${auth_endpoint}" 2>/dev/null >/dev/null || return 1
|
|
|
|
# Verify cookie was created
|
|
if [ ! -s "$cookie_jar" ] || ! grep -q "connect.sid" "$cookie_jar" 2>/dev/null; then
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# --- Recyclarr configuration ---
|
|
|
|
configure_recyclarr() {
|
|
local recyclarr_config_dir="$INSTALL_DIR/config/recyclarr"
|
|
local template_root="$INSTALL_DIR/templates/recyclarr"
|
|
mkdir -p "$recyclarr_config_dir/includes"
|
|
|
|
if [ ! -w "$recyclarr_config_dir" ]; then
|
|
log_step_fail "Recyclarr: config directory not writable (${recyclarr_config_dir})"
|
|
return 1
|
|
fi
|
|
|
|
# Only substitute known vars so $schema in YAML comments is left alone
|
|
export RADARR_API_KEY SONARR_API_KEY
|
|
|
|
local default_label
|
|
default_label=$(quality_profile_names radarr)
|
|
default_label="${default_label%%|*}"
|
|
|
|
if [ ! -f "$template_root/recyclarr.yml" ]; then
|
|
log_step_fail "Recyclarr: template root not found (${template_root}/recyclarr.yml)"
|
|
return 1
|
|
fi
|
|
|
|
if ! envsubst '${RADARR_API_KEY} ${SONARR_API_KEY}' \
|
|
< "$template_root/recyclarr.yml" \
|
|
> "$recyclarr_config_dir/recyclarr.yml"; then
|
|
log_step_fail "Recyclarr: failed to write config (${recyclarr_config_dir}/recyclarr.yml)"
|
|
return 1
|
|
fi
|
|
|
|
# Include packs (no secrets). Replace the directory contents with the template set.
|
|
local include_src="$template_root/includes"
|
|
if [ ! -d "$include_src" ]; then
|
|
log_step_fail "Recyclarr: includes directory not found (${include_src})"
|
|
return 1
|
|
fi
|
|
rm -f "$recyclarr_config_dir/includes"/*.yml 2>/dev/null || true
|
|
if ! cp "$include_src"/*.yml "$recyclarr_config_dir/includes/"; then
|
|
log_step_fail "Recyclarr: failed to copy include packs"
|
|
return 1
|
|
fi
|
|
|
|
local pack_count
|
|
pack_count=$(find "$recyclarr_config_dir/includes" -maxdepth 1 -name '*.yml' | wc -l | tr -d ' ')
|
|
log_step "Recyclarr: generated config (${pack_count} include packs; Seerr default: ${default_label})"
|
|
|
|
local sync_out
|
|
if wait_while "Syncing Recyclarr (TRaSH Guides)" docker exec recyclarr recyclarr sync; then
|
|
log_step "Recyclarr: synced Custom Formats and Quality Profiles successfully"
|
|
else
|
|
sync_out="${WAIT_WHILE_OUTPUT:-}"
|
|
log_step_fail "Recyclarr: failed to sync to Radarr/Sonarr"
|
|
echo "$sync_out" | grep -E '•|Error|error|Invalid|YAML' | head -5 | while IFS= read -r line; do
|
|
_cfg_log_info " $line"
|
|
done
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# --- Seerr configuration ---
|
|
|
|
configure_seerr() {
|
|
local seerr_port=5055
|
|
local max_wait=120
|
|
local wait_time=0
|
|
|
|
# Step 1: Wait for Seerr to be responsive
|
|
echo >&2
|
|
while [ $wait_time -lt $max_wait ]; do
|
|
local http_code
|
|
http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 \
|
|
"http://${API_HOST}:${seerr_port}/api/v1/settings/public" 2>/dev/null || echo "000")
|
|
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
|
|
break
|
|
fi
|
|
wait_inline "Waiting for Seerr to start" "$wait_time"
|
|
sleep 3
|
|
wait_time=$((wait_time + 3))
|
|
done
|
|
echo >&2
|
|
|
|
if [ $wait_time -ge $max_wait ]; then
|
|
log_step_fail "Seerr: not responsive after ${max_wait}s"
|
|
return 1
|
|
fi
|
|
|
|
# Step 2: Check if setup is already complete
|
|
local public_settings
|
|
public_settings=$(curl -s --connect-timeout 5 \
|
|
"http://${API_HOST}:${seerr_port}/api/v1/settings/public" 2>/dev/null || echo "")
|
|
|
|
if [ -n "$public_settings" ]; then
|
|
local initialized
|
|
initialized=$(echo "$public_settings" | jq -r '.initialized // ""' 2>/dev/null || echo "")
|
|
if [ "$initialized" = "True" ] || [ "$initialized" = "true" ]; then
|
|
log_step "Seerr: setup already completed — configuring services"
|
|
local seerr_cookie_jar="/tmp/seerr_cookie_jar_$$_${seerr_port}"
|
|
|
|
# Get session cookie (no hostname needed — already configured)
|
|
if ! seerr_get_cookie "$seerr_cookie_jar" "false"; then
|
|
log_step_fail "Seerr: failed to get session cookie for reconfiguration"
|
|
return 1
|
|
fi
|
|
|
|
echo "Connecting Seerr services" >&2
|
|
|
|
local connect_errors=0
|
|
local is_4k
|
|
is_4k=$(quality_seerr_is_4k)
|
|
if [ -n "$RADARR_API_KEY" ]; then
|
|
local radarr_id
|
|
radarr_id=$(seerr_service_id "radarr" "$seerr_cookie_jar" || true)
|
|
if ! seerr_connect_service "radarr" 7878 "$RADARR_API_KEY" "lookup_radarr_profile" \
|
|
"/data/media/movies" "$is_4k" "$seerr_cookie_jar" "$radarr_id"; then
|
|
log_step_fail "Seerr: failed to configure Radarr (re-run to retry)"
|
|
connect_errors=$((connect_errors + 1))
|
|
fi
|
|
fi
|
|
|
|
if [ -n "$SONARR_API_KEY" ]; then
|
|
local sonarr_id
|
|
sonarr_id=$(seerr_service_id "sonarr" "$seerr_cookie_jar" || true)
|
|
if ! seerr_connect_service "sonarr" 8989 "$SONARR_API_KEY" "lookup_sonarr_profile" \
|
|
"/data/media/tv" "$is_4k" "$seerr_cookie_jar" "$sonarr_id"; then
|
|
log_step_fail "Seerr: failed to configure Sonarr (re-run to retry)"
|
|
connect_errors=$((connect_errors + 1))
|
|
fi
|
|
fi
|
|
|
|
echo >&2
|
|
rm -f "$seerr_cookie_jar"
|
|
[ "$connect_errors" -eq 0 ]
|
|
return $?
|
|
fi
|
|
fi
|
|
|
|
# Step 3: Determine media server connection details
|
|
local media_server_type=""
|
|
local media_server_host=""
|
|
local media_server_port=0
|
|
|
|
case "${MEDIA_SERVICE:-jellyfin}" in
|
|
jellyfin)
|
|
media_server_type="2"
|
|
media_server_host="jellyfin"
|
|
media_server_port=8096
|
|
;;
|
|
emby)
|
|
media_server_type="3"
|
|
media_server_host="emby"
|
|
media_server_port=8096
|
|
;;
|
|
plex)
|
|
# Plex uses host networking — Seerr needs the host IP
|
|
media_server_type="1"
|
|
media_server_host="host.docker.internal"
|
|
media_server_port=32400
|
|
;;
|
|
esac
|
|
|
|
if [ -z "$media_server_type" ]; then
|
|
log_step_fail "Seerr: unsupported media service '${MEDIA_SERVICE:-}'"
|
|
return 1
|
|
fi
|
|
|
|
# Step 4: Authenticate against the media server via Seerr's setup endpoint
|
|
# This creates the admin user in Seerr and stores the media server connection
|
|
local auth_payload
|
|
if [ "${MEDIA_SERVICE:-}" = "plex" ]; then
|
|
# Plex requires an authToken from Plex.tv OAuth — cannot automate without user interaction
|
|
log_step "Seerr: Plex detected — manual setup required via web UI at http://${API_HOST}:${seerr_port}"
|
|
_cfg_log_info " Log in with your Plex account to complete Seerr setup"
|
|
return 0
|
|
fi
|
|
|
|
# Jellyfin/Emby: use credential-based auth (both use /auth/jellyfin endpoint)
|
|
local auth_endpoint="jellyfin"
|
|
|
|
auth_payload=$(jq -nc \
|
|
--arg user "$AUTH_USERNAME" \
|
|
--arg pass "$AUTH_PASSWORD" \
|
|
--arg host "$media_server_host" \
|
|
--argjson port "$media_server_port" \
|
|
--argjson stype "$media_server_type" \
|
|
'{username:$user,password:$pass,hostname:$host,port:$port,useSsl:false,urlBase:"",email:"",serverType:$stype}')
|
|
|
|
_cfg_log_info "Seerr: authenticating against ${MEDIA_SERVICE:-jellyfin}..."
|
|
|
|
# Use -s (not -sf) so we can capture error responses too
|
|
local auth_response
|
|
auth_response=$(curl -s --connect-timeout 10 -X POST \
|
|
-H "Content-Type: application/json" \
|
|
-d "$auth_payload" \
|
|
"http://${API_HOST}:${seerr_port}/api/v1/auth/${auth_endpoint}" 2>/dev/null || echo "")
|
|
|
|
if [ -z "$auth_response" ]; then
|
|
log_step_fail "Seerr: failed to authenticate (empty response)"
|
|
_cfg_log_info " Complete setup manually at http://${API_HOST}:${seerr_port}"
|
|
return 1
|
|
fi
|
|
|
|
# "already configured" is success when re-running against an initialized Seerr
|
|
local auth_error
|
|
auth_error=$(echo "$auth_response" | jq -r '.error // ""' 2>/dev/null || echo "")
|
|
if [ -n "$auth_error" ]; then
|
|
if echo "$auth_error" | grep -qi "already configured"; then
|
|
log_step "Seerr: media server already connected"
|
|
else
|
|
local auth_message
|
|
auth_message=$(echo "$auth_response" | jq -r '.message // .error // "unknown error"' 2>/dev/null || echo "unknown error")
|
|
log_step_fail "Seerr: authentication failed — ${auth_message}"
|
|
_cfg_log_info " Complete setup manually at http://${API_HOST}:${seerr_port}"
|
|
return 1
|
|
fi
|
|
else
|
|
local auth_id
|
|
auth_id=$(echo "$auth_response" | jq -r '.id // ""' 2>/dev/null || echo "")
|
|
if [ -n "$auth_id" ]; then
|
|
log_step "Seerr: authenticated against ${MEDIA_SERVICE:-jellyfin} (admin user created)"
|
|
else
|
|
log_step_fail "Seerr: authentication returned unexpected response"
|
|
_cfg_log_info " Complete setup manually at http://${API_HOST}:${seerr_port}"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# Step 5: session cookie for subsequent API calls
|
|
local seerr_cookie_jar="/tmp/seerr_cookie_jar_$$_${seerr_port}"
|
|
|
|
if ! seerr_get_cookie "$seerr_cookie_jar" "true"; then
|
|
if ! seerr_get_cookie "$seerr_cookie_jar" "false"; then
|
|
log_step_fail "Seerr: failed to get session cookie"
|
|
rm -f "$seerr_cookie_jar"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# Step 6: mark setup complete
|
|
local init_code
|
|
init_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 -X POST -b "$seerr_cookie_jar" \
|
|
-H "Content-Type: application/json" \
|
|
"http://${API_HOST}:${seerr_port}/api/v1/settings/initialize" 2>/dev/null || echo "000")
|
|
|
|
if [ "$init_code" -ge 200 ] && [ "$init_code" -lt 300 ]; then
|
|
log_step "Seerr: setup completed successfully"
|
|
else
|
|
log_step_fail "Seerr: failed to finalize setup (HTTP $init_code)"
|
|
_cfg_log_info " Complete setup manually at http://${API_HOST}:${seerr_port}"
|
|
rm -f "$seerr_cookie_jar"
|
|
return 1
|
|
fi
|
|
|
|
echo "Connecting Seerr services" >&2
|
|
|
|
# Connect Radarr and Sonarr using shared helper
|
|
local connect_errors=0
|
|
local is_4k
|
|
is_4k=$(quality_seerr_is_4k)
|
|
if [ -n "$RADARR_API_KEY" ]; then
|
|
if ! seerr_connect_service "radarr" 7878 "$RADARR_API_KEY" "lookup_radarr_profile" \
|
|
"/data/media/movies" "$is_4k" "$seerr_cookie_jar"; then
|
|
connect_errors=$((connect_errors + 1))
|
|
fi
|
|
fi
|
|
|
|
if [ -n "$SONARR_API_KEY" ]; then
|
|
if ! seerr_connect_service "sonarr" 8989 "$SONARR_API_KEY" "lookup_sonarr_profile" \
|
|
"/data/media/tv" "$is_4k" "$seerr_cookie_jar"; then
|
|
connect_errors=$((connect_errors + 1))
|
|
fi
|
|
fi
|
|
|
|
echo >&2
|
|
rm -f "$seerr_cookie_jar"
|
|
|
|
if [ $connect_errors -gt 0 ]; then
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Seerr has no separate password — it authenticates against Jellyfin/Emby.
|
|
seerr_verify_login() {
|
|
local seerr_cookie_jar="/tmp/seerr_cookie_jar_verify_$$"
|
|
if seerr_get_cookie "$seerr_cookie_jar" "false" || seerr_get_cookie "$seerr_cookie_jar" "true"; then
|
|
rm -f "$seerr_cookie_jar"
|
|
log_step "Seerr: session works with the new Jellyfin/Emby password"
|
|
return 0
|
|
fi
|
|
rm -f "$seerr_cookie_jar"
|
|
log_step_fail "Seerr: could not open a session (it uses the Jellyfin/Emby login)"
|
|
return 1
|
|
}
|