Rename: tv.s8n.ru → nasflix.s8n.ru, jellyfin-stack → NASFLIX

- Domain: tv.s8n.ru retired (404). nasflix.s8n.ru live (302 → /web).
  Pi-hole local DNS updated. Traefik file-provider router rule + docker-label
  router rule both flipped. Jellyfin PublishedServerUrl env updated. Cert
  re-issued via Gandi DNS-01. Onyx /etc/hosts pin moved.
- Repo: forgejo PATCH /api/v1/repos rename. Local clone remote URL updated.
  All in-tree refs to tv.s8n.ru and jellyfin-stack swept (sed).
- Scope: TV Shows + Movies only. anime/, musicvideos/, home/, music/,
  docs-*/ libraries removed from canonical layout. Sections kept as
  reference for re-introduction.
- Branding LoginDisclaimer text updated to nasflix.s8n.ru.
This commit is contained in:
s8n 2026-05-08 02:53:46 +01:00
parent ab16861314
commit cb95dce8bc
13 changed files with 77 additions and 92 deletions

View file

@ -1,4 +1,4 @@
# Admin Guide — tv.s8n.ru # Admin Guide — nasflix.s8n.ru
The single page that tells you what to do and where to read more. The single page that tells you what to do and where to read more.
Anything not on this page lives in `docs/` — links inline. Anything not on this page lives in `docs/` — links inline.
@ -13,9 +13,9 @@ What's next on this deploy → [`ROADMAP.md`](ROADMAP.md).
|---|---| |---|---|
| Compose file | `docker-compose.yml` (this repo) → deployed at `nullstone:/opt/docker/jellyfin/` | | Compose file | `docker-compose.yml` (this repo) → deployed at `nullstone:/opt/docker/jellyfin/` |
| Config + cache | `nullstone:/home/docker/jellyfin/{config,cache}/` | | Config + cache | `nullstone:/home/docker/jellyfin/{config,cache}/` |
| Media root | `nullstone:/home/user/media/{movies,tv,anime,musicvideos}/` | | Media root | `nullstone:/home/user/media/{movies,tv}/` |
| Traefik route | `/opt/docker/traefik/config/jellyfin-test.yml` (file-provider — see [docs/04 § routing-quirk](docs/04-theming-and-users.md)) | | Traefik route | `/opt/docker/traefik/config/jellyfin-test.yml` (file-provider — see [docs/04 § routing-quirk](docs/04-theming-and-users.md)) |
| Pi-hole DNS pin | `/opt/docker/pihole/etc-pihole/custom.list``192.168.0.100 tv.s8n.ru` | | Pi-hole DNS pin | `/opt/docker/pihole/etc-pihole/custom.list``192.168.0.100 nasflix.s8n.ru` |
| User wrapper | `bin/add-jellyfin-user.sh` (always use this) | | User wrapper | `bin/add-jellyfin-user.sh` (always use this) |
--- ---
@ -39,7 +39,7 @@ Why it exists (Jellyfin has no native global default) → see same doc.
3. Normalize filenames per [docs/08](docs/08-filename-normalization.md). Use `bin/normalize.py` once written. 3. Normalize filenames per [docs/08](docs/08-filename-normalization.md). Use `bin/normalize.py` once written.
4. Stage to `/home/admin/staging-jelly-<title>/<canonical-name>/`. 4. Stage to `/home/admin/staging-jelly-<title>/<canonical-name>/`.
5. `scp -r` to `nullstone:/home/user/media/<lib>/`. 5. `scp -r` to `nullstone:/home/user/media/<lib>/`.
6. Trigger refresh: `curl -X POST -H "Authorization: MediaBrowser Token=$TOKEN" https://tv.s8n.ru/Library/Refresh`. 6. Trigger refresh: `curl -X POST -H "Authorization: MediaBrowser Token=$TOKEN" https://nasflix.s8n.ru/Library/Refresh`.
7. Verify: hit the URL, check artwork + titles + episode counts. 7. Verify: hit the URL, check artwork + titles + episode counts.
8. **Only after confirmed working**: delete the source download + the staging dir. 8. **Only after confirmed working**: delete the source download + the staging dir.
@ -72,8 +72,8 @@ ElegantFin imports from `cdn.jsdelivr.net/gh/lscambo13/ElegantFin@main/...` —
1. **Wrapper-only user creation.** Don't create users in the Dashboard UI. Always run `bin/add-jellyfin-user.sh` so the layout + prefs are consistent. Doc 04. 1. **Wrapper-only user creation.** Don't create users in the Dashboard UI. Always run `bin/add-jellyfin-user.sh` so the layout + prefs are consistent. Doc 04.
2. **Stage before import.** Never drop raw downloads into `/home/user/media/`. Always clean + normalize first. Docs 05/07/08. 2. **Stage before import.** Never drop raw downloads into `/home/user/media/`. Always clean + normalize first. Docs 05/07/08.
3. **Verify before delete.** Don't delete source downloads or old library entries until the new content is confirmed playing in the Jellyfin UI. 3. **Verify before delete.** Don't delete source downloads or old library entries until the new content is confirmed playing in the Jellyfin UI.
4. **No bind-mount drift.** If you change `docker-compose.yml`, commit + push to `git.s8n.ru/s8n/jellyfin-stack` in the same step. The repo is the source of truth for the deploy. Doc memory: `feedback_always_commit_to_my_git.md`. 4. **No bind-mount drift.** If you change `docker-compose.yml`, commit + push to `git.s8n.ru/s8n/NASFLIX` in the same step. The repo is the source of truth for the deploy. Doc memory: `feedback_always_commit_to_my_git.md`.
5. **No public DNS.** `tv.s8n.ru` resolves only via Pi-hole locally. Do NOT add a Gandi A record. LAN-only is the threat model. 5. **No public DNS.** `nasflix.s8n.ru` resolves only via Pi-hole locally. Do NOT add a Gandi A record. LAN-only is the threat model.
--- ---

View file

@ -1,4 +1,4 @@
# jellyfin-stack # NASFLIX
Self-hosted Jellyfin media server on nullstone, LAN-only. Self-hosted Jellyfin media server on nullstone, LAN-only.
@ -8,7 +8,7 @@ Self-hosted Jellyfin media server on nullstone, LAN-only.
## Endpoint ## Endpoint
- `https://tv.s8n.ru` — accessible only from LAN (192.168.0.0/24) and Tailscale admin/infra tags via Traefik `no-guest@file` middleware. - `https://nasflix.s8n.ru` — accessible only from LAN (192.168.0.0/24) and Tailscale admin/infra tags via Traefik `no-guest@file` middleware.
- DNS resolved internally by Pi-hole (`/opt/docker/pihole/etc-pihole/custom.list`). - DNS resolved internally by Pi-hole (`/opt/docker/pihole/etc-pihole/custom.list`).
- TLS via Let's Encrypt DNS-01 (Gandi). - TLS via Let's Encrypt DNS-01 (Gandi).
@ -49,7 +49,7 @@ deploy:
## First-run setup ## First-run setup
1. Browse to `https://tv.s8n.ru` from the LAN. 1. Browse to `https://nasflix.s8n.ru` from the LAN.
2. Create the admin user (Jellyfin onboarding wizard). 2. Create the admin user (Jellyfin onboarding wizard).
3. Add libraries pointing at `/media/movies` and `/media/tv` inside the 3. Add libraries pointing at `/media/movies` and `/media/tv` inside the
container (these map to `/home/user/media/{movies,tv}`). container (these map to `/home/user/media/{movies,tv}`).

View file

@ -1,4 +1,4 @@
# Roadmap — jellyfin-stack # Roadmap — NASFLIX
What's done, what's open, what's deferred. Update on every commit that lands or What's done, what's open, what's deferred. Update on every commit that lands or
moves an item between buckets. moves an item between buckets.
@ -9,7 +9,7 @@ Last revised: 2026-05-08
## Done ## Done
- [x] **Deploy**: Jellyfin 10.10.3 on nullstone, LAN-only at `tv.s8n.ru`, file-provider Traefik route, LE cert via Gandi DNS-01, Pi-hole local DNS pin, userns_mode=host - [x] **Deploy**: Jellyfin 10.10.3 on nullstone, LAN-only at `nasflix.s8n.ru`, file-provider Traefik route, LE cert via Gandi DNS-01, Pi-hole local DNS pin, userns_mode=host
- [x] **Theme**: ElegantFin v25.12.31 applied via `/System/Configuration/branding` - [x] **Theme**: ElegantFin v25.12.31 applied via `/System/Configuration/branding`
- [x] **Cast & Crew + Guest Stars**: hidden globally via CustomCss (`#castCollapsible, #guestCastCollapsible`) - [x] **Cast & Crew + Guest Stars**: hidden globally via CustomCss (`#castCollapsible, #guestCastCollapsible`)
- [x] **Library**: TV Shows → `/media/tv/Futurama (1999)/`, 72 eps + 9 featurettes, locked to TMDB 615 - [x] **Library**: TV Shows → `/media/tv/Futurama (1999)/`, 72 eps + 9 featurettes, locked to TMDB 615
@ -40,7 +40,7 @@ Last revised: 2026-05-08
- Estimated wall: 30 min + reboot (nullstone hosts traefik, forgejo, matrix — ~2 min downtime) - Estimated wall: 30 min + reboot (nullstone hosts traefik, forgejo, matrix — ~2 min downtime)
- [ ] **Loading-splash rebrand** - [ ] **Loading-splash rebrand**
- Replace Jellyfin pre-bundle logo with `tv.s8n.ru` wordmark + 4-bar pulse spinner - Replace Jellyfin pre-bundle logo with `nasflix.s8n.ru` wordmark + 4-bar pulse spinner
- Approach: bind-mount patched `/jellyfin/jellyfin-web/index.html` per the plan in this session's history - Approach: bind-mount patched `/jellyfin/jellyfin-web/index.html` per the plan in this session's history
- Doc to write: `docs/09-loading-splash.md` (pre-bundle vs CustomCss timing, regen-on-upgrade) - Doc to write: `docs/09-loading-splash.md` (pre-bundle vs CustomCss timing, regen-on-upgrade)

View file

@ -19,7 +19,7 @@
# Or interactive (will prompt for missing creds). # Or interactive (will prompt for missing creds).
set -euo pipefail set -euo pipefail
JELLYFIN_URL="${JELLYFIN_URL:-https://tv.s8n.ru}" JELLYFIN_URL="${JELLYFIN_URL:-https://nasflix.s8n.ru}"
JELLYFIN_TOKEN="${JELLYFIN_TOKEN:?set JELLYFIN_TOKEN=<admin-token>}" JELLYFIN_TOKEN="${JELLYFIN_TOKEN:?set JELLYFIN_TOKEN=<admin-token>}"
USERNAME="${1:?usage: $0 <username> <password>}" USERNAME="${1:?usage: $0 <username> <password>}"

View file

@ -1,6 +1,6 @@
# Jellyfin — self-hosted media server (LAN-only) # Jellyfin — self-hosted media server (LAN-only)
# Deploy path on nullstone: /opt/docker/jellyfin/ # Deploy path on nullstone: /opt/docker/jellyfin/
# Domain: tv.s8n.ru (LAN-only via Pi-hole local DNS + no-guest middleware) # Domain: nasflix.s8n.ru (LAN-only via Pi-hole local DNS + no-guest middleware)
# #
# Notes: # Notes:
# - GTX 1660 Ti present but nvidia-smi failing on host. CPU transcode only # - GTX 1660 Ti present but nvidia-smi failing on host. CPU transcode only
@ -19,7 +19,7 @@ services:
userns_mode: "host" userns_mode: "host"
environment: environment:
- TZ=Europe/London - TZ=Europe/London
- JELLYFIN_PublishedServerUrl=https://tv.s8n.ru - JELLYFIN_PublishedServerUrl=https://nasflix.s8n.ru
volumes: volumes:
- /home/docker/jellyfin/config:/config - /home/docker/jellyfin/config:/config
- /home/docker/jellyfin/cache:/cache - /home/docker/jellyfin/cache:/cache
@ -29,7 +29,7 @@ services:
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=proxy" - "traefik.docker.network=proxy"
- "traefik.http.routers.jellyfin.rule=Host(`tv.s8n.ru`)" - "traefik.http.routers.jellyfin.rule=Host(`nasflix.s8n.ru`)"
- "traefik.http.routers.jellyfin.entrypoints=websecure" - "traefik.http.routers.jellyfin.entrypoints=websecure"
- "traefik.http.routers.jellyfin.tls=true" - "traefik.http.routers.jellyfin.tls=true"
- "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt" - "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt"

View file

@ -5,14 +5,14 @@ wrong with the Futurama library on first scan, and the exact API calls used to
fix it. Written for an operator who's never used Jellyfin before but is fix it. Written for an operator who's never used Jellyfin before but is
comfortable with curl and Docker. comfortable with curl and Docker.
Server: `https://tv.s8n.ru` (Jellyfin `10.10.3`, container `jellyfin` on Server: `https://nasflix.s8n.ru` (Jellyfin `10.10.3`, container `jellyfin` on
nullstone). Auth header below uses the long-lived API token — replace with your nullstone). Auth header below uses the long-lived API token — replace with your
own `X-Emby-Token` if needed. own `X-Emby-Token` if needed.
```bash ```bash
TOKEN="*redacted*" TOKEN="*redacted*"
H="-H \"Authorization: MediaBrowser Token=${TOKEN}\"" H="-H \"Authorization: MediaBrowser Token=${TOKEN}\""
BASE="https://tv.s8n.ru" BASE="https://nasflix.s8n.ru"
``` ```
--- ---

View file

@ -1,6 +1,6 @@
# Jellyfin Metadata & Episode Titles — Operator Guide # Jellyfin Metadata & Episode Titles — Operator Guide
Server: `https://tv.s8n.ru` (Jellyfin 10.10.3, container on nullstone) Server: `https://nasflix.s8n.ru` (Jellyfin 10.10.3, container on nullstone)
Fixture for this doc: TV Shows library, series **Futurama** (1999), 44 episodes split across S01S03. Fixture for this doc: TV Shows library, series **Futurama** (1999), 44 episodes split across S01S03.
--- ---
@ -87,7 +87,7 @@ Jellyfin's Identify wizard has three calls. Auth header: `X-Emby-Token: <api-key
```bash ```bash
curl -s -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \ curl -s -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \
-d '{"SearchInfo":{"Name":"Futurama"}}' \ -d '{"SearchInfo":{"Name":"Futurama"}}' \
https://tv.s8n.ru/Items/RemoteSearch/Series https://nasflix.s8n.ru/Items/RemoteSearch/Series
``` ```
Returns an array of candidates from each registered provider (TheMovieDb, TheTVDB, OMDb). Inspect `ProviderIds`, `ProductionYear`, `Overview`, `ImageUrl` to pick the right one. Returns an array of candidates from each registered provider (TheMovieDb, TheTVDB, OMDb). Inspect `ProviderIds`, `ProductionYear`, `Overview`, `ImageUrl` to pick the right one.
@ -97,14 +97,14 @@ For Episodes use `RemoteSearch/Episode` and pass `SeriesProviderIds` + `IndexNum
```bash ```bash
curl -s --max-time 60 -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \ curl -s --max-time 60 -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \
-d '{"Name":"Futurama","ProviderIds":{"Tmdb":"615"},"ProductionYear":1999,"SearchProviderName":"TheMovieDb"}' \ -d '{"Name":"Futurama","ProviderIds":{"Tmdb":"615"},"ProductionYear":1999,"SearchProviderName":"TheMovieDb"}' \
"https://tv.s8n.ru/Items/RemoteSearch/Apply/$SERIES_ID?ReplaceAllImages=true" "https://nasflix.s8n.ru/Items/RemoteSearch/Apply/$SERIES_ID?ReplaceAllImages=true"
``` ```
Response: `204 No Content` on success. **Note**: this endpoint runs synchronously for ~3060 s while Jellyfin pulls images. The Traefik proxy in front of Jellyfin will return `502 Bad Gateway` if you don't bump `--max-time` above the proxy's idle threshold. Use `--max-time 60` from the client; the operation continues server-side regardless of the client timeout. Response: `204 No Content` on success. **Note**: this endpoint runs synchronously for ~3060 s while Jellyfin pulls images. The Traefik proxy in front of Jellyfin will return `502 Bad Gateway` if you don't bump `--max-time` above the proxy's idle threshold. Use `--max-time 60` from the client; the operation continues server-side regardless of the client timeout.
### 4.3 Trigger a metadata refresh ### 4.3 Trigger a metadata refresh
```bash ```bash
curl -s --max-time 60 -X POST -H "X-Emby-Token: $TOKEN" \ curl -s --max-time 60 -X POST -H "X-Emby-Token: $TOKEN" \
"https://tv.s8n.ru/Items/$SERIES_ID/Refresh?Recursive=true&MetadataRefreshMode=FullRefresh&ImageRefreshMode=FullRefresh&ReplaceAllMetadata=true&ReplaceAllImages=false" "https://nasflix.s8n.ru/Items/$SERIES_ID/Refresh?Recursive=true&MetadataRefreshMode=FullRefresh&ImageRefreshMode=FullRefresh&ReplaceAllMetadata=true&ReplaceAllImages=false"
``` ```
Parameters: Parameters:
- `Recursive=true` — descend into seasons + episodes (otherwise only the series item refreshes) - `Recursive=true` — descend into seasons + episodes (otherwise only the series item refreshes)
@ -115,7 +115,7 @@ Parameters:
The refresh is a fire-and-forget job. Poll for completion by re-querying episodes: The refresh is a fire-and-forget job. Poll for completion by re-querying episodes:
```bash ```bash
curl -s -H "X-Emby-Token: $TOKEN" \ curl -s -H "X-Emby-Token: $TOKEN" \
"https://tv.s8n.ru/Items?Recursive=true&IncludeItemTypes=Episode&Fields=ProviderIds&ParentId=$SERIES_ID&Limit=44" \ "https://nasflix.s8n.ru/Items?Recursive=true&IncludeItemTypes=Episode&Fields=ProviderIds&ParentId=$SERIES_ID&Limit=44" \
| jq '[.Items[] | select(.ProviderIds | length > 0)] | length' | jq '[.Items[] | select(.ProviderIds | length > 0)] | length'
``` ```
@ -134,7 +134,7 @@ curl -s -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \
"PremiereDate":"1999-03-28", "PremiereDate":"1999-03-28",
"LockedFields":["Name","Overview"] "LockedFields":["Name","Overview"]
}' \ }' \
https://tv.s8n.ru/Items/$EP_ID https://nasflix.s8n.ru/Items/$EP_ID
``` ```
`LockedFields` tells subsequent refreshes to leave those fields alone — important if you want to keep your override across future scans. `LockedFields` tells subsequent refreshes to leave those fields alone — important if you want to keep your override across future scans.
@ -165,7 +165,7 @@ TMDB falls back to English automatically when a Polish translation doesn't exist
```bash ```bash
# Fetch current options # Fetch current options
curl -s -H "X-Emby-Token: $TOKEN" https://tv.s8n.ru/Library/VirtualFolders \ curl -s -H "X-Emby-Token: $TOKEN" https://nasflix.s8n.ru/Library/VirtualFolders \
| jq '.[] | select(.Name=="TV Shows") | .LibraryOptions' > opts.json | jq '.[] | select(.Name=="TV Shows") | .LibraryOptions' > opts.json
# Modify in place (e.g. flip language to en) # Modify in place (e.g. flip language to en)
@ -175,7 +175,7 @@ jq '.PreferredMetadataLanguage="en" | .MetadataCountryCode="US"' opts.json > opt
LIB_ID=767bffe4f11c93ef34b805451a696a4e # TV Shows LIB_ID=767bffe4f11c93ef34b805451a696a4e # TV Shows
jq -n --arg id "$LIB_ID" --slurpfile o opts2.json '{Id:$id, LibraryOptions:$o[0]}' \ jq -n --arg id "$LIB_ID" --slurpfile o opts2.json '{Id:$id, LibraryOptions:$o[0]}' \
| curl -s -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \ | curl -s -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \
-d @- "https://tv.s8n.ru/Library/VirtualFolders/LibraryOptions" -d @- "https://nasflix.s8n.ru/Library/VirtualFolders/LibraryOptions"
``` ```
Returns 204. **Side effect**: changing library options can trigger a container restart cycle on this Jellyfin install — saw it in our logs at the moment of update. Schedule this when no playback is active. Returns 204. **Side effect**: changing library options can trigger a container restart cycle on this Jellyfin install — saw it in our logs at the moment of update. Schedule this when no playback is active.
@ -198,7 +198,7 @@ For our Futurama set, all files are single-episode (`s01e01.pl.mkv`), so this di
--- ---
## 8. The exact fix applied to Futurama on tv.s8n.ru (2026-05-08) ## 8. The exact fix applied to Futurama on nasflix.s8n.ru (2026-05-08)
Step-by-step, with the exact commands run: Step-by-step, with the exact commands run:
@ -208,35 +208,35 @@ SERIES_ID=156e57437f795e5c8cd80fc98bafaee0 # Futurama
LIB_ID=767bffe4f11c93ef34b805451a696a4e # TV Shows library LIB_ID=767bffe4f11c93ef34b805451a696a4e # TV Shows library
# 1. Pull current TV Shows options, flip EnableInternetProviders=true, post back. # 1. Pull current TV Shows options, flip EnableInternetProviders=true, post back.
curl -s -H "X-Emby-Token: $TOKEN" https://tv.s8n.ru/Library/VirtualFolders \ curl -s -H "X-Emby-Token: $TOKEN" https://nasflix.s8n.ru/Library/VirtualFolders \
| jq '.[] | select(.Name=="TV Shows") | .LibraryOptions' > /tmp/opts.json | jq '.[] | select(.Name=="TV Shows") | .LibraryOptions' > /tmp/opts.json
jq '.EnableInternetProviders=true' /tmp/opts.json > /tmp/opts_new.json jq '.EnableInternetProviders=true' /tmp/opts.json > /tmp/opts_new.json
jq -n --arg id "$LIB_ID" --slurpfile o /tmp/opts_new.json \ jq -n --arg id "$LIB_ID" --slurpfile o /tmp/opts_new.json \
'{Id:$id, LibraryOptions:$o[0]}' \ '{Id:$id, LibraryOptions:$o[0]}' \
| curl -s -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \ | curl -s -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \
-d @- https://tv.s8n.ru/Library/VirtualFolders/LibraryOptions -d @- https://nasflix.s8n.ru/Library/VirtualFolders/LibraryOptions
# -> 204 # -> 204
# 2. Search TMDB for Futurama (without provider hint to see all candidates). # 2. Search TMDB for Futurama (without provider hint to see all candidates).
curl -s -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \ curl -s -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \
-d '{"SearchInfo":{"Name":"Futurama"}}' \ -d '{"SearchInfo":{"Name":"Futurama"}}' \
https://tv.s8n.ru/Items/RemoteSearch/Series | jq '.[0]' https://nasflix.s8n.ru/Items/RemoteSearch/Series | jq '.[0]'
# -> first hit: TheMovieDb, Tmdb=615, PremiereDate=1999-03-28 (correct: original 1999 series) # -> first hit: TheMovieDb, Tmdb=615, PremiereDate=1999-03-28 (correct: original 1999 series)
# 3. Apply that match to the series. # 3. Apply that match to the series.
curl -s --max-time 60 -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \ curl -s --max-time 60 -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \
-d '{"Name":"Futurama","ProviderIds":{"Tmdb":"615"},"ProductionYear":1999,"SearchProviderName":"TheMovieDb"}' \ -d '{"Name":"Futurama","ProviderIds":{"Tmdb":"615"},"ProductionYear":1999,"SearchProviderName":"TheMovieDb"}' \
"https://tv.s8n.ru/Items/RemoteSearch/Apply/$SERIES_ID?ReplaceAllImages=true" "https://nasflix.s8n.ru/Items/RemoteSearch/Apply/$SERIES_ID?ReplaceAllImages=true"
# -> 204 (after first attempt 502'd at proxy because client timeout was too low) # -> 204 (after first attempt 502'd at proxy because client timeout was too low)
# 4. Trigger recursive full refresh with replace-all metadata. # 4. Trigger recursive full refresh with replace-all metadata.
curl -s --max-time 60 -X POST -H "X-Emby-Token: $TOKEN" \ curl -s --max-time 60 -X POST -H "X-Emby-Token: $TOKEN" \
"https://tv.s8n.ru/Items/$SERIES_ID/Refresh?Recursive=true&MetadataRefreshMode=FullRefresh&ImageRefreshMode=FullRefresh&ReplaceAllMetadata=true&ReplaceAllImages=false" "https://nasflix.s8n.ru/Items/$SERIES_ID/Refresh?Recursive=true&MetadataRefreshMode=FullRefresh&ImageRefreshMode=FullRefresh&ReplaceAllMetadata=true&ReplaceAllImages=false"
# -> 204 # -> 204
# 5. Wait ~3 minutes, then verify episodes have providerids and Polish titles. # 5. Wait ~3 minutes, then verify episodes have providerids and Polish titles.
curl -s -H "X-Emby-Token: $TOKEN" \ curl -s -H "X-Emby-Token: $TOKEN" \
"https://tv.s8n.ru/Items?Recursive=true&IncludeItemTypes=Episode&Fields=ProviderIds&ParentId=$SERIES_ID" \ "https://nasflix.s8n.ru/Items?Recursive=true&IncludeItemTypes=Episode&Fields=ProviderIds&ParentId=$SERIES_ID" \
| jq '[.Items[] | select(.ProviderIds | length > 0)] | length' | jq '[.Items[] | select(.ProviderIds | length > 0)] | length'
# -> 44/44 # -> 44/44
``` ```

View file

@ -1,8 +1,8 @@
# 03 — Subtitles on Jellyfin (tv.s8n.ru) # 03 — Subtitles on Jellyfin (nasflix.s8n.ru)
Last updated: 2026-05-08 Last updated: 2026-05-08
Server: Jellyfin 10.10.3 (X64) on nullstone, container `jellyfin` Server: Jellyfin 10.10.3 (X64) on nullstone, container `jellyfin`
URL: <https://tv.s8n.ru> URL: <https://nasflix.s8n.ru>
Use-case: Futurama (44 episodes), Polish audio, no embedded subs, automatic English subtitles required. Use-case: Futurama (44 episodes), Polish audio, no embedded subs, automatic English subtitles required.
--- ---
@ -67,7 +67,7 @@ When the server is upgraded to 10.11.x, switch to v24 via:
```bash ```bash
curl -s -X POST -H "X-Emby-Token: $TOKEN" \ curl -s -X POST -H "X-Emby-Token: $TOKEN" \
"https://tv.s8n.ru/Packages/Installed/Open%20Subtitles?AssemblyGuid=4b9ed42f-5185-48b5-9803-6ff2989014c4&Version=24.0.0.0&RepositoryUrl=https%3A%2F%2Frepo.jellyfin.org%2Ffiles%2Fplugin%2Fmanifest.json" "https://nasflix.s8n.ru/Packages/Installed/Open%20Subtitles?AssemblyGuid=4b9ed42f-5185-48b5-9803-6ff2989014c4&Version=24.0.0.0&RepositoryUrl=https%3A%2F%2Frepo.jellyfin.org%2Ffiles%2Fplugin%2Fmanifest.json"
docker restart jellyfin docker restart jellyfin
``` ```
@ -97,12 +97,12 @@ PASS='your-opensubtitles-com-password'
# 1. Validate (returns 200 on success, 401 on bad creds) # 1. Validate (returns 200 on success, 401 on bad creds)
curl -s -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \ curl -s -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \
-d "{\"Username\":\"$USER\",\"Password\":\"$PASS\"}" \ -d "{\"Username\":\"$USER\",\"Password\":\"$PASS\"}" \
"https://tv.s8n.ru/Jellyfin.Plugin.OpenSubtitles/ValidateLoginInfo" -w "\nHTTP %{http_code}\n" "https://nasflix.s8n.ru/Jellyfin.Plugin.OpenSubtitles/ValidateLoginInfo" -w "\nHTTP %{http_code}\n"
# 2. Persist into plugin config # 2. Persist into plugin config
curl -s -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \ curl -s -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \
-d "{\"Username\":\"$USER\",\"Password\":\"$PASS\",\"CredentialsInvalid\":false}" \ -d "{\"Username\":\"$USER\",\"Password\":\"$PASS\",\"CredentialsInvalid\":false}" \
"https://tv.s8n.ru/Plugins/4b9ed42f-5185-48b5-9803-6ff2989014c4/Configuration" -w "\nHTTP %{http_code}\n" "https://nasflix.s8n.ru/Plugins/4b9ed42f-5185-48b5-9803-6ff2989014c4/Configuration" -w "\nHTTP %{http_code}\n"
``` ```
Or via UI: `Dashboard → Plugins → Open Subtitles → Settings`. Or via UI: `Dashboard → Plugins → Open Subtitles → Settings`.
@ -167,7 +167,7 @@ EP=2b73bc176fbf8a02bb9bea9015ec13c6
# Query providers # Query providers
curl -s -H "X-Emby-Token: $TOKEN" \ curl -s -H "X-Emby-Token: $TOKEN" \
"https://tv.s8n.ru/Items/$EP/RemoteSearch/Subtitles/eng" | jq . "https://nasflix.s8n.ru/Items/$EP/RemoteSearch/Subtitles/eng" | jq .
# Returns array of SubtitleInfo objects: Id, ProviderName, Format, Comment, IsHashMatch, ... # Returns array of SubtitleInfo objects: Id, ProviderName, Format, Comment, IsHashMatch, ...
# Pick one, e.g. SUBID = first result's Id # Pick one, e.g. SUBID = first result's Id
@ -175,7 +175,7 @@ SUBID="opensubtitles_..."
# Download + save next to media # Download + save next to media
curl -s -X POST -H "X-Emby-Token: $TOKEN" \ curl -s -X POST -H "X-Emby-Token: $TOKEN" \
"https://tv.s8n.ru/Items/$EP/RemoteSearch/Subtitles/$SUBID" -w "HTTP %{http_code}\n" "https://nasflix.s8n.ru/Items/$EP/RemoteSearch/Subtitles/$SUBID" -w "HTTP %{http_code}\n"
# 204 = saved. File appears as Futurama.s01e01.pl.eng.srt next to the mkv. # 204 = saved. File appears as Futurama.s01e01.pl.eng.srt next to the mkv.
``` ```
@ -188,7 +188,7 @@ Easiest is a series-level refresh once creds are entered:
```bash ```bash
SERIES=156e57437f795e5c8cd80fc98bafaee0 # Futurama SERIES=156e57437f795e5c8cd80fc98bafaee0 # Futurama
curl -s -X POST -H "X-Emby-Token: $TOKEN" \ curl -s -X POST -H "X-Emby-Token: $TOKEN" \
"https://tv.s8n.ru/Items/$SERIES/Refresh?MetadataRefreshMode=FullRefresh&ImageRefreshMode=Default&ReplaceAllMetadata=false&ReplaceAllImages=false&Recursive=true" \ "https://nasflix.s8n.ru/Items/$SERIES/Refresh?MetadataRefreshMode=FullRefresh&ImageRefreshMode=Default&ReplaceAllMetadata=false&ReplaceAllImages=false&Recursive=true" \
-w "HTTP %{http_code}\n" -w "HTTP %{http_code}\n"
``` ```
@ -240,7 +240,7 @@ Examples for `Futurama.s01e01.pl.mkv`:
After dropping sidecars, trigger a library scan: After dropping sidecars, trigger a library scan:
```bash ```bash
curl -s -X POST -H "X-Emby-Token: $TOKEN" "https://tv.s8n.ru/Library/Refresh" curl -s -X POST -H "X-Emby-Token: $TOKEN" "https://nasflix.s8n.ru/Library/Refresh"
``` ```
Or per-item: `POST /Items/{id}/Refresh`. Or per-item: `POST /Items/{id}/Refresh`.

View file

@ -1,6 +1,6 @@
# 04 — Theming and Users # 04 — Theming and Users
Status: applied 2026-05-08 against `https://tv.s8n.ru` (Jellyfin 10.10.3 on nullstone). Status: applied 2026-05-08 against `https://nasflix.s8n.ru` (Jellyfin 10.10.3 on nullstone).
Scope: visual theme, server-side branding, multi-user UX prep, SyncPlay, Scope: visual theme, server-side branding, multi-user UX prep, SyncPlay,
maintenance/revert. LAN-only constraints preserved (no public-facing changes). maintenance/revert. LAN-only constraints preserved (no public-facing changes).
@ -59,7 +59,7 @@ TOKEN=*redacted*
cat > /tmp/branding.json <<'EOF' cat > /tmp/branding.json <<'EOF'
{ {
"LoginDisclaimer": "Welcome to tv.s8n.ru — LAN-only. Be kind, rewind.", "LoginDisclaimer": "Welcome to nasflix.s8n.ru — LAN-only. Be kind, rewind.",
"CustomCss": "/* ElegantFin v25.12.31 — Jellyseerr-inspired Netflix-y theme */\n@import url(\"https://cdn.jsdelivr.net/gh/lscambo13/ElegantFin@main/Theme/ElegantFin-jellyfin-theme-build-latest-minified.css\");\n", "CustomCss": "/* ElegantFin v25.12.31 — Jellyseerr-inspired Netflix-y theme */\n@import url(\"https://cdn.jsdelivr.net/gh/lscambo13/ElegantFin@main/Theme/ElegantFin-jellyfin-theme-build-latest-minified.css\");\n",
"SplashscreenEnabled": true "SplashscreenEnabled": true
} }
@ -69,7 +69,7 @@ curl -sS -X POST \
-H "X-Emby-Token: $TOKEN" \ -H "X-Emby-Token: $TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
--data-binary @/tmp/branding.json \ --data-binary @/tmp/branding.json \
https://tv.s8n.ru/System/Configuration/branding https://nasflix.s8n.ru/System/Configuration/branding
# expect: HTTP 204 # expect: HTTP 204
``` ```
@ -77,19 +77,19 @@ curl -sS -X POST \
```bash ```bash
curl -sS -H "X-Emby-Token: $TOKEN" \ curl -sS -H "X-Emby-Token: $TOKEN" \
https://tv.s8n.ru/System/Configuration/branding | python3 -m json.tool https://nasflix.s8n.ru/System/Configuration/branding | python3 -m json.tool
# Should include the @import line and the disclaimer text. # Should include the @import line and the disclaimer text.
# Public branding endpoint the SPA reads at runtime — confirms anonymous # Public branding endpoint the SPA reads at runtime — confirms anonymous
# clients (i.e. the browser before login) will see the theme: # clients (i.e. the browser before login) will see the theme:
curl -sS https://tv.s8n.ru/Branding/Configuration | python3 -m json.tool curl -sS https://nasflix.s8n.ru/Branding/Configuration | python3 -m json.tool
``` ```
`/` returns Jellyfin's SPA shell; the theme CSS is fetched **at runtime** `/` returns Jellyfin's SPA shell; the theme CSS is fetched **at runtime**
by the JS bundle from `/Branding/Configuration`, not inlined into by the JS bundle from `/Branding/Configuration`, not inlined into
`index.html`. So `curl /` won't grep-match. The valid JSON at `index.html`. So `curl /` won't grep-match. The valid JSON at
`/Branding/Configuration` is the API-level confirmation. Final check is a `/Branding/Configuration` is the API-level confirmation. Final check is a
hard browser reload (cache-bust) on `https://tv.s8n.ru` from the LAN. hard browser reload (cache-bust) on `https://nasflix.s8n.ru` from the LAN.
### Cache clear ### Cache clear
@ -104,7 +104,7 @@ Jellyfin web caches aggressively in browsers. After applying:
| Field | Value | | Field | Value |
|---|---| |---|---|
| `LoginDisclaimer` | "Welcome to tv.s8n.ru — LAN-only. Be kind, rewind." | | `LoginDisclaimer` | "Welcome to nasflix.s8n.ru — LAN-only. Be kind, rewind." |
| `CustomCss` | `@import` of ElegantFin v25.12.31 from jsDelivr (autoupdating off `@main`) | | `CustomCss` | `@import` of ElegantFin v25.12.31 from jsDelivr (autoupdating off `@main`) |
| `SplashscreenEnabled` | `true` | | `SplashscreenEnabled` | `true` |
@ -156,7 +156,7 @@ NEW_USER=$(curl -sS -X POST \
-H "X-Emby-Token: $TOKEN" \ -H "X-Emby-Token: $TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"Name":"friend","Password":"<initial-password>"}' \ -d '{"Name":"friend","Password":"<initial-password>"}' \
https://tv.s8n.ru/Users/New) https://nasflix.s8n.ru/Users/New)
echo "$NEW_USER" | python3 -m json.tool echo "$NEW_USER" | python3 -m json.tool
NEW_ID=$(echo "$NEW_USER" | python3 -c "import sys,json; print(json.load(sys.stdin)['Id'])") NEW_ID=$(echo "$NEW_USER" | python3 -c "import sys,json; print(json.load(sys.stdin)['Id'])")
echo "NEW_ID=$NEW_ID" echo "NEW_ID=$NEW_ID"
@ -198,12 +198,12 @@ curl -sS -X POST \
-H "X-Emby-Token: $TOKEN" \ -H "X-Emby-Token: $TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
--data-binary @/tmp/policy.json \ --data-binary @/tmp/policy.json \
"https://tv.s8n.ru/Users/$NEW_ID/Policy" "https://nasflix.s8n.ru/Users/$NEW_ID/Policy"
# expect: HTTP 204 # expect: HTTP 204
# 3. Verify # 3. Verify
curl -sS -H "X-Emby-Token: $TOKEN" \ curl -sS -H "X-Emby-Token: $TOKEN" \
"https://tv.s8n.ru/Users/$NEW_ID" | python3 -m json.tool "https://nasflix.s8n.ru/Users/$NEW_ID" | python3 -m json.tool
``` ```
### 4e. Policy field cheat-sheet ### 4e. Policy field cheat-sheet
@ -238,7 +238,7 @@ curl -sS -X POST \
-H "X-Emby-Token: $TOKEN" \ -H "X-Emby-Token: $TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"NewPw":"<new-password>","ResetPassword":false}' \ -d '{"NewPw":"<new-password>","ResetPassword":false}' \
"https://tv.s8n.ru/Users/$USER_ID/Password" "https://nasflix.s8n.ru/Users/$USER_ID/Password"
``` ```
To clear the password entirely (forces friend to set one on next login): To clear the password entirely (forces friend to set one on next login):
@ -303,7 +303,7 @@ Verified current state: `s8n.SyncPlayAccess = CreateAndJoinGroups` ✓.
1. s8n opens a series episode and starts playing. 1. s8n opens a series episode and starts playing.
2. Player overlay → top-right people-icon ("SyncPlay") → "Create group". 2. Player overlay → top-right people-icon ("SyncPlay") → "Create group".
3. Friend logs in (any device — same `tv.s8n.ru`), opens the same item 3. Friend logs in (any device — same `nasflix.s8n.ru`), opens the same item
or the SyncPlay menu → "Join {s8n}'s group". or the SyncPlay menu → "Join {s8n}'s group".
4. Anyone in the group's play/pause/seek is mirrored within ~1 second. 4. Anyone in the group's play/pause/seek is mirrored within ~1 second.
5. Voice chat is up to you — Jellyfin doesn't bundle one (Matrix room 5. Voice chat is up to you — Jellyfin doesn't bundle one (Matrix room
@ -326,8 +326,8 @@ upstream commits propagate after jsDelivr's cache TTL (12h s-maxage,
# Force refresh by pinning to a specific tag, then back to main: # Force refresh by pinning to a specific tag, then back to main:
curl -sS -X POST -H "X-Emby-Token: $TOKEN" \ curl -sS -X POST -H "X-Emby-Token: $TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"CustomCss": "@import url(\"https://cdn.jsdelivr.net/gh/lscambo13/ElegantFin@v25.12.31/Theme/ElegantFin-jellyfin-theme-build-latest-minified.css\");", "LoginDisclaimer": "Welcome to tv.s8n.ru — LAN-only. Be kind, rewind.", "SplashscreenEnabled": true}' \ -d '{"CustomCss": "@import url(\"https://cdn.jsdelivr.net/gh/lscambo13/ElegantFin@v25.12.31/Theme/ElegantFin-jellyfin-theme-build-latest-minified.css\");", "LoginDisclaimer": "Welcome to nasflix.s8n.ru — LAN-only. Be kind, rewind.", "SplashscreenEnabled": true}' \
https://tv.s8n.ru/System/Configuration/branding https://nasflix.s8n.ru/System/Configuration/branding
``` ```
Or just ask each user to hard-reload — their browser cache is the Or just ask each user to hard-reload — their browser cache is the
@ -346,7 +346,7 @@ Empty out the CustomCss field via API:
curl -sS -X POST -H "X-Emby-Token: $TOKEN" \ curl -sS -X POST -H "X-Emby-Token: $TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"CustomCss": "", "LoginDisclaimer": "", "SplashscreenEnabled": false}' \ -d '{"CustomCss": "", "LoginDisclaimer": "", "SplashscreenEnabled": false}' \
https://tv.s8n.ru/System/Configuration/branding https://nasflix.s8n.ru/System/Configuration/branding
``` ```
Or in the UI: Dashboard → General → clear "Custom CSS code" → Save. Or in the UI: Dashboard → General → clear "Custom CSS code" → Save.
@ -383,7 +383,7 @@ When the friend gets their account, walk them through this **once**:
6. **SyncPlay test**: friend in one tab, s8n in another, friend joins 6. **SyncPlay test**: friend in one tab, s8n in another, friend joins
s8n's group, confirm play/pause syncs. (Drops the "do you have it s8n's group, confirm play/pause syncs. (Drops the "do you have it
running" question forever.) running" question forever.)
7. **Mobile/TV**: install Jellyfin app, server URL `https://tv.s8n.ru` 7. **Mobile/TV**: install Jellyfin app, server URL `https://nasflix.s8n.ru`
(must be on LAN or Tailscale), Quick Connect or password. (must be on LAN or Tailscale), Quick Connect or password.
8. **Bookmarks/RSS**: there isn't one — Jellyfin's "Latest" row is the 8. **Bookmarks/RSS**: there isn't one — Jellyfin's "Latest" row is the
substitute. Friend can favourite shows (heart icon) to pin. substitute. Friend can favourite shows (heart icon) to pin.

View file

@ -1,4 +1,4 @@
# 05 — File & Folder Structure Rules (tv.s8n.ru) # 05 — File & Folder Structure Rules (nasflix.s8n.ru)
Last updated: 2026-05-08 Last updated: 2026-05-08
Server: Jellyfin 10.10.3 on nullstone, container `jellyfin` Server: Jellyfin 10.10.3 on nullstone, container `jellyfin`
@ -970,7 +970,7 @@ used at library creation.)
```bash ```bash
TOKEN=*redacted* TOKEN=*redacted*
H="-H \"X-Emby-Token: ${TOKEN}\"" H="-H \"X-Emby-Token: ${TOKEN}\""
B="https://tv.s8n.ru" B="https://nasflix.s8n.ru"
# Movies library # Movies library
curl -s -X POST $H "$B/Library/VirtualFolders?name=Movies&collectionType=movies" \ curl -s -X POST $H "$B/Library/VirtualFolders?name=Movies&collectionType=movies" \
@ -1007,17 +1007,13 @@ Jellyfin library per category.
``` ```
/home/user/media/ /home/user/media/
├── movies/ ← collectionType: movies ├── movies/ ← collectionType: movies
├── tv/ ← collectionType: tvshows └── tv/ ← collectionType: tvshows
├── anime/ ← collectionType: tvshows (separate library)
├── musicvideos/ ← collectionType: musicvideos
├── docs-movies/ ← collectionType: movies (future, optional)
└── docs-tv/ ← collectionType: tvshows (future, optional)
``` ```
> `home/` and `music/` libraries removed from the canonical layout per owner > NASFLIX scope locked 2026-05-08: TV Shows + Movies only. `anime/`,
> decision 2026-05-08. Sections 6 (Music) and the Home-videos category remain > `musicvideos/`, `home/`, `music/`, `docs-*/` libraries removed. Sections in
> in this doc as reference for re-introduction later — just `mkdir` + add > this doc covering anime/music/etc. remain as reference for the day scope is
> library via API when needed. > revisited — just `mkdir` + add library via API when needed.
### 13.2 Why Architecture A (not B or C) ### 13.2 Why Architecture A (not B or C)
@ -1045,8 +1041,6 @@ or client-facing change needed at migration time.
**A wins because:** **A wins because:**
- One library per `collectionType` is the simplest correct mapping. - One library per `collectionType` is the simplest correct mapping.
- Anime as its own library lets us set provider order and language
preference independently from `tv/`.
### 13.3 Concrete mkdir commands ### 13.3 Concrete mkdir commands
@ -1056,9 +1050,7 @@ by `user:user`):
```bash ```bash
ssh user@192.168.0.100 'mkdir -p \ ssh user@192.168.0.100 'mkdir -p \
/home/user/media/movies \ /home/user/media/movies \
/home/user/media/tv \ /home/user/media/tv'
/home/user/media/anime \
/home/user/media/musicvideos'
``` ```
Verify: Verify:
@ -1076,20 +1068,13 @@ under `/media/<name>`:
volumes: volumes:
- /home/user/media/movies:/media/movies:ro - /home/user/media/movies:/media/movies:ro
- /home/user/media/tv:/media/tv:ro - /home/user/media/tv:/media/tv:ro
- /home/user/media/anime:/media/anime:ro
- /home/user/media/musicvideos:/media/musicvideos:ro
``` ```
The current compose only mounts `movies` and `tv`; extend it before adding
the new libraries via API.
### 13.5 Initial state after applying ### 13.5 Initial state after applying
``` ```
Movies library → /media/movies (empty, ready) Movies library → /media/movies (empty, ready)
TV Shows library → /media/tv (Futurama 1999, S01-S03, 44 eps) TV Shows library → /media/tv (Futurama 1999, S01-S04, 72 eps + 9 featurettes)
Anime library → /media/anime (empty, ready)
Music Videos lib → /media/musicvideos (empty, ready)
``` ```
--- ---
@ -1105,7 +1090,7 @@ Before declaring a new addition "done":
5. Per-item folder exists (no loose files in library root, except music videos). 5. Per-item folder exists (no loose files in library root, except music videos).
6. `tvshow.nfo` / `movie.nfo` exists IFF you needed to override the scraper. 6. `tvshow.nfo` / `movie.nfo` exists IFF you needed to override the scraper.
7. Subtitles use `<basename>.<lang>.srt` (doc 03). 7. Subtitles use `<basename>.<lang>.srt` (doc 03).
8. Scan: `curl -s -X POST -H "X-Emby-Token: $TOKEN" https://tv.s8n.ru/Library/Refresh`. 8. Scan: `curl -s -X POST -H "X-Emby-Token: $TOKEN" https://nasflix.s8n.ru/Library/Refresh`.
9. Wait ~30 s, check item via `/Items?searchTerm=...` — verify `ProviderIds` 9. Wait ~30 s, check item via `/Items?searchTerm=...` — verify `ProviderIds`
is populated. Empty `ProviderIds` = filename didn't disambiguate; doc 02 is populated. Empty `ProviderIds` = filename didn't disambiguate; doc 02
§ 5 has the manual-lock recipe. § 5 has the manual-lock recipe.

View file

@ -1,6 +1,6 @@
# 06 — Per-Library Themes (Movies = Netflix, Anime = Crunchyroll, Music = Spotify) # 06 — Per-Library Themes (Movies = Netflix, Anime = Crunchyroll, Music = Spotify)
> **Scope of this doc:** research only. No live changes. Targets Jellyfin **10.10.3** at https://tv.s8n.ru > **Scope of this doc:** research only. No live changes. Targets Jellyfin **10.10.3** at https://nasflix.s8n.ru
> with the current global theme **ElegantFin v25.12.31** in `/System/Configuration/branding` `CustomCss`. > with the current global theme **ElegantFin v25.12.31** in `/System/Configuration/branding` `CustomCss`.
--- ---
@ -34,7 +34,7 @@ or using user-policy library hiding.
| 2 | **JS shim → body class → scoped CSS** | **Yes** — URL hash includes `topParentId`+`collectionType`, easy to mirror onto body | **Low** — ~30 lines of JS, stable across upgrades because it consumes URL params, not DOM internals | **Good** (8/10) — full CSS variable + layout override per library; falls short of perfect brand mimicry (fonts, motion design) | Sub-100ms class flip on hashchange; no flicker if rules use the right specificity | | 2 | **JS shim → body class → scoped CSS** | **Yes** — URL hash includes `topParentId`+`collectionType`, easy to mirror onto body | **Low** — ~30 lines of JS, stable across upgrades because it consumes URL params, not DOM internals | **Good** (8/10) — full CSS variable + layout override per library; falls short of perfect brand mimicry (fonts, motion design) | Sub-100ms class flip on hashchange; no flicker if rules use the right specificity |
| 3 | Per-library `Branding`/CustomCss via API | **No**`LibraryOptions` schema has no CustomCss / theme field. Confirmed against `/Library/VirtualFolders` response. | n/a | n/a | n/a | | 3 | Per-library `Branding`/CustomCss via API | **No**`LibraryOptions` schema has no CustomCss / theme field. Confirmed against `/Library/VirtualFolders` response. | n/a | n/a | n/a |
| 4 | Existing community plugin promising per-library theming | **No** — none exists. `Skin Manager`, `JellySkin`, `ElegantFin`, `Jellyfish`, `JellyFlix`, `DarkFlix` are all server-wide. Closest building block: `Jellyfin-JavaScript-Injector` (plugin route to deliver approach #2). | Low if used as injector for #2 | Same as #2 | Same as #2 | | 4 | Existing community plugin promising per-library theming | **No** — none exists. `Skin Manager`, `JellySkin`, `ElegantFin`, `Jellyfish`, `JellyFlix`, `DarkFlix` are all server-wide. Closest building block: `Jellyfin-JavaScript-Injector` (plugin route to deliver approach #2). | Low if used as injector for #2 | Same as #2 | Same as #2 |
| 5 | Subdomain split — `movies.tv.s8n.ru`, `anime.tv.s8n.ru`, `music.tv.s8n.ru` (3 Jellyfin containers) | **Yes** — straightforward Traefik + 3 stacks | **High** — 3× DBs, 3× scans, 3× upgrades, user accounts to sync | **Perfect (10/10)** — each instance is just a normal Jellyfin with one global theme | Users must bookmark/jump between subdomains; no unified library | | 5 | Subdomain split — `movies.nasflix.s8n.ru`, `anime.nasflix.s8n.ru`, `music.nasflix.s8n.ru` (3 Jellyfin containers) | **Yes** — straightforward Traefik + 3 stacks | **High** — 3× DBs, 3× scans, 3× upgrades, user accounts to sync | **Perfect (10/10)** — each instance is just a normal Jellyfin with one global theme | Users must bookmark/jump between subdomains; no unified library |
### Why approach #1 fails ### Why approach #1 fails
@ -273,7 +273,7 @@ Total ongoing burden: ~1 hour/year. Compared with running 3 separate Jellyfin in
Choose subdomain split if **any** of these are true: Choose subdomain split if **any** of these are true:
- You want true Netflix UX (autoplay trailers on hover, exact card geometry, top-10 row, "skip - You want true Netflix UX (autoplay trailers on hover, exact card geometry, top-10 row, "skip
intro" branded affordances) — CSS alone cannot deliver these regardless of approach. intro" branded affordances) — CSS alone cannot deliver these regardless of approach.
- You want fully isolated user accounts per "service" (e.g. kid account on `anime.tv.s8n.ru` cannot - You want fully isolated user accounts per "service" (e.g. kid account on `anime.nasflix.s8n.ru` cannot
see movies subdomain at all). see movies subdomain at all).
- You're prepared to either (a) duplicate libraries (3× disk metadata, 3× scans) or (b) maintain a - You're prepared to either (a) duplicate libraries (3× disk metadata, 3× scans) or (b) maintain a
per-user library policy on a single backend that mirrors content into 3 frontend instances — per-user library policy on a single backend that mirrors content into 3 frontend instances —

View file

@ -1,4 +1,4 @@
# 07 — Pre-Import Cleanup Ruleset (tv.s8n.ru) # 07 — Pre-Import Cleanup Ruleset (nasflix.s8n.ru)
Last updated: 2026-05-08 Last updated: 2026-05-08
Server: Jellyfin 10.10.3 on nullstone, container `jellyfin` Server: Jellyfin 10.10.3 on nullstone, container `jellyfin`
@ -705,11 +705,11 @@ or a cryptominer. Auto-delete, no questions asked.
Idempotent. Dry-run by default. Quarantine-first. Source-immutable. Idempotent. Dry-run by default. Quarantine-first. Source-immutable.
Returns the staging path on stdout for piping to doc 08's normalizer. Returns the staging path on stdout for piping to doc 08's normalizer.
Save to `bin/cleanup-import.sh` in the `jellyfin-stack` repo. Save to `bin/cleanup-import.sh` in the `NASFLIX` repo.
```bash ```bash
#!/usr/bin/env bash #!/usr/bin/env bash
# cleanup-import.sh — Pre-import cleanup for tv.s8n.ru # cleanup-import.sh — Pre-import cleanup for nasflix.s8n.ru
# Version 1.0 (2026-05-08) — see docs/07-pre-import-cleanup.md # Version 1.0 (2026-05-08) — see docs/07-pre-import-cleanup.md
# #
# Usage: # Usage:

View file

@ -1,4 +1,4 @@
# 08 — Filename & Folder Normalization Ruleset (tv.s8n.ru) # 08 — Filename & Folder Normalization Ruleset (nasflix.s8n.ru)
Last updated: 2026-05-08 Last updated: 2026-05-08
Server: Jellyfin 10.10.3 on nullstone, container `jellyfin` Server: Jellyfin 10.10.3 on nullstone, container `jellyfin`
@ -1195,9 +1195,9 @@ Run with Python 3.10+. Stdlib only — no external deps.
```python ```python
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
normalize.py — canonical filename normalizer for tv.s8n.ru normalize.py — canonical filename normalizer for nasflix.s8n.ru
Per /tmp/jellyfin-stack/docs/08-filename-normalization.md. Per /tmp/NASFLIX/docs/08-filename-normalization.md.
Safe by default: dry-run, no overwrite, no delete. Safe by default: dry-run, no overwrite, no delete.
""" """