2026-05-08 02:09:41 +01:00
|
|
|
#!/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)
|
Restrict non-admin users + 3 imports landed
- EnableUserPreferenceAccess=false on guest + 5 (hides Display, Home,
Playback, Subtitles pref pages — owner controls UX centrally).
- Wrapper bin/add-jellyfin-user.sh updated to bake this into all future
non-admin user creations.
- ROADMAP entries (added by sibling import agents):
- Imported: The Incredible Hulk (2008), TMDB 1724, 4 images
- Imported: Idiocracy (2006), TMDB 7512 (NOT 1542 = Office Space)
- Imported: American Dad! (2005) S01-S04, 58 eps, TMDB 1433
- WAN exposure docs added (doc 09, 256 lines): Gandi A record live,
no-guest middleware dropped, lockout=5 baked in. Owner still must
port-forward 80/443 on home router for actual public access.
2026-05-08 03:18:58 +01:00
|
|
|
# - SubtitleMode=Default, SubtitleLanguagePreference=eng, AudioLanguagePreference=eng
|
2026-05-08 02:09:41 +01:00
|
|
|
# - Non-admin policy with EnableContentDeletion=false
|
Restrict non-admin users + 3 imports landed
- EnableUserPreferenceAccess=false on guest + 5 (hides Display, Home,
Playback, Subtitles pref pages — owner controls UX centrally).
- Wrapper bin/add-jellyfin-user.sh updated to bake this into all future
non-admin user creations.
- ROADMAP entries (added by sibling import agents):
- Imported: The Incredible Hulk (2008), TMDB 1724, 4 images
- Imported: Idiocracy (2006), TMDB 7512 (NOT 1542 = Office Space)
- Imported: American Dad! (2005) S01-S04, 58 eps, TMDB 1433
- WAN exposure docs added (doc 09, 256 lines): Gandi A record live,
no-guest middleware dropped, lockout=5 baked in. Owner still must
port-forward 80/443 on home router for actual public access.
2026-05-08 03:18:58 +01:00
|
|
|
# - LoginAttemptsBeforeLockout=5 (brute-force lockout after 5 failed attempts;
|
|
|
|
|
# required since arrflix.s8n.ru exposed to public internet on 2026-05-08)
|
|
|
|
|
# - EnableUserPreferenceAccess=false (hides Display/Home/Playback/Subtitles
|
|
|
|
|
# pref pages — owner controls all UX defaults centrally; non-admins can't
|
|
|
|
|
# change their own home layout, subtitle lang, etc.)
|
2026-05-08 02:09:41 +01:00
|
|
|
#
|
|
|
|
|
# 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 <username> <password>
|
|
|
|
|
#
|
|
|
|
|
# Or interactive (will prompt for missing creds).
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
Rename: nasflix → ARRFLIX + apply Cineplex theme
Domain + repo rename: nasflix.s8n.ru → arrflix.s8n.ru, NASFLIX → ARRFLIX
(Forgejo repo, Pi-hole DNS, Traefik file+label routes, compose env+labels,
onyx /etc/hosts, branding LoginDisclaimer, all repo refs, logo asset).
Theme: ElegantFin → Cineplex v1.0.6 (MRunkehl, pinned). Picked by research
agent over JellyFlix (halted), DarkFlix (10.8.x only), Theme Park (no
Netflix preset). Real #E50914 + Netflix Sans webfont + transform:scale
hover + gradient login backdrop. Doc 04 updated with full candidate
matrix, theme-history subsection, rollback-to-ElegantFin snippet.
Logo asset saved at assets/logo.png (235x85 RGBA).
Live: https://arrflix.s8n.ru → 302. tv.s8n.ru + nasflix.s8n.ru retired (404).
2026-05-08 02:57:34 +01:00
|
|
|
JELLYFIN_URL="${JELLYFIN_URL:-https://arrflix.s8n.ru}"
|
2026-05-08 02:09:41 +01:00
|
|
|
JELLYFIN_TOKEN="${JELLYFIN_TOKEN:?set JELLYFIN_TOKEN=<admin-token>}"
|
|
|
|
|
|
|
|
|
|
USERNAME="${1:?usage: $0 <username> <password>}"
|
|
|
|
|
PASSWORD="${2:?usage: $0 <username> <password>}"
|
|
|
|
|
|
|
|
|
|
AUTH="MediaBrowser Token=$JELLYFIN_TOKEN"
|
|
|
|
|
|
Restrict non-admin users + 3 imports landed
- EnableUserPreferenceAccess=false on guest + 5 (hides Display, Home,
Playback, Subtitles pref pages — owner controls UX centrally).
- Wrapper bin/add-jellyfin-user.sh updated to bake this into all future
non-admin user creations.
- ROADMAP entries (added by sibling import agents):
- Imported: The Incredible Hulk (2008), TMDB 1724, 4 images
- Imported: Idiocracy (2006), TMDB 7512 (NOT 1542 = Office Space)
- Imported: American Dad! (2005) S01-S04, 58 eps, TMDB 1433
- WAN exposure docs added (doc 09, 256 lines): Gandi A record live,
no-guest middleware dropped, lockout=5 baked in. Owner still must
port-forward 80/443 on home router for actual public access.
2026-05-08 03:18:58 +01:00
|
|
|
echo "[1/4] Creating user '$USERNAME'..."
|
2026-05-08 02:09:41 +01:00
|
|
|
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"
|
|
|
|
|
|
Restrict non-admin users + 3 imports landed
- EnableUserPreferenceAccess=false on guest + 5 (hides Display, Home,
Playback, Subtitles pref pages — owner controls UX centrally).
- Wrapper bin/add-jellyfin-user.sh updated to bake this into all future
non-admin user creations.
- ROADMAP entries (added by sibling import agents):
- Imported: The Incredible Hulk (2008), TMDB 1724, 4 images
- Imported: Idiocracy (2006), TMDB 7512 (NOT 1542 = Office Space)
- Imported: American Dad! (2005) S01-S04, 58 eps, TMDB 1433
- WAN exposure docs added (doc 09, 256 lines): Gandi A record live,
no-guest middleware dropped, lockout=5 baked in. Owner still must
port-forward 80/443 on home router for actual public access.
2026-05-08 03:18:58 +01:00
|
|
|
echo "[2/4] Applying canonical home layout..."
|
2026-05-08 02:09:41 +01:00
|
|
|
curl -ks "$JELLYFIN_URL/DisplayPreferences/usersettings?userId=$USER_ID&client=emby" \
|
|
|
|
|
-H "Authorization: $AUTH" > /tmp/dp-cur.$$.json
|
|
|
|
|
python3 - <<EOF > /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."
|
|
|
|
|
|
2026-05-08 23:46:02 +01:00
|
|
|
echo "[3/4] Setting language prefs (force English everywhere, no fallback)..."
|
2026-05-08 02:09:41 +01:00
|
|
|
curl -ks "$JELLYFIN_URL/Users/$USER_ID" -H "Authorization: $AUTH" > /tmp/u.$$.json
|
|
|
|
|
python3 - <<EOF > /tmp/u-fix.$$.json
|
|
|
|
|
import json
|
|
|
|
|
with open('/tmp/u.$$.json') as f: u = json.load(f)
|
|
|
|
|
c = u['Configuration']
|
2026-05-08 02:37:23 +01:00
|
|
|
c['SubtitleMode'] = 'Default'
|
2026-05-08 02:09:41 +01:00
|
|
|
c['SubtitleLanguagePreference'] = 'eng'
|
2026-05-08 02:37:23 +01:00
|
|
|
c['AudioLanguagePreference'] = 'eng'
|
|
|
|
|
c['PlayDefaultAudioTrack'] = True
|
2026-05-08 23:46:02 +01:00
|
|
|
c['UICulture'] = 'en-US'
|
|
|
|
|
c['DisplayMissingEpisodes'] = False
|
2026-05-08 02:09:41 +01:00
|
|
|
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."
|
|
|
|
|
|
Restrict non-admin users + 3 imports landed
- EnableUserPreferenceAccess=false on guest + 5 (hides Display, Home,
Playback, Subtitles pref pages — owner controls UX centrally).
- Wrapper bin/add-jellyfin-user.sh updated to bake this into all future
non-admin user creations.
- ROADMAP entries (added by sibling import agents):
- Imported: The Incredible Hulk (2008), TMDB 1724, 4 images
- Imported: Idiocracy (2006), TMDB 7512 (NOT 1542 = Office Space)
- Imported: American Dad! (2005) S01-S04, 58 eps, TMDB 1433
- WAN exposure docs added (doc 09, 256 lines): Gandi A record live,
no-guest middleware dropped, lockout=5 baked in. Owner still must
port-forward 80/443 on home router for actual public access.
2026-05-08 03:18:58 +01:00
|
|
|
echo "[4/4] Applying restricted policy (lockout=5, no user-pref access)..."
|
|
|
|
|
curl -ks "$JELLYFIN_URL/Users/$USER_ID" -H "Authorization: $AUTH" > /tmp/p.$$.json
|
|
|
|
|
python3 - <<EOF > /tmp/p-fix.$$.json
|
|
|
|
|
import json
|
|
|
|
|
with open('/tmp/p.$$.json') as f: u = json.load(f)
|
|
|
|
|
p = u['Policy']
|
|
|
|
|
p['LoginAttemptsBeforeLockout'] = 5
|
|
|
|
|
p['EnableUserPreferenceAccess'] = False
|
|
|
|
|
print(json.dumps(p))
|
|
|
|
|
EOF
|
|
|
|
|
HTTP=$(curl -ks -X POST "$JELLYFIN_URL/Users/$USER_ID/Policy" \
|
|
|
|
|
-H "Authorization: $AUTH" \
|
|
|
|
|
-H "Content-Type: application/json" \
|
|
|
|
|
--data-binary @/tmp/p-fix.$$.json -w "%{http_code}" -o /dev/null)
|
|
|
|
|
rm -f /tmp/p.$$.json /tmp/p-fix.$$.json
|
|
|
|
|
[[ "$HTTP" == "204" ]] || { echo " Policy POST failed: $HTTP"; exit 1; }
|
|
|
|
|
echo " Lockout policy applied."
|
|
|
|
|
|
2026-05-08 02:09:41 +01:00
|
|
|
echo
|
|
|
|
|
echo "Done. User '$USERNAME' ready at $JELLYFIN_URL"
|
|
|
|
|
echo " UserId: $USER_ID"
|