Skip to content

Commit f8ad8e5

Browse files
committed
fix: harden jellyfin feature rollout and upgrade follow-through
1 parent a160579 commit f8ad8e5

7 files changed

Lines changed: 75 additions & 17 deletions

File tree

IMAGE_LOCK.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Tested lock snapshot:
2323
| qbittorrent | `lscr.io/linuxserver/qbittorrent@sha256:065792d2b11f0facff340210fc1cf13623b029a94ecdf08b02d06d922205f618` |
2424
| seerr | `ghcr.io/seerr-team/seerr@sha256:1b5fc1ea825631d9d165364472663b817a4c58ef6aa1013f58d82c1570d7c866` |
2525
| jellyfin (optional) | `lscr.io/linuxserver/jellyfin@sha256:4ee07757abcaa0b74fbc74179392311dc2874c03b0bef04bc2d79e9e1a875793` |
26+
| jellystat-db (optional) | `postgres@sha256:1090bc3a8ccfb0b55f78a494d76f8d603434f7e4553543d6e807bc7bd6bbd17f` |
27+
| jellystat (optional) | `cyfershepard/jellystat@sha256:c8c451704ba7985340142cd047e2364cabaf41b613669b6c5340688ed217f82a` |
2628
| tdarr | `ghcr.io/haveagitgat/tdarr@sha256:20a5656c4af4854e1877046294f77113f949d27e35940a9a65f231423d063207` |
2729
| tidarr (music profile) | `cstaelen/tidarr@sha256:79a2c62aed04dbe9770272443192eae145f26bdc2d188e665b13ab763341206c` |
2830

UPGRADE.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,11 @@ bash scripts/health-check.sh
8282

8383
After either upgrade path, confirm:
8484

85-
1. `~/Media/config/kometa/config.yml` has `PLEX_TOKEN` + TMDB key
86-
2. Tdarr libraries/plugins are configured at `http://localhost:8265`
87-
3. `bash scripts/health-check.sh` reports clean results
85+
1. Tdarr libraries/plugins are configured at `http://localhost:8265`
86+
2. `bash scripts/health-check.sh` reports clean results
87+
3. If using Plex: `~/Media/config/kometa/config.yml` has `PLEX_TOKEN` + TMDB key
88+
4. If using Jellyfin: open Jellystat at `http://localhost:3000` and connect it to `http://jellyfin:8096`
89+
5. If using Jellyfin: restart Jellyfin once after plugin install to activate Intro Skipper/TMDb Box Sets
8890

8991
## Rollback
9092

docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ services:
248248
restart: unless-stopped
249249

250250
jellystat-db:
251-
image: postgres:18.1
251+
image: postgres@sha256:1090bc3a8ccfb0b55f78a494d76f8d603434f7e4553543d6e807bc7bd6bbd17f
252252
container_name: jellystat-db
253253
profiles: ["jellyfin"]
254254
environment:
@@ -265,7 +265,7 @@ services:
265265
restart: unless-stopped
266266

267267
jellystat:
268-
image: cyfershepard/jellystat:latest
268+
image: cyfershepard/jellystat@sha256:c8c451704ba7985340142cd047e2364cabaf41b613669b6c5340688ed217f82a
269269
container_name: jellystat
270270
profiles: ["jellyfin"]
271271
depends_on:

scripts/configure.sh

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -440,33 +440,85 @@ if [[ "$MEDIA_SERVER" == "jellyfin" ]]; then
440440
# Fetch plugin catalog
441441
PLUGIN_CATALOG=$(curl -fsS "http://localhost:8096/Packages" -H "X-Emby-Token: $JF_API_KEY" 2>/dev/null || true)
442442

443+
url_encode() {
444+
local raw="$1"
445+
if command -v python3 >/dev/null 2>&1; then
446+
python3 - "$raw" <<'PY'
447+
import sys, urllib.parse
448+
print(urllib.parse.quote(sys.argv[1], safe=""))
449+
PY
450+
else
451+
# Best-effort fallback for environments without python3
452+
printf '%s\n' "${raw// /%20}"
453+
fi
454+
}
455+
456+
resolve_jf_plugin() {
457+
local requested="$1"
458+
if ! command -v python3 >/dev/null 2>&1; then
459+
return 1
460+
fi
461+
462+
printf '%s' "$PLUGIN_CATALOG" | python3 - "$requested" <<'PY'
463+
import json, sys
464+
465+
wanted = sys.argv[1].strip().lower()
466+
try:
467+
payload = json.load(sys.stdin)
468+
except Exception:
469+
sys.exit(1)
470+
471+
items = payload.get("Items", payload) if isinstance(payload, dict) else payload
472+
if not isinstance(items, list):
473+
sys.exit(1)
474+
475+
for item in items:
476+
if not isinstance(item, dict):
477+
continue
478+
name = str(item.get("name", item.get("Name", ""))).strip()
479+
guid = str(item.get("guid", item.get("Guid", ""))).strip()
480+
if name.lower() == wanted:
481+
print(name)
482+
print(guid)
483+
sys.exit(0)
484+
485+
sys.exit(1)
486+
PY
487+
}
488+
443489
install_jf_plugin() {
444490
local plugin_name="$1"
445491
if [[ -z "$PLUGIN_CATALOG" ]]; then
446492
warn "$plugin_name: could not fetch plugin catalog"
447493
return 0
448494
fi
449495

450-
# Extract the plugin GUID from catalog
451-
local plugin_guid
452-
plugin_guid=$(echo "$PLUGIN_CATALOG" | grep -o "\"name\":\"$plugin_name\"[^}]*\"guid\":\"[^\"]*\"" | grep -o '"guid":"[^"]*"' | cut -d'"' -f4 || true)
496+
local plugin_info plugin_guid resolved_name
497+
plugin_info="$(resolve_jf_plugin "$plugin_name" || true)"
498+
resolved_name="$(echo "$plugin_info" | sed -n '1p')"
499+
plugin_guid="$(echo "$plugin_info" | sed -n '2p')"
453500

454-
if [[ -z "$plugin_guid" ]]; then
501+
if [[ -z "$resolved_name" ]]; then
455502
warn "$plugin_name: not found in plugin catalog"
456503
warn " Install manually: Administration > Plugins > Catalog > $plugin_name"
457504
return 0
458505
fi
459506

460-
# Get latest version
461-
local install_result
507+
local install_result encoded_name install_url
508+
encoded_name="$(url_encode "$resolved_name")"
509+
install_url="http://localhost:8096/Packages/Installed/$encoded_name"
510+
if [[ -n "$plugin_guid" ]]; then
511+
install_url="${install_url}?assemblyGuid=$plugin_guid"
512+
fi
513+
462514
install_result=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
463-
"http://localhost:8096/Packages/Installed/$plugin_name" \
515+
"$install_url" \
464516
-H "X-Emby-Token: $JF_API_KEY" 2>/dev/null || echo "000")
465517

466518
if [[ "$install_result" =~ ^2 ]]; then
467-
log "$plugin_name plugin installed"
519+
log "$resolved_name plugin installed"
468520
else
469-
warn "$plugin_name: install returned HTTP $install_result"
521+
warn "$resolved_name: install returned HTTP $install_result"
470522
warn " Install manually: Administration > Plugins > Catalog > $plugin_name"
471523
fi
472524
}

scripts/health-check.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,8 @@ if [[ "$MEDIA_SERVER" == "jellyfin" ]]; then
133133
echo ""
134134
echo "Jellystat:"
135135
if [[ "$jellystat_state" == "running" ]]; then
136-
jellystat_http=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "http://localhost:3000" 2>/dev/null)
137-
if [[ "$jellystat_http" =~ ^[23][0-9][0-9]$ ]]; then
136+
jellystat_http=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "http://localhost:3000/api/getConfiguration" 2>/dev/null)
137+
if [[ "$jellystat_http" =~ ^(200|401|403)$ ]]; then
138138
echo -e " ${GREEN}OK${NC} Jellystat"
139139
((PASS++))
140140
else

scripts/refresh-image-lock.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ LOCK_FILE="$SCRIPT_DIR/IMAGE_LOCK.md"
1010
ENV_FILE="$SCRIPT_DIR/.env"
1111

1212
PROFILES=(autoupdate music jellyfin)
13-
OPTIONAL_SERVICES=(watchtower jellyfin)
13+
OPTIONAL_SERVICES=(watchtower jellyfin jellystat jellystat-db)
1414
MUSIC_SERVICES=(lidarr tidarr)
1515

1616
for cmd in docker awk sed mktemp; do

scripts/upgrade-from-basic.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,8 @@ if [[ "$MEDIA_SERVER" == "plex" ]]; then
287287
echo " 2. Configure Tdarr at http://localhost:8265"
288288
else
289289
echo " 1. Configure Tdarr at http://localhost:8265"
290+
echo " 2. Open Jellystat at http://localhost:3000 and connect to http://jellyfin:8096"
291+
echo " 3. If prompted, restart Jellyfin to finish Intro Skipper/TMDb Box Sets plugin activation"
290292
fi
291293
echo ""
292294
echo "Rollback (if needed):"

0 commit comments

Comments
 (0)