mirror of
https://github.com/soulis-1256/assemblrr.git
synced 2026-08-31 01:44:29 +00:00
Fix quality profile setup and split Recyclarr into include packs
Always sync HD/UHD (and Sonarr WEB) as include packs, name them with an assemblrr prefix, soften blocking CF scores, zero quality minSize, relax minimumSeeders, and update Seerr on reconfigure. Document TRaSH guide drift and tighten comments and uninstall messaging.
This commit is contained in:
parent
a381de1f8f
commit
bd126b704e
19 changed files with 439 additions and 410 deletions
246
lib/arr.sh
246
lib/arr.sh
|
|
@ -48,14 +48,13 @@ set_arr_auth() {
|
|||
|
||||
# --- qBittorrent helpers ---
|
||||
|
||||
# Set qBittorrent credentials and save path using the temp password from container logs
|
||||
# Called once before configuring any *arr service.
|
||||
# On re-runs, tries the final credentials first to avoid triggering brute-force bans.
|
||||
# Set qBittorrent WebUI credentials and default save paths.
|
||||
# Prefer final credentials when already configured (avoids brute-force lockouts).
|
||||
qbit_set_credentials() {
|
||||
local qbit_port=8081
|
||||
local qbit_cookie_jar="/tmp/qb_cookie_jar_init_$$_${qbit_port}"
|
||||
|
||||
# Try logging in with the final credentials first (already set from a previous run)
|
||||
# Idempotent: succeed if final credentials already work
|
||||
if [ -n "$AUTH_USERNAME" ] && [ -n "$AUTH_PASSWORD" ]; then
|
||||
curl -s --connect-timeout 5 -c "$qbit_cookie_jar" \
|
||||
-H "Referer: http://${API_HOST}:${qbit_port}" \
|
||||
|
|
@ -64,7 +63,6 @@ qbit_set_credentials() {
|
|||
local qbit_sid
|
||||
qbit_sid=$(qbit_cookie_sid "$qbit_cookie_jar")
|
||||
if [ -n "$qbit_sid" ]; then
|
||||
# Credentials already set — ensure save path is configured
|
||||
curl -s --connect-timeout 5 \
|
||||
"http://${API_HOST}:${qbit_port}/api/v2/app/setPreferences" \
|
||||
-b "$qbit_cookie_jar" \
|
||||
|
|
@ -77,7 +75,7 @@ qbit_set_credentials() {
|
|||
fi
|
||||
fi
|
||||
|
||||
# Credentials not set yet — use temp password from container logs
|
||||
# First-time setup: temporary password from container logs
|
||||
local wait_time=0
|
||||
local max_wait=60
|
||||
|
||||
|
|
@ -116,7 +114,7 @@ qbit_set_credentials() {
|
|||
return 1
|
||||
}
|
||||
|
||||
# Create a qBittorrent category with a save path
|
||||
# Create or update a qBittorrent category with a save path
|
||||
qbit_create_category() {
|
||||
local service_name="$1"
|
||||
local category="$2"
|
||||
|
|
@ -131,13 +129,20 @@ qbit_create_category() {
|
|||
local qbit_sid
|
||||
qbit_sid=$(qbit_cookie_sid "$qbit_cookie_jar")
|
||||
if [ -n "$qbit_sid" ]; then
|
||||
# createCategory + editCategory so savePath is set whether the category is new or existing
|
||||
curl -s --connect-timeout 10 \
|
||||
"http://${API_HOST}:${qbit_port}/api/v2/torrents/createCategory" \
|
||||
-b "$qbit_cookie_jar" \
|
||||
-H "Referer: http://${API_HOST}:${qbit_port}" \
|
||||
-d "category=${category}&savePath=${save_path}" \
|
||||
2>/dev/null >/dev/null || true
|
||||
log_step "${service_name}: created qBittorrent '${category}' category (${save_path})"
|
||||
curl -s --connect-timeout 10 \
|
||||
"http://${API_HOST}:${qbit_port}/api/v2/torrents/editCategory" \
|
||||
-b "$qbit_cookie_jar" \
|
||||
-H "Referer: http://${API_HOST}:${qbit_port}" \
|
||||
-d "category=${category}&savePath=${save_path}" \
|
||||
2>/dev/null >/dev/null || true
|
||||
log_step "${service_name}: qBittorrent '${category}' category → ${save_path}"
|
||||
else
|
||||
log_step_fail "${service_name}: could not login to qBittorrent to create categories"
|
||||
fi
|
||||
|
|
@ -184,16 +189,16 @@ configure_arr_service() {
|
|||
log_step "${service_name}: root folder ${root_path} already exists"
|
||||
fi
|
||||
|
||||
# 1b. Remove stale auto-detected root folders
|
||||
# Keep only the configured root folder
|
||||
local all_root_folders
|
||||
all_root_folders=$(api_get "$port" "/api/v3/rootfolder" "$apikey")
|
||||
local stale_ids
|
||||
stale_ids=$(echo "$all_root_folders" | jq -r --arg rp "$root_path" '.[] | select(.path != $rp) | .id' 2>/dev/null || echo "")
|
||||
local extra_ids
|
||||
extra_ids=$(echo "$all_root_folders" | jq -r --arg rp "$root_path" '.[] | select(.path != $rp) | .id' 2>/dev/null || echo "")
|
||||
|
||||
for stale_id in $stale_ids; do
|
||||
if [ -n "$stale_id" ]; then
|
||||
api_delete "$port" "/api/v3/rootfolder/${stale_id}" "$apikey" >/dev/null || true
|
||||
log_step "${service_name}: removed stale root folder (id: ${stale_id})"
|
||||
for extra_id in $extra_ids; do
|
||||
if [ -n "$extra_id" ]; then
|
||||
api_delete "$port" "/api/v3/rootfolder/${extra_id}" "$apikey" >/dev/null || true
|
||||
log_step "${service_name}: removed extra root folder (id: ${extra_id})"
|
||||
fi
|
||||
done
|
||||
|
||||
|
|
@ -296,6 +301,81 @@ configure_arr_service() {
|
|||
set_arr_auth "$service_name" "$port" "$apikey"
|
||||
}
|
||||
|
||||
# --- Quality sizes & default profiles ---
|
||||
|
||||
# Set minSize=0 on every quality definition (no minimum file-size filter).
|
||||
# Values live in the *arr DB; they only change when written via this API.
|
||||
relax_quality_sizes() {
|
||||
local service_name="$1"
|
||||
local port="$2"
|
||||
local apikey="$3"
|
||||
|
||||
local defs
|
||||
defs=$(api_get "$port" "/api/v3/qualitydefinition" "$apikey")
|
||||
if [ -z "$defs" ] || [ "$defs" = "[]" ]; then
|
||||
log_step_fail "${service_name}: no quality definitions to update"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Only minSize is cleared; leave max/preferred alone (bulk update is picky about nulls).
|
||||
local payload
|
||||
payload=$(echo "$defs" | jq '[.[] | .minSize = 0]' 2>/dev/null || echo "")
|
||||
if [ -z "$payload" ]; then
|
||||
log_step_fail "${service_name}: failed to build quality definition payload"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local result
|
||||
result=$(api_put "$port" "/api/v3/qualitydefinition/update" "$apikey" "$payload")
|
||||
if echo "$result" | jq -e 'type == "array"' >/dev/null 2>&1; then
|
||||
log_step "${service_name}: quality minSize set to 0"
|
||||
return 0
|
||||
fi
|
||||
# Fallback: one PUT per definition
|
||||
local ok=0 total=0
|
||||
while IFS= read -r row; do
|
||||
[ -z "$row" ] && continue
|
||||
total=$((total + 1))
|
||||
local id
|
||||
id=$(echo "$row" | jq -r '.id // empty')
|
||||
[ -z "$id" ] && continue
|
||||
if api_put "$port" "/api/v3/qualitydefinition/${id}" "$apikey" "$row" >/dev/null 2>&1; then
|
||||
ok=$((ok + 1))
|
||||
fi
|
||||
done < <(echo "$payload" | jq -c '.[]' 2>/dev/null || true)
|
||||
|
||||
if [ "$ok" -gt 0 ]; then
|
||||
log_step "${service_name}: quality minSize set to 0 (${ok}/${total})"
|
||||
return 0
|
||||
fi
|
||||
log_step_fail "${service_name}: failed to update quality definitions"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Record the preferred quality profile id/name for this install (setup choice).
|
||||
set_default_quality_profile() {
|
||||
local service_name="$1"
|
||||
local port="$2"
|
||||
local apikey="$3"
|
||||
local profile_func="$4"
|
||||
|
||||
local profile profile_id
|
||||
profile=$($profile_func "$apikey")
|
||||
profile_id="${profile%%:*}"
|
||||
local profile_name="${profile##*:}"
|
||||
|
||||
if [ -z "$profile_id" ] || [ "$profile_id" = "1" ] && [ "$profile_name" = "Any" ]; then
|
||||
log_step "${service_name}: preferred quality profile → Any"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -n "${INSTALL_DIR:-}" ]; then
|
||||
mkdir -p "$INSTALL_DIR/config"
|
||||
echo "${profile_id}:${profile_name}" > "$INSTALL_DIR/config/.${service_name,,}-default-quality-profile" 2>/dev/null || true
|
||||
fi
|
||||
log_step "${service_name}: preferred quality profile → ${profile_name} (id ${profile_id})"
|
||||
}
|
||||
|
||||
# --- Quality profile lookup ---
|
||||
|
||||
lookup_radarr_profile() {
|
||||
|
|
@ -308,27 +388,42 @@ lookup_radarr_profile() {
|
|||
return
|
||||
fi
|
||||
|
||||
# Recyclarr creates profiles with names like "Ultra-HD" or "HD Bluray + WEB"
|
||||
# Match by keyword since the exact name depends on TRaSH Guide version
|
||||
# Default Radarr profiles to skip: Any, SD, HD-720p, HD-1080p, HD - 720p/1080p
|
||||
local target_keyword
|
||||
# Prefer assemblrr-named Recyclarr profiles; fall back to stock *arr names.
|
||||
local target_name=""
|
||||
local stock_fallback=""
|
||||
|
||||
if [ "${SEERR_IS_4K:-false}" = "true" ]; then
|
||||
target_keyword="Ultra"
|
||||
target_name="assemblrr UHD Bluray + WEB"
|
||||
stock_fallback="Ultra-HD"
|
||||
elif [ "${SEERR_DEFAULT_PROFILE:-1}" != "1" ]; then
|
||||
target_keyword="Bluray + WEB"
|
||||
target_name="assemblrr HD Bluray + WEB"
|
||||
stock_fallback="HD-1080p"
|
||||
else
|
||||
echo "1:Any"
|
||||
return
|
||||
fi
|
||||
|
||||
local matched
|
||||
matched=$(echo "$profiles_json" | jq -r --arg kw "$target_keyword" '.[] | select(.name | contains($kw)) | select(.name as $n | ["Any","SD","HD-720p","HD-1080p","HD - 720p/1080p"] | index($n) | not) | "\(.id):\(.name)"' 2>/dev/null | head -1 || echo "")
|
||||
matched=$(echo "$profiles_json" | jq -r --arg name "$target_name" '
|
||||
.[] | select(.name == $name) | "\(.id):\(.name)"
|
||||
' 2>/dev/null | head -1 || echo "")
|
||||
|
||||
if [ -n "$matched" ]; then
|
||||
echo "$matched"
|
||||
else
|
||||
echo "1:Any"
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -n "$stock_fallback" ]; then
|
||||
matched=$(echo "$profiles_json" | jq -r --arg name "$stock_fallback" '
|
||||
.[] | select(.name == $name) | "\(.id):\(.name)"
|
||||
' 2>/dev/null | head -1 || echo "")
|
||||
if [ -n "$matched" ]; then
|
||||
echo "$matched"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "1:Any"
|
||||
}
|
||||
|
||||
lookup_sonarr_profile() {
|
||||
|
|
@ -337,28 +432,43 @@ lookup_sonarr_profile() {
|
|||
profiles_json=$(api_get "8989" "/api/v3/qualityprofile" "$apikey")
|
||||
|
||||
if [ -z "$profiles_json" ]; then
|
||||
echo "1:Default"
|
||||
echo "1:Any"
|
||||
return
|
||||
fi
|
||||
|
||||
# Recyclarr creates profiles with names like "Ultra-HD" or "WEB-1080p"
|
||||
# Match by keyword since the exact name depends on TRaSH Guide version
|
||||
# Default Sonarr profiles to skip: Any, SD, HD-720p, HD-1080p, HD - 720p/1080p
|
||||
local target_keyword
|
||||
if [ "${SEERR_IS_4K:-false}" = "true" ]; then
|
||||
target_keyword="Ultra"
|
||||
else
|
||||
target_keyword="WEB"
|
||||
fi
|
||||
# Default TV profile is assemblrr WEB-1080p (movies 4K choice does not change this).
|
||||
local target_name="assemblrr WEB-1080p"
|
||||
local stock_fallback="HD-1080p"
|
||||
|
||||
local matched
|
||||
matched=$(echo "$profiles_json" | jq -r --arg kw "$target_keyword" '.[] | select(.name | contains($kw)) | select(.name as $n | ["Any","SD","HD-720p","HD-1080p","HD - 720p/1080p"] | index($n) | not) | "\(.id):\(.name)"' 2>/dev/null | head -1 || echo "")
|
||||
matched=$(echo "$profiles_json" | jq -r --arg name "$target_name" '
|
||||
.[] | select(.name == $name) | "\(.id):\(.name)"
|
||||
' 2>/dev/null | head -1 || echo "")
|
||||
|
||||
if [ -n "$matched" ]; then
|
||||
echo "$matched"
|
||||
else
|
||||
echo "1:Default"
|
||||
return
|
||||
fi
|
||||
|
||||
matched=$(echo "$profiles_json" | jq -r '
|
||||
.[] | select(.name | test("^assemblrr.*WEB.*1080"; "i")) | "\(.id):\(.name)"
|
||||
' 2>/dev/null | head -1 || echo "")
|
||||
if [ -n "$matched" ]; then
|
||||
echo "$matched"
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -n "$stock_fallback" ]; then
|
||||
matched=$(echo "$profiles_json" | jq -r --arg name "$stock_fallback" '
|
||||
.[] | select(.name == $name) | "\(.id):\(.name)"
|
||||
' 2>/dev/null | head -1 || echo "")
|
||||
if [ -n "$matched" ]; then
|
||||
echo "$matched"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "1:Any"
|
||||
}
|
||||
|
||||
# --- Radarr configuration ---
|
||||
|
|
@ -374,6 +484,58 @@ configure_radarr() {
|
|||
|
||||
# --- Prowlarr configuration ---
|
||||
|
||||
# Set minimumSeeders=0 on Prowlarr app profiles and *arr indexers.
|
||||
# Peer counts from indexers are often incomplete; requiring seeders>=1 blocks valid grabs.
|
||||
relax_minimum_seeders() {
|
||||
local prowlarr_key="$1"
|
||||
local radarr_key="${2:-}"
|
||||
local sonarr_key="${3:-}"
|
||||
|
||||
local profiles
|
||||
profiles=$(api_get "9696" "/api/v1/appprofile" "$prowlarr_key")
|
||||
if [ -n "$profiles" ] && [ "$profiles" != "[]" ]; then
|
||||
local profile_id profile_payload
|
||||
while IFS= read -r row; do
|
||||
[ -z "$row" ] && continue
|
||||
profile_id=$(echo "$row" | jq -r '.id // empty')
|
||||
[ -z "$profile_id" ] && continue
|
||||
profile_payload=$(echo "$row" | jq '.minimumSeeders = 0' 2>/dev/null || echo "")
|
||||
[ -z "$profile_payload" ] && continue
|
||||
if api_put "9696" "/api/v1/appprofile/${profile_id}" "$prowlarr_key" "$profile_payload" >/dev/null 2>&1; then
|
||||
log_step "Prowlarr: app profile '${profile_id}' minimumSeeders → 0"
|
||||
fi
|
||||
done < <(echo "$profiles" | jq -c '.[]' 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
local port key name
|
||||
for port_key_name in "7878:${radarr_key}:Radarr" "8989:${sonarr_key}:Sonarr"; do
|
||||
port="${port_key_name%%:*}"
|
||||
rest="${port_key_name#*:}"
|
||||
key="${rest%%:*}"
|
||||
name="${rest#*:}"
|
||||
[ -z "$key" ] && continue
|
||||
|
||||
local indexers
|
||||
indexers=$(api_get "$port" "/api/v3/indexer" "$key")
|
||||
[ -z "$indexers" ] || [ "$indexers" = "[]" ] && continue
|
||||
|
||||
while IFS= read -r idx; do
|
||||
[ -z "$idx" ] && continue
|
||||
local idx_id idx_payload
|
||||
idx_id=$(echo "$idx" | jq -r '.id // empty')
|
||||
[ -z "$idx_id" ] && continue
|
||||
idx_payload=$(echo "$idx" | jq '
|
||||
.fields = [.fields[] |
|
||||
if .name == "minimumSeeders" then .value = 0 else . end
|
||||
]
|
||||
' 2>/dev/null || echo "")
|
||||
[ -z "$idx_payload" ] && continue
|
||||
api_put "$port" "/api/v3/indexer/${idx_id}" "$key" "$idx_payload" >/dev/null 2>&1 || true
|
||||
done < <(echo "$indexers" | jq -c '.[]' 2>/dev/null || true)
|
||||
log_step "${name}: indexer minimumSeeders → 0"
|
||||
done
|
||||
}
|
||||
|
||||
configure_prowlarr() {
|
||||
local apikey="$1"
|
||||
local radarr_apikey="$2"
|
||||
|
|
@ -436,8 +598,8 @@ configure_prowlarr() {
|
|||
fi
|
||||
fi
|
||||
|
||||
# 2. Add public indexers
|
||||
echo "Adding ${#PUBLIC_INDEXERS[@]} indexer(s) to Prowlarr" >&2
|
||||
# 2. Add selected indexers
|
||||
echo "Adding ${#SELECTED_INDEXERS[@]} indexer(s) to Prowlarr" >&2
|
||||
local indexer_schemas
|
||||
indexer_schemas=$(api_get "9696" "/api/v1/indexer/schema" "$apikey")
|
||||
|
||||
|
|
@ -452,7 +614,7 @@ configure_prowlarr() {
|
|||
local existing_names
|
||||
existing_names=$(jq_json_list_values "$existing_indexers" "name")
|
||||
|
||||
for indexer_name in ${PUBLIC_INDEXERS[@]+"${PUBLIC_INDEXERS[@]}"}; do
|
||||
for indexer_name in ${SELECTED_INDEXERS[@]+"${SELECTED_INDEXERS[@]}"}; do
|
||||
# Skip if already added
|
||||
if echo "$existing_names" | grep -q -F -x "$indexer_name"; then
|
||||
log_step "Prowlarr: ${indexer_name} indexer already exists"
|
||||
|
|
@ -502,7 +664,9 @@ configure_prowlarr() {
|
|||
echo >&2
|
||||
log_success "Indexers added to Prowlarr!" >&2
|
||||
|
||||
# 3. Set authentication
|
||||
relax_minimum_seeders "$apikey" "$radarr_apikey" "${sonarr_apikey:-}"
|
||||
|
||||
# 4. Set authentication
|
||||
set_arr_auth "Prowlarr" "9696" "$apikey" "v1"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ prepare_install_dirs() {
|
|||
"scripts"
|
||||
)
|
||||
|
||||
# Reclaim install root if a previous run left it root-owned
|
||||
# Ensure install root is owned by the target user
|
||||
if [ -d "$install_dir" ] && [ ! -w "$install_dir" ]; then
|
||||
ensure_owned "$install_dir" "$(id -u)" "$(id -g)"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
#!/bin/bash
|
||||
# fzf-based TUI for selections (Prowlarr indexers, Jellyfin languages)
|
||||
# Provides reusable fzf selection patterns for assemblrr.
|
||||
# Sets PUBLIC_INDEXERS array for indexers.
|
||||
# Sets SELECTED_INDEXERS array for Prowlarr.
|
||||
# Usage: source lib/fzf-tui.sh; configure_indexers "$api_key"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PUBLIC_INDEXERS=()
|
||||
SELECTED_INDEXERS=()
|
||||
|
||||
# --- TTY detection (reused across functions) ---
|
||||
_fzf_has_tty() {
|
||||
|
|
@ -73,8 +73,8 @@ fzf_single_select() {
|
|||
}
|
||||
|
||||
# --- Generic multi-select fzf (for indexers) ---
|
||||
# Args: $1 = data_source (jq expression or curl), $2 = awk filter
|
||||
# Sets PUBLIC_INDEXERS array
|
||||
# Args: $1 = tab-separated list lines
|
||||
# Sets SELECTED_INDEXERS array
|
||||
fzf_multi_select() {
|
||||
local data="$1"
|
||||
local has_tty=false
|
||||
|
|
@ -104,20 +104,20 @@ fzf_multi_select() {
|
|||
|
||||
if [ -n "$selected" ]; then
|
||||
while IFS= read -r idx; do
|
||||
[ -n "$idx" ] && PUBLIC_INDEXERS+=("$idx")
|
||||
[ -n "$idx" ] && SELECTED_INDEXERS+=("$idx")
|
||||
done <<< "$selected"
|
||||
echo "Selected ${#PUBLIC_INDEXERS[@]} indexer(s): $(IFS=,; echo "${PUBLIC_INDEXERS[*]}")" >&2
|
||||
echo "Selected ${#SELECTED_INDEXERS[@]} indexer(s): $(IFS=,; echo "${SELECTED_INDEXERS[*]}")" >&2
|
||||
else
|
||||
echo "No indexers selected." >&2
|
||||
fi
|
||||
else
|
||||
# No TTY or no fzf — show top public indexers and prompt
|
||||
echo "No interactive terminal available — listing popular public indexers." >&2
|
||||
echo "$data" | jq -r 'sort_by(.name | ascii_downcase) | .[0:20][] | " \(.name)"' 2>/dev/null >&2
|
||||
# No TTY or no fzf — list options and prompt
|
||||
echo "No interactive terminal available — listing available indexers." >&2
|
||||
echo "$data" | cut -f1 | head -20 | sed 's/^/ /' >&2
|
||||
echo "Enter indexer names separated by spaces (or blank to skip):" >&2
|
||||
read -r input_line || true
|
||||
for idx in $input_line; do
|
||||
PUBLIC_INDEXERS+=("$idx")
|
||||
SELECTED_INDEXERS+=("$idx")
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
|
|
|||
144
lib/seerr.sh
144
lib/seerr.sh
|
|
@ -7,8 +7,9 @@
|
|||
|
||||
set -euo pipefail
|
||||
|
||||
# --- Shared helper: connect a service (Radarr/Sonarr) to Seerr ---
|
||||
# Usage: seerr_connect_service <service_type> <port> <api_key> <profile_func> <active_dir> <is_4k> <cookie_jar>
|
||||
# --- 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"
|
||||
|
|
@ -18,6 +19,7 @@ seerr_connect_service() {
|
|||
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"
|
||||
|
|
@ -29,13 +31,12 @@ seerr_connect_service() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
# Look up quality profile from the service (created by Recyclarr)
|
||||
local profile
|
||||
profile=$($profile_func "$api_key")
|
||||
local profile_id="${profile%%:*}"
|
||||
local profile_name="${profile##*:}"
|
||||
|
||||
# Build the service payload
|
||||
# id is read-only on PUT — omit it from the body
|
||||
local payload
|
||||
payload=$(cat <<EOF
|
||||
{
|
||||
|
|
@ -62,15 +63,29 @@ EOF
|
|||
payload=$(echo "$payload" | jq '.enableSeasonFolders = true' 2>/dev/null || echo "$payload")
|
||||
fi
|
||||
|
||||
# POST to Seerr
|
||||
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^}"
|
||||
log_step "Seerr: connected ${service_type^} (profile: ${profile_name})"
|
||||
return 0
|
||||
else
|
||||
log_step_fail "Seerr: failed to connect ${service_type} (HTTP $http_code)"
|
||||
|
|
@ -99,6 +114,24 @@ seerr_service_connected() {
|
|||
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
|
||||
|
|
@ -158,42 +191,60 @@ EOF
|
|||
|
||||
configure_recyclarr() {
|
||||
local recyclarr_config_dir="$INSTALL_DIR/config/recyclarr"
|
||||
mkdir -p "$recyclarr_config_dir"
|
||||
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
|
||||
|
||||
# Export variables for envsubst
|
||||
# Only substitute known vars so $schema in YAML comments is left alone
|
||||
export RADARR_API_KEY SONARR_API_KEY
|
||||
|
||||
# Pick the right template based on user's 4K choice
|
||||
local template_file="recyclarr-full_hd.yml" # default: 1080p
|
||||
local profile_label="HD Bluray + WEB"
|
||||
local default_label="assemblrr HD Bluray + WEB"
|
||||
if [ "${SEERR_IS_4K}" = "true" ]; then
|
||||
template_file="recyclarr-ultra_hd.yml" # 4K
|
||||
profile_label="UHD Bluray + WEB"
|
||||
default_label="assemblrr UHD Bluray + WEB"
|
||||
fi
|
||||
|
||||
if [ -f "$INSTALL_DIR/templates/${template_file}" ]; then
|
||||
if envsubst < "$INSTALL_DIR/templates/${template_file}" > "$recyclarr_config_dir/recyclarr.yml"; then
|
||||
log_step "Recyclarr: generated config (${profile_label} profile)"
|
||||
else
|
||||
log_step_fail "Recyclarr: failed to write config (${recyclarr_config_dir}/recyclarr.yml)"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_step_fail "Recyclarr: template ${template_file} not found"
|
||||
if [ ! -f "$template_root/recyclarr.yml" ]; then
|
||||
log_step_fail "Recyclarr: template root not found (${template_root}/recyclarr.yml)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Trigger a sync
|
||||
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})"
|
||||
|
||||
_cfg_log_info "Recyclarr: syncing TRaSH Guides to Radarr and Sonarr... this might take a moment"
|
||||
if docker exec recyclarr recyclarr sync > /dev/null 2>&1; then
|
||||
local sync_out
|
||||
if sync_out=$(docker exec recyclarr recyclarr sync 2>&1); then
|
||||
log_step "Recyclarr: synced Custom Formats and Quality Profiles successfully"
|
||||
else
|
||||
log_step_fail "Recyclarr: failed to sync to Radarr/Sonarr (check docker logs recyclarr)"
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -245,31 +296,21 @@ configure_seerr() {
|
|||
|
||||
echo "Connecting Seerr services" >&2
|
||||
|
||||
# Connect Radarr if not already connected
|
||||
if [ -n "$RADARR_API_KEY" ]; then
|
||||
local radarr_count
|
||||
radarr_count=$(seerr_service_connected "radarr" "$seerr_cookie_jar") || radarr_count=0
|
||||
if [ "$radarr_count" -eq 0 ]; then
|
||||
if ! seerr_connect_service "radarr" 7878 "$RADARR_API_KEY" "lookup_radarr_profile" \
|
||||
"/data/media/movies" "${SEERR_IS_4K:-false}" "$seerr_cookie_jar"; then
|
||||
log_step_fail "Seerr: failed to connect Radarr (re-run to retry)"
|
||||
fi
|
||||
else
|
||||
log_step "Seerr: Radarr already connected"
|
||||
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" "${SEERR_IS_4K:-false}" "$seerr_cookie_jar" "$radarr_id"; then
|
||||
log_step_fail "Seerr: failed to configure Radarr (re-run to retry)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Connect Sonarr if not already connected
|
||||
if [ -n "$SONARR_API_KEY" ]; then
|
||||
local sonarr_count
|
||||
sonarr_count=$(seerr_service_connected "sonarr" "$seerr_cookie_jar") || sonarr_count=0
|
||||
if [ "$sonarr_count" -eq 0 ]; then
|
||||
if ! seerr_connect_service "sonarr" 8989 "$SONARR_API_KEY" "lookup_sonarr_profile" \
|
||||
"/data/media/tv" "false" "$seerr_cookie_jar"; then
|
||||
log_step_fail "Seerr: failed to connect Sonarr (re-run to retry)"
|
||||
fi
|
||||
else
|
||||
log_step "Seerr: Sonarr already connected"
|
||||
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" "false" "$seerr_cookie_jar" "$sonarr_id"; then
|
||||
log_step_fail "Seerr: failed to configure Sonarr (re-run to retry)"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
|
@ -350,8 +391,7 @@ EOF
|
|||
return 1
|
||||
fi
|
||||
|
||||
# Check for error in response — "hostname already configured" means media server
|
||||
# was connected by a previous run, which is fine (admin user already exists)
|
||||
# "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
|
||||
|
|
@ -365,7 +405,6 @@ EOF
|
|||
return 1
|
||||
fi
|
||||
else
|
||||
# Successful auth — verify admin user was created
|
||||
local auth_id
|
||||
auth_id=$(echo "$auth_response" | jq -r '.id // ""' 2>/dev/null || echo "")
|
||||
if [ -n "$auth_id" ]; then
|
||||
|
|
@ -377,12 +416,10 @@ EOF
|
|||
fi
|
||||
fi
|
||||
|
||||
# Step 5: Get a session cookie for subsequent API calls
|
||||
# Step 5: session cookie for subsequent API calls
|
||||
local seerr_cookie_jar="/tmp/seerr_cookie_jar_$$_${seerr_port}"
|
||||
|
||||
# Get session cookie (with hostname for initial setup)
|
||||
if ! seerr_get_cookie "$seerr_cookie_jar" "true"; then
|
||||
# Retry with login-only if first attempt fails (hostname already configured)
|
||||
if ! seerr_get_cookie "$seerr_cookie_jar" "false"; then
|
||||
log_step_fail "Seerr: failed to get session cookie"
|
||||
rm -f "$seerr_cookie_jar"
|
||||
|
|
@ -390,10 +427,7 @@ EOF
|
|||
fi
|
||||
fi
|
||||
|
||||
# Note: Library sync is skipped here — Jellyfin hasn't indexed anything yet on first setup.
|
||||
# Seerr will auto-sync libraries periodically (every 24h) once content exists.
|
||||
|
||||
# Step 6: Initialize Seerr (mark setup as complete)
|
||||
# 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" \
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue