diff --git a/bin/add-jellyfin-user.sh b/bin/add-jellyfin-user.sh new file mode 100755 index 0000000..5b3445b --- /dev/null +++ b/bin/add-jellyfin-user.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# add-jellyfin-user.sh — create a Jellyfin user with the canonical s8n layout. +# +# Defaults applied: +# - Home sections 0..9: resume, resumeaudio, nextup, latestmedia, none x6 +# (no "My Media" tile row — sidebar already exposes libraries) +# - SubtitleMode=Always, SubtitleLanguagePreference=eng, AudioLanguagePreference=pol +# - Non-admin policy with EnableContentDeletion=false +# +# Why this exists: Jellyfin has no native global default for new-user +# DisplayPreferences. The home-layout defaults are baked into the web bundle. +# This wrapper layers our preferences on top after user creation, so the same +# layout shows up for every user without anyone having to click through +# Settings → Display → Home Screen. +# +# Usage: +# JELLYFIN_TOKEN=xxx ./add-jellyfin-user.sh +# +# Or interactive (will prompt for missing creds). +set -euo pipefail + +JELLYFIN_URL="${JELLYFIN_URL:-https://tv.s8n.ru}" +JELLYFIN_TOKEN="${JELLYFIN_TOKEN:?set JELLYFIN_TOKEN=}" + +USERNAME="${1:?usage: $0 }" +PASSWORD="${2:?usage: $0 }" + +AUTH="MediaBrowser Token=$JELLYFIN_TOKEN" + +echo "[1/3] Creating user '$USERNAME'..." +RESP=$(curl -ks -X POST "$JELLYFIN_URL/Users/New" \ + -H "Authorization: $AUTH" \ + -H "Content-Type: application/json" \ + -d "{\"Name\":\"$USERNAME\",\"Password\":\"$PASSWORD\"}") +USER_ID=$(echo "$RESP" | python3 -c "import json,sys; print(json.load(sys.stdin)['Id'])" 2>/dev/null || true) +if [[ -z "$USER_ID" ]]; then + echo "User creation failed:" + echo "$RESP" + exit 1 +fi +echo " Created. UserId=$USER_ID" + +echo "[2/3] Applying canonical home layout..." +curl -ks "$JELLYFIN_URL/DisplayPreferences/usersettings?userId=$USER_ID&client=emby" \ + -H "Authorization: $AUTH" > /tmp/dp-cur.$$.json +python3 - < /tmp/dp-fix.$$.json +import json +with open('/tmp/dp-cur.$$.json') as f: dp = json.load(f) +cp = dp['CustomPrefs'] +for k in list(cp.keys()): + if k.lower().startswith('homesection'): + del cp[k] +cp['homesection0'] = 'resume' +cp['homesection1'] = 'resumeaudio' +cp['homesection2'] = 'nextup' +cp['homesection3'] = 'latestmedia' +for i in range(4, 10): + cp[f'homesection{i}'] = 'none' +print(json.dumps(dp)) +EOF +HTTP=$(curl -ks -X POST "$JELLYFIN_URL/DisplayPreferences/usersettings?userId=$USER_ID&client=emby" \ + -H "Authorization: $AUTH" \ + -H "Content-Type: application/json" \ + --data-binary @/tmp/dp-fix.$$.json -w "%{http_code}" -o /dev/null) +rm -f /tmp/dp-cur.$$.json /tmp/dp-fix.$$.json +[[ "$HTTP" == "204" ]] || { echo " DisplayPreferences POST failed: $HTTP"; exit 1; } +echo " Home layout applied." + +echo "[3/3] Setting subtitle + audio prefs (subs=Always/eng, audio=pol)..." +curl -ks "$JELLYFIN_URL/Users/$USER_ID" -H "Authorization: $AUTH" > /tmp/u.$$.json +python3 - < /tmp/u-fix.$$.json +import json +with open('/tmp/u.$$.json') as f: u = json.load(f) +c = u['Configuration'] +c['SubtitleMode'] = 'Always' +c['SubtitleLanguagePreference'] = 'eng' +c['AudioLanguagePreference'] = 'pol' +print(json.dumps(c)) +EOF +HTTP=$(curl -ks -X POST "$JELLYFIN_URL/Users/$USER_ID/Configuration" \ + -H "Authorization: $AUTH" \ + -H "Content-Type: application/json" \ + --data-binary @/tmp/u-fix.$$.json -w "%{http_code}" -o /dev/null) +rm -f /tmp/u.$$.json /tmp/u-fix.$$.json +[[ "$HTTP" == "204" ]] || { echo " User config POST failed: $HTTP"; exit 1; } +echo " User prefs applied." + +echo +echo "Done. User '$USERNAME' ready at $JELLYFIN_URL" +echo " UserId: $USER_ID"