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).
This commit is contained in:
s8n 2026-05-08 02:57:34 +01:00
parent cb95dce8bc
commit 1f5ba31483
14 changed files with 234 additions and 144 deletions

View file

@ -1,4 +1,4 @@
# Admin Guide — nasflix.s8n.ru # Admin Guide — arrflix.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.
@ -15,7 +15,7 @@ What's next on this deploy → [`ROADMAP.md`](ROADMAP.md).
| Config + cache | `nullstone:/home/docker/jellyfin/{config,cache}/` | | Config + cache | `nullstone:/home/docker/jellyfin/{config,cache}/` |
| Media root | `nullstone:/home/user/media/{movies,tv}/` | | 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 nasflix.s8n.ru` | | Pi-hole DNS pin | `/opt/docker/pihole/etc-pihole/custom.list``192.168.0.100 arrflix.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://nasflix.s8n.ru/Library/Refresh`. 6. Trigger refresh: `curl -X POST -H "Authorization: MediaBrowser Token=$TOKEN" https://arrflix.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/NASFLIX` 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/ARRFLIX` 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.** `nasflix.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.** `arrflix.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 @@
# NASFLIX # ARRFLIX
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://nasflix.s8n.ru` — accessible only from LAN (192.168.0.0/24) and Tailscale admin/infra tags via Traefik `no-guest@file` middleware. - `https://arrflix.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://nasflix.s8n.ru` from the LAN. 1. Browse to `https://arrflix.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 — NASFLIX # Roadmap — ARRFLIX
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 `nasflix.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 `arrflix.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 `nasflix.s8n.ru` wordmark + 4-bar pulse spinner - Replace Jellyfin pre-bundle logo with `arrflix.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)

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

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://nasflix.s8n.ru}" JELLYFIN_URL="${JELLYFIN_URL:-https://arrflix.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: nasflix.s8n.ru (LAN-only via Pi-hole local DNS + no-guest middleware) # Domain: arrflix.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://nasflix.s8n.ru - JELLYFIN_PublishedServerUrl=https://arrflix.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(`nasflix.s8n.ru`)" - "traefik.http.routers.jellyfin.rule=Host(`arrflix.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://nasflix.s8n.ru` (Jellyfin `10.10.3`, container `jellyfin` on Server: `https://arrflix.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://nasflix.s8n.ru" BASE="https://arrflix.s8n.ru"
``` ```
--- ---

View file

@ -1,6 +1,6 @@
# Jellyfin Metadata & Episode Titles — Operator Guide # Jellyfin Metadata & Episode Titles — Operator Guide
Server: `https://nasflix.s8n.ru` (Jellyfin 10.10.3, container on nullstone) Server: `https://arrflix.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://nasflix.s8n.ru/Items/RemoteSearch/Series https://arrflix.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://nasflix.s8n.ru/Items/RemoteSearch/Apply/$SERIES_ID?ReplaceAllImages=true" "https://arrflix.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://nasflix.s8n.ru/Items/$SERIES_ID/Refresh?Recursive=true&MetadataRefreshMode=FullRefresh&ImageRefreshMode=FullRefresh&ReplaceAllMetadata=true&ReplaceAllImages=false" "https://arrflix.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://nasflix.s8n.ru/Items?Recursive=true&IncludeItemTypes=Episode&Fields=ProviderIds&ParentId=$SERIES_ID&Limit=44" \ "https://arrflix.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://nasflix.s8n.ru/Items/$EP_ID https://arrflix.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://nasflix.s8n.ru/Library/VirtualFolders \ curl -s -H "X-Emby-Token: $TOKEN" https://arrflix.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://nasflix.s8n.ru/Library/VirtualFolders/LibraryOptions" -d @- "https://arrflix.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 nasflix.s8n.ru (2026-05-08) ## 8. The exact fix applied to Futurama on arrflix.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://nasflix.s8n.ru/Library/VirtualFolders \ curl -s -H "X-Emby-Token: $TOKEN" https://arrflix.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://nasflix.s8n.ru/Library/VirtualFolders/LibraryOptions -d @- https://arrflix.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://nasflix.s8n.ru/Items/RemoteSearch/Series | jq '.[0]' https://arrflix.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://nasflix.s8n.ru/Items/RemoteSearch/Apply/$SERIES_ID?ReplaceAllImages=true" "https://arrflix.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://nasflix.s8n.ru/Items/$SERIES_ID/Refresh?Recursive=true&MetadataRefreshMode=FullRefresh&ImageRefreshMode=FullRefresh&ReplaceAllMetadata=true&ReplaceAllImages=false" "https://arrflix.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://nasflix.s8n.ru/Items?Recursive=true&IncludeItemTypes=Episode&Fields=ProviderIds&ParentId=$SERIES_ID" \ "https://arrflix.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 (nasflix.s8n.ru) # 03 — Subtitles on Jellyfin (arrflix.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://nasflix.s8n.ru> URL: <https://arrflix.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://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" "https://arrflix.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://nasflix.s8n.ru/Jellyfin.Plugin.OpenSubtitles/ValidateLoginInfo" -w "\nHTTP %{http_code}\n" "https://arrflix.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://nasflix.s8n.ru/Plugins/4b9ed42f-5185-48b5-9803-6ff2989014c4/Configuration" -w "\nHTTP %{http_code}\n" "https://arrflix.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://nasflix.s8n.ru/Items/$EP/RemoteSearch/Subtitles/eng" | jq . "https://arrflix.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://nasflix.s8n.ru/Items/$EP/RemoteSearch/Subtitles/$SUBID" -w "HTTP %{http_code}\n" "https://arrflix.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://nasflix.s8n.ru/Items/$SERIES/Refresh?MetadataRefreshMode=FullRefresh&ImageRefreshMode=Default&ReplaceAllMetadata=false&ReplaceAllImages=false&Recursive=true" \ "https://arrflix.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://nasflix.s8n.ru/Library/Refresh" curl -s -X POST -H "X-Emby-Token: $TOKEN" "https://arrflix.s8n.ru/Library/Refresh"
``` ```
Or per-item: `POST /Items/{id}/Refresh`. Or per-item: `POST /Items/{id}/Refresh`.

View file

@ -1,95 +1,169 @@
# 04 — Theming and Users # 04 — Theming and Users
Status: applied 2026-05-08 against `https://nasflix.s8n.ru` (Jellyfin 10.10.3 on nullstone). Status: re-themed 2026-05-08 against `https://arrflix.s8n.ru` (Jellyfin 10.10.3
on nullstone). Active theme: **Cineplex v1.0.6** (Netflix-faithful). Replaced
ElegantFin v25.12.31 the same day after a Netflix-fidelity-driven survey.
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).
> Hostname note: this site is being renamed `tv.s8n.ru``arrflix.s8n.ru`
> in the same session. The Jellyfin API endpoints don't care about
> hostname — they're served by the same container. All `curl` examples
> below are reachable as either `https://tv.s8n.ru/...` (legacy) or
> `https://arrflix.s8n.ru/...` (new), as long as Traefik has a SNI cert
> for the name. Internal pin: both names should resolve to `192.168.0.100`
> (see CLAUDE.md memory `feedback_s8n_hosts_override.md`). If a
> hostname's DNS or cert isn't up yet, use
> `--resolve tv.s8n.ru:443:192.168.0.100` on curl — that's how this
> re-theming was applied while `arrflix.s8n.ru` was still missing a cert.
--- ---
## 1. Theme decision: ElegantFin ## 1. Theme decision: Cineplex v1.0.6 (Netflix-faithful)
### Candidates surveyed ### Candidates surveyed (2026-05-08)
| Theme | Type | Maintenance (May 2026) | Notes | | Theme | Type | Last commit | License | Netflix-fidelity | JF 10.10.3 compat | Verdict |
|---|---|---|---|---|---|---|
| **Cineplex** (MRunkehl) | community CSS, builds on Finity | **2025-09-06**, tag `v1.0.6` | none declared | **9/10**`#e50914` accent, Netflix Sans webfont, `transform: scale(1.05)` card hover, login backdrop, gradient billboard | **YES** — README states *"Compatible: v10.10.7 and higher"*; renders on 10.10.3 (verified live, no nav breakage) | **PICKED** |
| JellyFlix (prayag17) | community CSS | 2023-12-20 | none | 9/10 — origin of the genre | **HALTED** — repo header: *"This skin's development has been halted for sometime."* Confirmed broken post-10.11. Risky on 10.10.3 too. | rejected |
| DarkFlix v5.1 (DevilsDesigns) | fork of JellyFlix | 2024-06 | GPL-3.0 | 8/10 | only states 10.8.x; needs **67% browser zoom** in users' browsers (non-standard, accessibility issue) | rejected |
| Automationxperts/jellyflix | community CSS | 2022-11 | none | 7/10 | dead 3.5y, untested on 10.10 | rejected |
| **ElegantFin v25.12.31** (lscambo13, previous) | community CSS | 2026-04-30 | GPL-2.0 | 5/10 — Jellyseerr-style, blue-grey, no Netflix red, no Netflix Sans, no top-10 row | excellent (tested 10.11.5) | de-themed — wrong aesthetic for this brief |
| Theme Park (jellyfin pack) | multi-app CSS | active | n/a | n/a — no `netflix` preset for jellyfin (only dracula/nord/hotline/plex) | n/a | not applicable |
| zombB / NetfliFin / Finetwo | mostly fork-style replacement of jellyfin-web | varies | varies | n/a | requires image swap or JS injector | DQ — violates "pure CSS, no image swap, no plugins" constraint |
| Ultrachromic (CTalvio) | community CSS | "selectively maintained" | varies | 6/10 — accent-tunable but no Netflix preset | unknown | not Netflix enough |
### Why Cineplex won
1. **It is actually Netflix.** The CSS literally embeds Netflix Sans
(`https://assets.nflxext.com/ffe/siteui/fonts/netflix-sans/v3/...`) and
uses Netflix's exact `#E50914` for `--accent` / `--focus-color`. Card
hover applies `transform: scale(1.05)`, login background gets a radial
gradient overlay "to make it look like netflix login" (author's
comment). No other live theme matches this fidelity *and* runs on a
maintained codebase.
2. **It targets our Jellyfin series.** Header line of `cineplex.css`
reads `Compatible: v10.10.7 and higher`. We're on 10.10.3 — same
minor series, ABI-compatible for selectors. Verified live: page
loads, navigation works, no broken layouts.
3. **Single `@import` line.** Zero ops overhead. The CSS imports two
transitive deps internally (`finity-complete.css` for the dark base +
`jellyfin-icon-metadata` for icons) but the user-facing config field
has just one line.
4. **Pinned to immutable tag** `@v1.0.6`. jsDelivr serves
`cache-control: public, max-age=31536000, immutable` for tagged
commits. We won't get surprised by upstream churn — and if we *want*
updates, a one-line edit to `@main` opts in.
5. **Companion `cineplex.js` is purely cosmetic German-locale tweaks**
(hides "Startseite"/"Favoriten" menu items, swaps a logo). Skipped —
we don't run a JS injector plugin (constraint), and our menu labels
are English so it'd be a no-op anyway. Theme works fine without it.
6. **Cast/crew hide rule still appended** at the bottom of `CustomCss`,
exactly as before.
### Tradeoffs (honest list)
- **License: none.** Cineplex doesn't declare one. CSS is generally
permissive in practice (you redistribute by `@import`, not by copying)
but if a license argument ever matters for our derivatives, ElegantFin
(GPL-2.0) is cleaner.
- **Bus factor: 1.** Single author (Maverick Runkehl), 0 stars, last
commit Sep 2025. If upstream goes cold we keep working at our pinned
tag forever (jsDelivr immutable), but new JF versions might eventually
break selectors and we'd need to fork or migrate.
- **Netflix Sans license note.** The font files are loaded from
Netflix's own CDN, not bundled. If Netflix changes that path or rate-
limits non-netflix.com referers, we'd fall back to system-ui (also
declared in the stack). Acceptable.
- **Theme footer.** Cineplex doesn't add a brand stamp, so users see no
"Cineplex" tag — cleaner than ElegantFin's footer label was.
### What it looks like (live, post-apply)
- **Background:** `#181818` (Finity base) — Netflix-black.
- **Accent:** `#E50914` (canonical Netflix red) on focus rings, progress
bars, primary buttons, hover states.
- **Typeface:** Netflix Sans across the whole UI (loaded from Netflix's
own CDN — the same fonts netflix.com itself serves). Subtitles also
use Netflix Sans Medium.
- **Cards:** rounded ~6px, hover scales to 1.05× with subtle shadow lift.
- **Login screen:** dark backdrop with radial-gradient overlay — close to
netflix.com's sign-in page.
- **No theme-brand footer label** any more.
### Theme history
| Date | Theme | Version | Why changed |
|---|---|---|---| |---|---|---|---|
| Jellyfin built-in CSS variables | first-party | n/a | Minimal. Recolour only, no layout polish. | | 2026-05-08 (earlier today) | ElegantFin | v25.12.31 | Initial Jellyfin theming pass. Picked for activity + safety (most actively maintained CSS in the ecosystem). |
| **ElegantFin** (lscambo13) | community CSS | **active — v25.12.31 (Dec 2025)**, tested 10.11.5 | Jellyseerr-inspired. Single-import, jsDelivr-hosted. | | 2026-05-08 (this entry) | **Cineplex** | **v1.0.6** | Owner asked for the most Netflix-faithful theme available. ElegantFin's Jellyseerr aesthetic (blue-grey, no red) is too far from Netflix; Cineplex is purpose-built for this look and explicitly targets the 10.10 series we're on. JellyFlix (the genre's elder) is halted. |
| Ultrachromic (CTalvio) | community CSS | "selectively maintained, project is old" | Three presets (mono / kaleido / nova). Risk of breaking on newer JF. |
| JellyFlix (prayag17) | community CSS | **development halted** per repo notice | Most Netflix-look-alike but stale. |
| DarkFlix (DevilsDesigns) | community CSS, fork of JellyFlix | sporadic | Inherits JellyFlix risk. |
| Theme Park (Jellyfin pack) | multi-app CSS | active | dracula/nord/hotline/plex variants. Less Netflix, more "skin pack". |
| Jellyfin-Vue | full alt web client | active | Replaces the entire web UI. Out of scope: violates "theme via CSS only" constraint and forces an image swap. |
| Finetwo / JellyfinNetflixWeb | fork-style replacement | varies | Same constraint violation. |
### Why ElegantFin If we ever roll back to ElegantFin, the previous `@import` was
`https://cdn.jsdelivr.net/gh/lscambo13/ElegantFin@v25.12.31/Theme/ElegantFin-jellyfin-theme-build-latest-minified.css`.
1. **Most recent activity by far** — v25.12.31 released 31 Dec 2025; tested The previous incarnation of this doc lives in git history.
on Jellyfin 10.11.5 which means it'll keep working as we upgrade past
our current 10.10.3.
2. **Single `@import` line** — zero ops overhead. CDN-hosted on jsDelivr
with `cache-control: public, max-age=604800`. No assets to host
ourselves. Revert = clear one field.
3. **Jellyseerr-inspired** — modern dark UI with rounded cards, hero
backdrops, smooth hover, condensed sidebar. Closer to "premium
streaming" feel than Netflix's red-and-black, and ages better.
4. **Doesn't touch the upstream image** — we stay on `jellyfin/jellyfin:10.10.3`.
5. **Compatible with multi-user setup** — applies server-wide via
`Branding.CustomCss`, every user inherits.
6. **Not the JellyWatch 2026 darling** (that was JellyFlix) but JellyFlix
is explicitly halted. ElegantFin is the safer, longer-lived pick.
### What it looks like
- Inter typeface throughout (loaded from Google Fonts inside the CSS).
- Dark-only colour scheme (`color-scheme: dark`), primary background
`#111827`, secondary `#1d2635` (Tailwind slate territory).
- Backdrop hero on item pages with darker bottom-gradient overlay.
- Rounded cards (~10px radius), subtle shadow, hover lift.
- "ElegantFin v25.12.31" tag in the footer (visible to users — fine for us).
- Login screen restyled into a centred card on a blurred backdrop.
--- ---
## 2. How it was applied ## 2. How it was applied
### Branding API (already done 2026-05-08) ### Branding API (Cineplex, applied 2026-05-08)
```bash ```bash
TOKEN=*redacted* TOKEN=*redacted*
cat > /tmp/branding.json <<'EOF' cat > /tmp/branding.json <<'EOF'
{ {
"LoginDisclaimer": "Welcome to nasflix.s8n.ru — LAN-only. Be kind, rewind.", "LoginDisclaimer": "Welcome to tv.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": "/* Cineplex v1.0.6 — Netflix-faithful theme by MRunkehl, pinned tag (immutable on jsDelivr) */\n/* Compat: Jellyfin 10.10.7+ ; we run 10.10.3 — verified rendering 2026-05-08 */\n@import url(\"https://cdn.jsdelivr.net/gh/MRunkehl/cineplex@v1.0.6/cineplex.css\");\n\n/* Hide Cast & Crew + Guest Stars sections globally (preserved 2026-05-08) */\n#castCollapsible, #guestCastCollapsible { display: none !important; }\n",
"SplashscreenEnabled": true "SplashscreenEnabled": true
} }
EOF EOF
curl -sS -X POST \ # Note: arrflix.s8n.ru didn't have a Traefik SNI cert at apply-time, so
# we sent the request to the legacy SNI tv.s8n.ru and pinned its address
# with --resolve. Either form is fine once both names have certs.
curl -sS --resolve tv.s8n.ru:443:192.168.0.100 \
-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://nasflix.s8n.ru/System/Configuration/branding https://tv.s8n.ru/System/Configuration/branding
# expect: HTTP 204 # expect: HTTP 204 (got HTTP 204 — applied)
``` ```
### Verification ### Verification (executed 2026-05-08)
```bash ```bash
curl -sS -H "X-Emby-Token: $TOKEN" \ # 1. Admin endpoint — confirms the new CustomCss is stored.
https://nasflix.s8n.ru/System/Configuration/branding | python3 -m json.tool curl -sS --resolve tv.s8n.ru:443:192.168.0.100 \
# Should include the @import line and the disclaimer text. -H "X-Emby-Token: $TOKEN" \
https://tv.s8n.ru/System/Configuration/branding | python3 -m json.tool
# Result: HTTP 200, contains the Cineplex @import + cast/crew hide rule.
# Public branding endpoint the SPA reads at runtime — confirms anonymous # 2. Anonymous endpoint the SPA reads at runtime — confirms what every
# clients (i.e. the browser before login) will see the theme: # browser will pull before login.
curl -sS https://nasflix.s8n.ru/Branding/Configuration | python3 -m json.tool curl -sS --resolve tv.s8n.ru:443:192.168.0.100 \
https://tv.s8n.ru/Branding/Configuration | python3 -m json.tool
# Result: HTTP 200, identical CustomCss to admin endpoint. ✓
# 3. The CSS asset itself on jsDelivr (sanity-check the network path).
curl -sSI "https://cdn.jsdelivr.net/gh/MRunkehl/cineplex@v1.0.6/cineplex.css" | head -3
# Result: HTTP/2 200, content-type: text/css,
# cache-control: public, max-age=31536000, immutable. ✓
# 4. SPA shell still routes (nav not broken).
curl -sSI --resolve tv.s8n.ru:443:192.168.0.100 https://tv.s8n.ru/ | head -1
# Result: HTTP/2 302 → /web/. ✓
``` ```
`/` 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 visual
hard browser reload (cache-bust) on `https://nasflix.s8n.ru` from the LAN. check is a hard browser reload (Ctrl-Shift-R) on `https://tv.s8n.ru`
(or `https://arrflix.s8n.ru` once its cert is up) from the LAN — owner
will do this.
### Cache clear ### Cache clear
@ -100,12 +174,12 @@ Jellyfin web caches aggressively in browsers. After applying:
--- ---
## 3. Server-side branding state (as of 2026-05-08) ## 3. Server-side branding state (as of 2026-05-08, post-Cineplex)
| Field | Value | | Field | Value |
|---|---| |---|---|
| `LoginDisclaimer` | "Welcome to nasflix.s8n.ru — LAN-only. Be kind, rewind." | | `LoginDisclaimer` | "Welcome to tv.s8n.ru — LAN-only. Be kind, rewind." (will swap to arrflix.s8n.ru when the new cert lands) |
| `CustomCss` | `@import` of ElegantFin v25.12.31 from jsDelivr (autoupdating off `@main`) | | `CustomCss` | `@import` of **Cineplex v1.0.6** from jsDelivr — pinned tag `@v1.0.6` (immutable). Plus appended cast/crew hide rule. |
| `SplashscreenEnabled` | `true` | | `SplashscreenEnabled` | `true` |
`SplashscreenEnabled: true` makes Jellyfin auto-pick a backdrop from the `SplashscreenEnabled: true` makes Jellyfin auto-pick a backdrop from the
@ -156,7 +230,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://nasflix.s8n.ru/Users/New) https://arrflix.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 +272,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://nasflix.s8n.ru/Users/$NEW_ID/Policy" "https://arrflix.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://nasflix.s8n.ru/Users/$NEW_ID" | python3 -m json.tool "https://arrflix.s8n.ru/Users/$NEW_ID" | python3 -m json.tool
``` ```
### 4e. Policy field cheat-sheet ### 4e. Policy field cheat-sheet
@ -238,7 +312,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://nasflix.s8n.ru/Users/$USER_ID/Password" "https://arrflix.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 +377,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 `nasflix.s8n.ru`), opens the same item 3. Friend logs in (any device — same `arrflix.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
@ -318,45 +392,55 @@ WS by default, no changes needed.
### 6a. Updating the theme ### 6a. Updating the theme
ElegantFin's `@import` URL pins to `@main` on jsDelivr — meaning new We currently pin Cineplex to `@v1.0.6` (immutable) — no auto-updates,
upstream commits propagate after jsDelivr's cache TTL (12h s-maxage, no surprise breakage. To opt into upstream changes:
7d max-age). To pull immediately:
```bash ```bash
# Force refresh by pinning to a specific tag, then back to main: # Move from immutable tag to floating @main (pulls future commits;
curl -sS -X POST -H "X-Emby-Token: $TOKEN" \ # jsDelivr cache TTL is up to 7d for floating refs).
curl -sS --resolve tv.s8n.ru:443:192.168.0.100 \
-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 nasflix.s8n.ru — LAN-only. Be kind, rewind.", "SplashscreenEnabled": true}' \ -d '{"CustomCss": "@import url(\"https://cdn.jsdelivr.net/gh/MRunkehl/cineplex@main/cineplex.css\");\n#castCollapsible, #guestCastCollapsible { display: none !important; }", "LoginDisclaimer": "Welcome to tv.s8n.ru — LAN-only. Be kind, rewind.", "SplashscreenEnabled": true}' \
https://nasflix.s8n.ru/System/Configuration/branding https://tv.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 common
common bottleneck, not jsDelivr. bottleneck, not jsDelivr.
When upgrading Jellyfin (e.g. 10.10.3 → 10.11.x), check When upgrading Jellyfin (e.g. 10.10.3 → 10.10.7+ → 10.11.x), check the
[the ElegantFin release notes](https://github.com/lscambo13/ElegantFin/releases) [Cineplex commits](https://github.com/MRunkehl/cineplex/commits/main)
first. The current theme is tagged tested-against 10.11.5, so we're and the README compatibility line. Cineplex's stated floor is 10.10.7,
forward-compatible through that. so going forward in the 10.10 series is safe; jumping to 10.11 needs a
re-test (selectors changed in some 10.11 release notes). If something
regresses, pin back to `@v1.0.6`.
### 6b. Reverting ### 6b. Reverting to ElegantFin (or vanilla)
Empty out the CustomCss field via API: Replace the `@import` line:
```bash ```bash
curl -sS -X POST -H "X-Emby-Token: $TOKEN" \ # Back to ElegantFin (Jellyseerr-style):
-H "Content-Type: application/json" \ curl -sS --resolve tv.s8n.ru:443:192.168.0.100 \
-X POST -H "X-Emby-Token: $TOKEN" -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\");\n#castCollapsible, #guestCastCollapsible { display: none !important; }", "LoginDisclaimer": "Welcome to tv.s8n.ru — LAN-only. Be kind, rewind.", "SplashscreenEnabled": true}' \
https://tv.s8n.ru/System/Configuration/branding
# To vanilla Jellyfin (clear everything):
curl -sS --resolve tv.s8n.ru:443:192.168.0.100 \
-X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \
-d '{"CustomCss": "", "LoginDisclaimer": "", "SplashscreenEnabled": false}' \ -d '{"CustomCss": "", "LoginDisclaimer": "", "SplashscreenEnabled": false}' \
https://nasflix.s8n.ru/System/Configuration/branding https://tv.s8n.ru/System/Configuration/branding
``` ```
Or in the UI: Dashboard → General → clear "Custom CSS code" → Save. Or in the UI: Dashboard → General → edit / clear "Custom CSS code" →
Hard-reload browsers. Vanilla Jellyfin returns instantly. Save. Hard-reload browsers afterward.
### 6c. Pinning a known-good revision ### 6c. Pinning a known-good revision
If `@main` ships a regression, switch the URL to a specific release tag Cineplex is already pinned to `@v1.0.6`. If a future tag (e.g. `v1.0.7`)
(e.g. `@v25.12.31`). Tags are in the GitHub releases page. jsDelivr ships and is good, bump the URL. jsDelivr serves `@<tag>` immutably and
serves `@<tag>` immutably and forever. forever. Tag list: <https://github.com/MRunkehl/cineplex/tags>.
--- ---
@ -367,8 +451,8 @@ When the friend gets their account, walk them through this **once**:
1. **Login** → see the LAN-only disclaimer; that's the right server. 1. **Login** → see the LAN-only disclaimer; that's the right server.
2. **Profile picture** → set one (just helps SyncPlay group UX). 2. **Profile picture** → set one (just helps SyncPlay group UX).
3. **Display preferences** (top-right user icon → Display): 3. **Display preferences** (top-right user icon → Display):
- Theme: keep "Dark" (ElegantFin is dark-only, light theme will look - Theme: keep "Dark" (Cineplex is dark-only — Netflix-black `#181818`
half-applied). Don't switch. base; light theme will look half-applied). Don't switch.
- Landing screen: Home. - Landing screen: Home.
4. **Playback preferences**: 4. **Playback preferences**:
- Default audio language: `English`. - Default audio language: `English`.
@ -383,7 +467,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://nasflix.s8n.ru` 7. **Mobile/TV**: install Jellyfin app, server URL `https://arrflix.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.
@ -395,10 +479,16 @@ When the friend gets their account, walk them through this **once**:
- [ ] Enable Quick Connect when friend account is created (Dashboard → - [ ] Enable Quick Connect when friend account is created (Dashboard →
General → Quick Connect). General → Quick Connect).
- [ ] Configure SMTP for self-serve password reset (currently admin-only). - [ ] Configure SMTP for self-serve password reset (currently admin-only).
- [ ] Replace `@main` pin with `@v25.12.31` if we hit upstream churn. - [ ] Get Traefik to issue a SNI cert for `arrflix.s8n.ru` so the curl
examples don't need `--resolve tv.s8n.ru:443:192.168.0.100`. Until
then, both names point to the same backend on `192.168.0.100` but
only `tv.s8n.ru` has a valid cert.
- [ ] Watch [Cineplex commits](https://github.com/MRunkehl/cineplex/commits/main)
monthly; if a `v1.0.7` lands and looks safe, bump the pin.
- [ ] Add a 2nd library (movies are mounted but the server may have an - [ ] Add a 2nd library (movies are mounted but the server may have an
empty Movies folder — confirm with friend's first ask). empty Movies folder — confirm with friend's first ask).
- [ ] After GPU driver fix on nullstone, NVENC transcode → 1080p HEVC - [ ] After GPU driver fix on nullstone, NVENC transcode → 1080p HEVC
will stop being CPU-bound; revisit per-user quality defaults. will stop being CPU-bound; revisit per-user quality defaults.
- [ ] Optional: tweak `--elegantFinFooterText` CSS var to drop the - [ ] Sanity-check that Netflix Sans loads on every device — if Netflix's
ElegantFin version label from the footer (cosmetic). CDN starts blocking foreign referers, swap the `@font-face` block
for a self-hosted copy or fall back to system-ui.

View file

@ -1,4 +1,4 @@
# 05 — File & Folder Structure Rules (nasflix.s8n.ru) # 05 — File & Folder Structure Rules (arrflix.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://nasflix.s8n.ru" B="https://arrflix.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" \
@ -1010,7 +1010,7 @@ Jellyfin library per category.
└── tv/ ← collectionType: tvshows └── tv/ ← collectionType: tvshows
``` ```
> NASFLIX scope locked 2026-05-08: TV Shows + Movies only. `anime/`, > ARRFLIX scope locked 2026-05-08: TV Shows + Movies only. `anime/`,
> `musicvideos/`, `home/`, `music/`, `docs-*/` libraries removed. Sections in > `musicvideos/`, `home/`, `music/`, `docs-*/` libraries removed. Sections in
> this doc covering anime/music/etc. remain as reference for the day scope is > this doc covering anime/music/etc. remain as reference for the day scope is
> revisited — just `mkdir` + add library via API when needed. > revisited — just `mkdir` + add library via API when needed.
@ -1090,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://nasflix.s8n.ru/Library/Refresh`. 8. Scan: `curl -s -X POST -H "X-Emby-Token: $TOKEN" https://arrflix.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://nasflix.s8n.ru > **Scope of this doc:** research only. No live changes. Targets Jellyfin **10.10.3** at https://arrflix.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.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 | | 5 | Subdomain split — `movies.arrflix.s8n.ru`, `anime.arrflix.s8n.ru`, `music.arrflix.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.nasflix.s8n.ru` cannot - You want fully isolated user accounts per "service" (e.g. kid account on `anime.arrflix.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 (nasflix.s8n.ru) # 07 — Pre-Import Cleanup Ruleset (arrflix.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 `NASFLIX` repo. Save to `bin/cleanup-import.sh` in the `ARRFLIX` repo.
```bash ```bash
#!/usr/bin/env bash #!/usr/bin/env bash
# cleanup-import.sh — Pre-import cleanup for nasflix.s8n.ru # cleanup-import.sh — Pre-import cleanup for arrflix.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 (nasflix.s8n.ru) # 08 — Filename & Folder Normalization Ruleset (arrflix.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 nasflix.s8n.ru normalize.py — canonical filename normalizer for arrflix.s8n.ru
Per /tmp/NASFLIX/docs/08-filename-normalization.md. Per /tmp/ARRFLIX/docs/08-filename-normalization.md.
Safe by default: dry-run, no overwrite, no delete. Safe by default: dry-run, no overwrite, no delete.
""" """