From e528d23c68daa50277cfb6ed1d14bef351feb660 Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Sat, 18 Jul 2026 22:26:07 -0400 Subject: [PATCH] =?UTF-8?q?Add=20media-server=20auto-detection=20=E2=80=94?= =?UTF-8?q?=20pick=20Emby=20or=20Jellyfin=20by=20actual=20running=20state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common.sh | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/common.sh b/common.sh index 5e71bd4..21eb14c 100755 --- a/common.sh +++ b/common.sh @@ -439,6 +439,9 @@ is_in_list() { # EMBY_CONTAINER ← HOST*_EMBY_CONTAINER # EMBY_URL ← HOST*_EMBY_URL # EMBY_API_KEY ← HOST*_EMBY_API_KEY +# JELLYFIN_CONTAINER ← HOST*_JELLYFIN_CONTAINER +# JELLYFIN_URL ← HOST*_JELLYFIN_URL +# JELLYFIN_API_KEY ← HOST*_JELLYFIN_API_KEY # GITEA_API_TOKEN ← HOST*_GITEA_API_TOKEN # # Arr-specific vars set by detect_hosts() when needed by arr scripts: @@ -531,6 +534,13 @@ detect_hosts() { EMBY_API_KEY_VAR="${MY_ID}_EMBY_API_KEY" EMBY_API_KEY="${!EMBY_API_KEY_VAR:-}" + JELLYFIN_CONTAINER_VAR="${MY_ID}_JELLYFIN_CONTAINER" + JELLYFIN_CONTAINER="${!JELLYFIN_CONTAINER_VAR:-}" + JELLYFIN_URL_VAR="${MY_ID}_JELLYFIN_URL" + JELLYFIN_URL="${!JELLYFIN_URL_VAR:-}" + JELLYFIN_API_KEY_VAR="${MY_ID}_JELLYFIN_API_KEY" + JELLYFIN_API_KEY="${!JELLYFIN_API_KEY_VAR:-}" + GITEA_API_TOKEN_VAR="${MY_ID}_GITEA_API_TOKEN" GITEA_API_TOKEN="${!GITEA_API_TOKEN_VAR:-}" @@ -1363,6 +1373,77 @@ emby_api() { echo "$body" } +# ── Media Server Detection ──────────────────────────────────────────────────── +# Picks whichever media server is actually running right now, so callers don't have to +# hardcode Emby or Jellyfin. Emby is preferred when both are up (it's the primary player on +# every host; Jellyfin is mainly kept around for its working ffprobe binary — see +# HOST*_FFPROBE_CONTAINER). Either container being merely *configured* in host*.conf doesn't +# count — this checks Docker's actual running state, so a host with Jellyfin installed but +# stopped correctly falls through to Emby (or to nothing, if neither is up). +# +# Requires detect_hosts() to have already run (needs EMBY_*/JELLYFIN_* aliases). +# Sets: MEDIA_SERVER ("Emby"|"Jellyfin"|""), MEDIA_SERVER_URL, MEDIA_SERVER_API_KEY. +# Returns 1 and leaves MEDIA_SERVER="" if neither configured container is running. +# Usage: detect_media_server [docker_timeout_seconds] +detect_media_server() { + local docker_timeout="${1:-10}" + MEDIA_SERVER="" MEDIA_SERVER_URL="" MEDIA_SERVER_API_KEY="" + + local emby_up=false jellyfin_up=false + + if [[ -n "${EMBY_CONTAINER:-}" ]]; then + [[ "$(timeout "$docker_timeout" docker inspect -f '{{.State.Running}}' \ + "$EMBY_CONTAINER" 2>/dev/null)" == "true" ]] && emby_up=true + fi + if [[ -n "${JELLYFIN_CONTAINER:-}" ]]; then + [[ "$(timeout "$docker_timeout" docker inspect -f '{{.State.Running}}' \ + "$JELLYFIN_CONTAINER" 2>/dev/null)" == "true" ]] && jellyfin_up=true + fi + + if [[ "$emby_up" == true && "$jellyfin_up" == true ]]; then + log "Both Emby and Jellyfin are running — using Emby (primary)" + elif [[ "$emby_up" == true ]]; then + log "Emby is running, Jellyfin is not — using Emby" + elif [[ "$jellyfin_up" == true ]]; then + log "Jellyfin is running, Emby is not — using Jellyfin" + else + warn "Neither Emby nor Jellyfin is running — no media server available" + return 1 + fi + + if [[ "$emby_up" == true ]]; then + MEDIA_SERVER="Emby" + MEDIA_SERVER_URL="$EMBY_URL" + MEDIA_SERVER_API_KEY="$EMBY_API_KEY" + else + MEDIA_SERVER="Jellyfin" + MEDIA_SERVER_URL="$JELLYFIN_URL" + MEDIA_SERVER_API_KEY="$JELLYFIN_API_KEY" + fi +} + +# General REST call against whichever server detect_media_server() picked. Jellyfin accepts +# the same X-Emby-Token header Emby uses (inherited from its Emby-fork lineage — confirmed +# live 2026-07-19), so one call shape covers both backends. +# Usage: detect_media_server && media_server_api [timeout_seconds] +# Prints response body on 200; returns 1 on HTTP error, curl failure, or no server detected. +media_server_api() { + local endpoint="$1" max_time="${2:-30}" + if [[ -z "${MEDIA_SERVER:-}" ]]; then + error "media_server_api called with no MEDIA_SERVER set — call detect_media_server first" + return 1 + fi + local response http_code body + response=$(curl -sf --max-time "$max_time" \ + -H "X-Emby-Token: $MEDIA_SERVER_API_KEY" \ + -w "\n%{http_code}" \ + "${MEDIA_SERVER_URL}/${endpoint}" 2>/dev/null) + http_code=$(echo "$response" | tail -1) + body=$(echo "$response" | head -n -1) + [[ "$http_code" != "200" ]] && { error "$MEDIA_SERVER API HTTP $http_code: $endpoint"; return 1; } + echo "$body" +} + # Stop containers on the REMOTE server via SSH. # Reads CRITICAL_CONTAINER_NAMES — set from profile arrays by rsync.sh. # Tracks running state in RUNNING_CONTAINERS for restart after sync.