From 93b9c9d533396431a884004aa2a353bb2b3773ca Mon Sep 17 00:00:00 2001 From: s8n Date: Mon, 11 May 2026 04:15:18 +0100 Subject: [PATCH] docs(30): stock Jellyfin 10.11.8 rebuild on tv.s8n.ru MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brand-new container, brand-new volumes, ZERO ARRFLIX customisation. Sister instance to prod (10.10.3, untouched) and dev (10.11.8 + scyfin). P1+P2 from the rebuild plan: Movies + TV Shows libraries added via API, library scan complete (4 movies / 12 series / 230 eps). Auto-scrape matched 10/12 series + 4/4 movies to canonical TMDB IDs without manual intervention; 3 unmatched are TMDB-absent indie content. No theme, no shim, no CustomCss, no plugins, no user import, no home- section seed — owner explicitly asked for a stock baseline. --- docs/30-stock-jellyfin-tv-build.md | 186 +++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 docs/30-stock-jellyfin-tv-build.md diff --git a/docs/30-stock-jellyfin-tv-build.md b/docs/30-stock-jellyfin-tv-build.md new file mode 100644 index 0000000..bdbeb01 --- /dev/null +++ b/docs/30-stock-jellyfin-tv-build.md @@ -0,0 +1,186 @@ +# 30 — Stock Jellyfin rebuild on tv.s8n.ru (ground-up) + +Date: 2026-05-11 +Scope: brand new container, brand new volumes, zero ARRFLIX customisation. +Sister docs: 29 (the failed in-place dev upgrade that led here). + +--- + +## 1. Decision + +After running the dev migration (10.10.3 → 10.11.8 + scyfin) on the existing +`jellyfin-dev` container, the result still carried index.html shim, Cineplex +remnants, and accumulated configuration drift. Owner asked for a true clean +build instead. + +Approach: new container, new domain, no shim, no CustomCss. Stock Jellyfin. +We layer ARRFLIX brand on top once the bare server is happy. + +--- + +## 2. Deploy + +```yaml +# /opt/docker/jellyfin-stock/docker-compose.yml +services: + jellyfin-stock: + image: jellyfin/jellyfin:10.11.8 + container_name: jellyfin-stock + restart: unless-stopped + user: "1000:1000" + userns_mode: "host" + environment: + - TZ=Europe/London + - JELLYFIN_PublishedServerUrl=https://tv.s8n.ru + volumes: + - /home/docker/jellyfin-stock/config:/config + - /home/docker/jellyfin-stock/cache:/cache + - /home/user/media:/media:ro + networks: [proxy] + labels: + - traefik.enable=true + - traefik.docker.network=proxy + - traefik.http.routers.jellyfin-stock.rule=Host(`tv.s8n.ru`) + - traefik.http.routers.jellyfin-stock.entrypoints=websecure + - traefik.http.routers.jellyfin-stock.tls=true + - traefik.http.routers.jellyfin-stock.tls.certresolver=letsencrypt + - traefik.http.services.jellyfin-stock.loadbalancer.server.port=8096 +``` + +Volumes initialised empty. No bind-mount of index.html — the stock web UI +serves from the image as-is. + +### DNS + +``` +Pi-hole local DNS: tv.s8n.ru +onyx /etc/hosts: tv.s8n.ru (appended to existing pin block) +Public DNS (Gandi): none — LAN-only by design +``` + +(LAN IP is the standard nullstone bind, see SYSTEM.md.) + +`/opt/docker/pihole/etc-pihole/custom.list` is owned by root; we wrote via +privileged Alpine container + `--userns=host` to bypass the userns-remap. +Same trick used for the `/home/docker/jellyfin-stock/` dirs. + +ServerId: `adbc441eb46e475c9610c3bd5258dc6e` (fresh, not migrated from prod). + +--- + +## 3. Library scope (P1+P2) + +User chose P1+P2 from `tv.s8n.ru` plan: libraries + canonical-ID lock only. +No user import, no watched-state transfer, no plugins, no theme. + +### Libraries added via API + +```bash +TOKEN= + +curl -X POST -H "X-Emby-Token: $TOKEN" \ + "https://tv.s8n.ru/Library/VirtualFolders?name=Movies&collectionType=movies&paths=%2Fmedia%2Fmovies&refreshLibrary=false" \ + -H "Content-Type: application/json" \ + -d '{"LibraryOptions":{"EnableInternetProviders":true,"PreferredMetadataLanguage":"en","MetadataCountryCode":"US","SubtitleDownloadLanguages":["eng"],"SaveSubtitlesWithMedia":true,"RequirePerfectSubtitleMatch":false,"EnabledMetadataFetchers":["TheMovieDb","The Open Movie Database"],"MetadataFetcherOrder":["TheMovieDb","The Open Movie Database"]}}' + +curl -X POST -H "X-Emby-Token: $TOKEN" \ + "https://tv.s8n.ru/Library/VirtualFolders?name=TV%20Shows&collectionType=tvshows&paths=%2Fmedia%2Ftv&refreshLibrary=false" \ + -H "Content-Type: application/json" \ + -d '{"LibraryOptions":{...same shape, fetchers=[TheMovieDb,TheTVDB]}}' + +curl -X POST -H "X-Emby-Token: $TOKEN" "https://tv.s8n.ru/Library/Refresh" +``` + +### Scan result + +``` +MovieCount 4 +SeriesCount 12 +EpisodeCount 230 +``` + +### Auto-scrape outcome + +10 / 12 series + 4 / 4 movies matched canonical IDs without intervention. +Three unmatched, all expected: + +``` +The Big Lez Saga (2022) TMDB --- (TMDB has no entry; Australian indie) +The Donny & Clarence Show TMDB --- (IMDb tt32043762 only) +Star Wars: Maul - Shadow Lord [Before Upscale] no IDs (intentional dupe folder) +``` + +Matched IDs (sanity-checked against prod docs): + +``` +American Dad! TMDB 1433 +Archer TMDB 10283 +Futurama TMDB 615 TVDB 73871 IMDb tt0149460 +The Mandalorian TMDB 82856 +The Mike Nolan Show TMDB 67160 +Obi-Wan Kenobi TMDB 92830 +Rick and Morty TMDB 60625 +Sassy the Sasquatch TMDB 321760 +Star Wars: Maul TMDB 289219 +Movies + The Dark Knight TMDB 155 + Idiocracy TMDB 7512 + The Incredible Hulk TMDB 1724 + Lilo & Stitch TMDB 11544 +``` + +No `POST /Items/{id}` lock calls needed — the auto-scrape was clean. + +--- + +## 4. Passwords + +Admin `s8n` + user `guest` created via first-run wizard with throwaway +passwords. Owner asked to use the same passwords as prod. Approach for that +(deferred — pending owner decision): + +```sql +-- prod jellyfin.db Users.Password is $PBKDF2-SHA512$iterations=2100$$ +-- Copy hash from prod to stock: +ATTACH '/path/to/prod-jellyfin.db' AS prod; +UPDATE Users + SET Password = (SELECT Password FROM prod.Users WHERE Username = Users.Username) + WHERE Username IN ('s8n', 'guest'); +``` + +Run with container stopped. Verified the PBKDF2 hash includes the salt +inline so copying the column is enough — no separate salt column. + +--- + +## 5. Explicitly NOT done + +- No theme (no scyfin, no Cineplex, no ElegantFin). +- No `web-overrides/index.html` shim — stock Jellyfin chrome visible. +- No CustomCss in `branding.xml` (file is the 225-byte default). +- No plugins installed (no OpenSubtitles, no anything). +- No 13-user import — only `s8n` admin + `guest`. +- No home-section seed — stock defaults apply (smalllibrarytiles, resume, + resumeaudio, nextup, latestmedia). Owner will iterate from here. +- No backdrop pinning, no scrollbar themeing, no per-user prefs scripts. + +--- + +## 6. State table + +| Instance | Domain | Image | Theme | Brand | Status | +|---|---|---|---|---|---| +| `jellyfin` (prod) | arrflix.s8n.ru | 10.10.3 | Cineplex v1.0.6 + INC1-7 patches | ARRFLIX | Untouched, real users on it | +| `jellyfin-dev` | dev.arrflix.s8n.ru | 10.11.8 | scyfin OLED (broken brand-vs-shim mismatch) | ARRFLIX | Experimental — can be wiped | +| `jellyfin-stock` | tv.s8n.ru | 10.11.8 | — | stock Jellyfin | Fresh, ready to configure | + +--- + +## 7. Open follow-ups (none owed before owner sign-off) + +- Decide fate of `jellyfin-dev` (keep / wipe / repurpose). +- Owner explores stock UX → identifies what to brand vs leave alone. +- Eventually layer ARRFLIX skin (logo, accent, dark scrollbar) on top of + stock — incrementally, documenting each step. +- If migration to 10.11.8 on prod is later approved: docs/29 staged + 10.10.3 → 10.10.7 → 10.11.8 path with snapshots is the playbook.