snapshot: pre-ElegantFin migration baseline
Captures the full Cineplex-themed deploy state for rollback before swapping the active CSS theme to ElegantFin with ARRFLIX recolor. - branding.json: full /System/Configuration/branding payload (CustomCss + LoginDisclaimer + SplashscreenEnabled) - index.html: bind-mounted Jellyfin web shim from nullstone - docker-compose.yml: jellyfin compose file from nullstone - users.json + libraries.json: reference state (not for direct restore) - displayprefs-<userid>.json x5: per-user DisplayPreferences - RESTORE.md: three concrete rollback commands
This commit is contained in:
parent
d41aaa04fd
commit
ba64be7fa3
11 changed files with 263 additions and 0 deletions
119
snapshots/2026-05-08-pre-elegantfin/RESTORE.md
Normal file
119
snapshots/2026-05-08-pre-elegantfin/RESTORE.md
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
# Rollback — 2026-05-08 pre-ElegantFin
|
||||||
|
|
||||||
|
Snapshot captured immediately before the Cineplex → ElegantFin theme migration.
|
||||||
|
Restore one or all of the artefacts below to revert to the Cineplex-themed
|
||||||
|
ARRFLIX deploy as of 2026-05-08 03:58 UTC.
|
||||||
|
|
||||||
|
Tag: `snapshot-2026-05-08-pre-elegantfin`
|
||||||
|
Remote: `git.s8n.ru/s8n/ARRFLIX`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files in this snapshot
|
||||||
|
|
||||||
|
| File | Source | Purpose |
|
||||||
|
|------|--------|---------|
|
||||||
|
| `branding.json` | `GET /System/Configuration/branding` | CustomCss + LoginDisclaimer + SplashscreenEnabled |
|
||||||
|
| `index.html` | `nullstone:/opt/docker/jellyfin/web-overrides/index.html` | Bind-mounted Jellyfin web shim (critical-path CSS, branding, runtime shim include) |
|
||||||
|
| `docker-compose.yml` | `nullstone:/opt/docker/jellyfin/docker-compose.yml` | Compose file driving the jellyfin container |
|
||||||
|
| `users.json` | `GET /Users` | Full user list with policies + configurations |
|
||||||
|
| `libraries.json` | `GET /Library/VirtualFolders` | Library definitions (paths, types, options) |
|
||||||
|
| `displayprefs-<userid>.json` × 5 | `GET /DisplayPreferences/usersettings?userId=<id>&client=emby` | Per-user UI preferences (home sections, theme, etc.) |
|
||||||
|
|
||||||
|
User IDs captured:
|
||||||
|
- `571decc67cdc4ea683b4c936b0a31ff8` — 5
|
||||||
|
- `82dd8542915740c8ae799b6723542c63` — guest
|
||||||
|
- `a4cbcdf95bb34888885af6fbf5c340d1` — house
|
||||||
|
- `d787fbfc373a44119a247e7406b2721e` — marco
|
||||||
|
- `2be0f0d3fe3a45dc9298138a15a01925` — s8n
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rollback — three concrete commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SNAP=/tmp/ARRFLIX/snapshots/2026-05-08-pre-elegantfin
|
||||||
|
TOKEN="*redacted*"
|
||||||
|
BASE="https://arrflix.s8n.ru"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1. Restore CustomCss + LoginDisclaimer (branding)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -ksX POST "$BASE/System/Configuration/branding" \
|
||||||
|
-H "Authorization: MediaBrowser Token=$TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
--data-binary @$SNAP/branding.json
|
||||||
|
# Expected: HTTP 204
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Restore the bind-mounted index.html on nullstone
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scp $SNAP/index.html user@nullstone:/opt/docker/jellyfin/web-overrides/index.html
|
||||||
|
# No restart needed — Traefik serves the file directly via the bind-mount.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Restore per-user DisplayPreferences
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for uid in 571decc67cdc4ea683b4c936b0a31ff8 \
|
||||||
|
82dd8542915740c8ae799b6723542c63 \
|
||||||
|
a4cbcdf95bb34888885af6fbf5c340d1 \
|
||||||
|
d787fbfc373a44119a247e7406b2721e \
|
||||||
|
2be0f0d3fe3a45dc9298138a15a01925; do
|
||||||
|
curl -ksX POST "$BASE/DisplayPreferences/usersettings?userId=$uid&client=emby" \
|
||||||
|
-H "Authorization: MediaBrowser Token=$TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
--data-binary @$SNAP/displayprefs-$uid.json
|
||||||
|
done
|
||||||
|
# Expected: HTTP 204 each
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## One-shot rollback (all three at once)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SNAP=/tmp/ARRFLIX/snapshots/2026-05-08-pre-elegantfin
|
||||||
|
TOKEN="*redacted*"
|
||||||
|
BASE="https://arrflix.s8n.ru"
|
||||||
|
|
||||||
|
# 1. branding
|
||||||
|
curl -ksX POST "$BASE/System/Configuration/branding" \
|
||||||
|
-H "Authorization: MediaBrowser Token=$TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
--data-binary @$SNAP/branding.json
|
||||||
|
|
||||||
|
# 2. index.html
|
||||||
|
scp $SNAP/index.html user@nullstone:/opt/docker/jellyfin/web-overrides/index.html
|
||||||
|
|
||||||
|
# 3. per-user displayprefs
|
||||||
|
for uid in 571decc67cdc4ea683b4c936b0a31ff8 82dd8542915740c8ae799b6723542c63 \
|
||||||
|
a4cbcdf95bb34888885af6fbf5c340d1 d787fbfc373a44119a247e7406b2721e \
|
||||||
|
2be0f0d3fe3a45dc9298138a15a01925; do
|
||||||
|
curl -ksX POST "$BASE/DisplayPreferences/usersettings?userId=$uid&client=emby" \
|
||||||
|
-H "Authorization: MediaBrowser Token=$TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
--data-binary @$SNAP/displayprefs-$uid.json
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reference (read-only — for diffing, not posting back)
|
||||||
|
|
||||||
|
- `users.json` — sanity-check that policies/permissions match before/after a future user-mgmt change. The User API uses `/Users/{id}/Policy` and `/Users/{id}/Configuration` endpoints, NOT a bulk POST.
|
||||||
|
- `libraries.json` — sanity-check that VirtualFolders are still intact. Library mutations go through `/Library/VirtualFolders` add/remove endpoints, not a single POST.
|
||||||
|
- `docker-compose.yml` — reference only. If the compose file changes, restore by hand and `docker compose up -d` on nullstone.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What this snapshot does NOT cover
|
||||||
|
|
||||||
|
- Jellyfin SQLite databases (`library.db`, `users.db`, etc.) — full data is preserved by Restic backups, not this snapshot.
|
||||||
|
- Plugins / plugin config — not part of the CSS/branding migration scope.
|
||||||
|
- Media files on disk — never touched by theme work.
|
||||||
|
|
||||||
|
If a recovery requires DB-level restore, fall back to the Restic snapshot job
|
||||||
|
documented in `SYSTEM.md` rather than this CSS-scoped rollback.
|
||||||
1
snapshots/2026-05-08-pre-elegantfin/branding.json
Normal file
1
snapshots/2026-05-08-pre-elegantfin/branding.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
||||||
|
{"Id":"3ce5b65d-e116-d731-65d1-efc4a30ec35c","SortBy":"SortName","RememberIndexing":false,"PrimaryImageHeight":250,"PrimaryImageWidth":250,"CustomPrefs":{"homesection0":"resume","homesection1":"resumeaudio","homesection2":"nextup","homesection3":"latestmedia","homesection4":"none","homesection5":"none","homesection6":"none","homesection7":"none","homesection8":"none","homesection9":"none","chromecastVersion":"stable","skipForwardLength":"30000","skipBackLength":"10000","enableNextVideoInfoOverlay":"False","tvhome":null,"dashboardTheme":null,"767bffe4f11c93ef34b805451a696a4e-series":"{\u0022SortBy\u0022:\u0022SortName\u0022,\u0022SortOrder\u0022:\u0022Ascending\u0022}"},"ScrollDirection":"Horizontal","ShowBackdrop":true,"RememberSorting":false,"SortOrder":"Ascending","ShowSidebar":false,"Client":"emby"}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"Id":"3ce5b65d-e116-d731-65d1-efc4a30ec35c","SortBy":"SortName","RememberIndexing":false,"PrimaryImageHeight":250,"PrimaryImageWidth":250,"CustomPrefs":{"homesection0":"resume","homesection1":"resumeaudio","homesection2":"nextup","homesection3":"latestmedia","homesection4":"none","homesection5":"none","homesection6":"none","homesection7":"none","homesection8":"none","homesection9":"none","chromecastVersion":"stable","skipForwardLength":"30000","skipBackLength":"10000","enableNextVideoInfoOverlay":"False","tvhome":null,"dashboardTheme":null},"ScrollDirection":"Horizontal","ShowBackdrop":true,"RememberSorting":false,"SortOrder":"Ascending","ShowSidebar":false,"Client":"emby"}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"Id":"3ce5b65d-e116-d731-65d1-efc4a30ec35c","SortBy":"SortName","RememberIndexing":false,"PrimaryImageHeight":250,"PrimaryImageWidth":250,"CustomPrefs":{"homesection0":"resume","homesection1":"resumeaudio","homesection2":"nextup","homesection3":"latestmedia","homesection4":"none","homesection5":"none","homesection6":"none","homesection7":"none","homesection8":"none","homesection9":"none","chromecastVersion":"stable","skipForwardLength":"30000","skipBackLength":"10000","enableNextVideoInfoOverlay":"False","tvhome":null,"dashboardTheme":null,"767bffe4f11c93ef34b805451a696a4e-series":"{\u0022SortBy\u0022:\u0022SortName\u0022,\u0022SortOrder\u0022:\u0022Ascending\u0022}","f137a2dd21bbc1b99aa5c0f6bf02a805-moviecollections":"{\u0022SortBy\u0022:\u0022SortName\u0022,\u0022SortOrder\u0022:\u0022Ascending\u0022}","f137a2dd21bbc1b99aa5c0f6bf02a805-moviegenres":"{\u0022SortBy\u0022:\u0022SortName\u0022,\u0022SortOrder\u0022:\u0022Ascending\u0022}"},"ScrollDirection":"Horizontal","ShowBackdrop":true,"RememberSorting":false,"SortOrder":"Ascending","ShowSidebar":false,"Client":"emby"}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"Id":"3ce5b65d-e116-d731-65d1-efc4a30ec35c","SortBy":"SortName","RememberIndexing":false,"PrimaryImageHeight":250,"PrimaryImageWidth":250,"CustomPrefs":{"homesection0":"resume","homesection1":"resumeaudio","homesection2":"nextup","homesection3":"latestmedia","homesection4":"none","homesection5":"none","homesection6":"none","homesection7":"none","homesection8":"none","homesection9":"none","chromecastVersion":"stable","skipForwardLength":"30000","skipBackLength":"10000","enableNextVideoInfoOverlay":"False","tvhome":null,"dashboardTheme":null},"ScrollDirection":"Horizontal","ShowBackdrop":true,"RememberSorting":false,"SortOrder":"Ascending","ShowSidebar":false,"Client":"emby"}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"Id":"3ce5b65d-e116-d731-65d1-efc4a30ec35c","SortBy":"SortName","RememberIndexing":false,"PrimaryImageHeight":250,"PrimaryImageWidth":250,"CustomPrefs":{"homesection0":"resume","homesection1":"resumeaudio","homesection2":"nextup","homesection3":"latestmedia","homesection4":"none","homesection5":"none","homesection6":"none","homesection7":"none","homesection8":"none","homesection9":"none","chromecastVersion":"stable","skipForwardLength":"30000","skipBackLength":"10000","enableNextVideoInfoOverlay":"False","tvhome":null,"dashboardTheme":null},"ScrollDirection":"Horizontal","ShowBackdrop":true,"RememberSorting":false,"SortOrder":"Ascending","ShowSidebar":false,"Client":"emby"}
|
||||||
42
snapshots/2026-05-08-pre-elegantfin/docker-compose.yml
Normal file
42
snapshots/2026-05-08-pre-elegantfin/docker-compose.yml
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Jellyfin — self-hosted media server (LAN-only)
|
||||||
|
# Deploy path on nullstone: /opt/docker/jellyfin/
|
||||||
|
# Domain: arrflix.s8n.ru (LAN-only via Pi-hole local DNS + no-guest middleware)
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - GTX 1660 Ti present but nvidia-smi failing on host. CPU transcode only
|
||||||
|
# until driver is fixed; revisit hwaccel after fix.
|
||||||
|
# - Media mounted read-only into container; write only to /config + /cache.
|
||||||
|
# - userns: host matches nullstone Docker convention (host UID 1000 owns volumes).
|
||||||
|
# - Cert via existing letsencrypt resolver (Gandi DNS-01) — works without
|
||||||
|
# public A record.
|
||||||
|
|
||||||
|
services:
|
||||||
|
jellyfin:
|
||||||
|
image: jellyfin/jellyfin:10.10.3
|
||||||
|
container_name: jellyfin
|
||||||
|
restart: unless-stopped
|
||||||
|
user: "1000:1000"
|
||||||
|
userns_mode: "host"
|
||||||
|
environment:
|
||||||
|
- TZ=Europe/London
|
||||||
|
- JELLYFIN_PublishedServerUrl=https://arrflix.s8n.ru
|
||||||
|
volumes:
|
||||||
|
- /home/docker/jellyfin/config:/config
|
||||||
|
- /home/docker/jellyfin/cache:/cache
|
||||||
|
- /home/user/media:/media:ro
|
||||||
|
- /opt/docker/jellyfin/web-overrides/index.html:/jellyfin/jellyfin-web/index.html:ro
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=proxy"
|
||||||
|
- "traefik.http.routers.jellyfin.rule=Host(`arrflix.s8n.ru`)"
|
||||||
|
- "traefik.http.routers.jellyfin.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.jellyfin.tls=true"
|
||||||
|
- "traefik.http.routers.jellyfin.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.routers.jellyfin.middlewares=security-headers@file"
|
||||||
|
- "traefik.http.services.jellyfin.loadbalancer.server.port=8096"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
94
snapshots/2026-05-08-pre-elegantfin/index.html
Normal file
94
snapshots/2026-05-08-pre-elegantfin/index.html
Normal file
File diff suppressed because one or more lines are too long
1
snapshots/2026-05-08-pre-elegantfin/libraries.json
Normal file
1
snapshots/2026-05-08-pre-elegantfin/libraries.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
[{"Name":"Movies","Locations":["/media/movies"],"CollectionType":"movies","LibraryOptions":{"Enabled":true,"EnablePhotos":false,"EnableRealtimeMonitor":true,"EnableLUFSScan":false,"EnableChapterImageExtraction":false,"ExtractChapterImagesDuringLibraryScan":false,"EnableTrickplayImageExtraction":false,"ExtractTrickplayImagesDuringLibraryScan":false,"PathInfos":[{"Path":"/media/movies"}],"SaveLocalMetadata":false,"EnableInternetProviders":false,"EnableAutomaticSeriesGrouping":true,"EnableEmbeddedTitles":false,"EnableEmbeddedExtrasTitles":false,"EnableEmbeddedEpisodeInfos":false,"AutomaticRefreshIntervalDays":0,"PreferredMetadataLanguage":"en","MetadataCountryCode":"US","SeasonZeroDisplayName":"Specials","DisabledLocalMetadataReaders":[],"DisabledSubtitleFetchers":[],"SubtitleFetcherOrder":[],"DisabledMediaSegmentProviders":[],"MediaSegmentProvideOrder":[],"SkipSubtitlesIfEmbeddedSubtitlesPresent":false,"SkipSubtitlesIfAudioTrackMatches":true,"SubtitleDownloadLanguages":["eng"],"RequirePerfectSubtitleMatch":true,"SaveSubtitlesWithMedia":true,"SaveLyricsWithMedia":false,"SaveTrickplayWithMedia":false,"DisabledLyricFetchers":[],"LyricFetcherOrder":[],"PreferNonstandardArtistsTag":false,"UseCustomTagDelimiters":false,"CustomTagDelimiters":["/","|",";","\\"],"DelimiterWhitelist":[],"AutomaticallyAddToCollection":false,"AllowEmbeddedSubtitles":"AllowAll","TypeOptions":[{"Type":"Movie","MetadataFetchers":["TheMovieDb"],"MetadataFetcherOrder":["TheMovieDb"],"ImageFetchers":["TheMovieDb"],"ImageFetcherOrder":["TheMovieDb"],"ImageOptions":[]}]},"ItemId":"f137a2dd21bbc1b99aa5c0f6bf02a805","PrimaryImageItemId":"f137a2dd21bbc1b99aa5c0f6bf02a805","RefreshStatus":"Idle"},{"Name":"TV Shows","Locations":["/media/tv"],"CollectionType":"tvshows","LibraryOptions":{"Enabled":true,"EnablePhotos":false,"EnableRealtimeMonitor":true,"EnableLUFSScan":false,"EnableChapterImageExtraction":false,"ExtractChapterImagesDuringLibraryScan":false,"EnableTrickplayImageExtraction":false,"ExtractTrickplayImagesDuringLibraryScan":false,"PathInfos":[{"Path":"/media/tv"}],"SaveLocalMetadata":false,"EnableInternetProviders":false,"EnableAutomaticSeriesGrouping":true,"EnableEmbeddedTitles":false,"EnableEmbeddedExtrasTitles":false,"EnableEmbeddedEpisodeInfos":false,"AutomaticRefreshIntervalDays":0,"PreferredMetadataLanguage":"en","MetadataCountryCode":"US","SeasonZeroDisplayName":"Specials","DisabledLocalMetadataReaders":[],"DisabledSubtitleFetchers":[],"SubtitleFetcherOrder":[],"DisabledMediaSegmentProviders":[],"MediaSegmentProvideOrder":[],"SkipSubtitlesIfEmbeddedSubtitlesPresent":false,"SkipSubtitlesIfAudioTrackMatches":true,"SubtitleDownloadLanguages":["eng"],"RequirePerfectSubtitleMatch":true,"SaveSubtitlesWithMedia":true,"SaveLyricsWithMedia":false,"SaveTrickplayWithMedia":false,"DisabledLyricFetchers":[],"LyricFetcherOrder":[],"PreferNonstandardArtistsTag":false,"UseCustomTagDelimiters":false,"CustomTagDelimiters":["/","|",";","\\"],"DelimiterWhitelist":[],"AutomaticallyAddToCollection":false,"AllowEmbeddedSubtitles":"AllowAll","TypeOptions":[{"Type":"Series","MetadataFetchers":["TheMovieDb"],"MetadataFetcherOrder":["TheMovieDb"],"ImageFetchers":["TheMovieDb"],"ImageFetcherOrder":["TheMovieDb"],"ImageOptions":[]},{"Type":"Season","MetadataFetchers":["TheMovieDb"],"MetadataFetcherOrder":["TheMovieDb"],"ImageFetchers":["TheMovieDb"],"ImageFetcherOrder":["TheMovieDb"],"ImageOptions":[]},{"Type":"Episode","MetadataFetchers":["TheMovieDb"],"MetadataFetcherOrder":["TheMovieDb"],"ImageFetchers":["TheMovieDb"],"ImageFetcherOrder":["TheMovieDb"],"ImageOptions":[]}]},"ItemId":"767bffe4f11c93ef34b805451a696a4e","PrimaryImageItemId":"767bffe4f11c93ef34b805451a696a4e","RefreshStatus":"Idle"}]
|
||||||
1
snapshots/2026-05-08-pre-elegantfin/users.json
Normal file
1
snapshots/2026-05-08-pre-elegantfin/users.json
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue