docs(media): the SSD metadata view has never been deployed on Dankserver
The vault described the overlay as the live shape. It is not: `media-view.sh status` answers NOT MOUNTED, /var/lib/media-meta does not exist, and the installed quadlets under ~/.config/containers/systemd still carry the pre-view binds (/mnt/entropy:/data:ro for Jellyfin, /mnt/entropy:/mnt/entropy:ro for Plex) while git binds the view with ConditionPathIsMountPoint. The deploy gate (deploy-remote.sh:ensure_media_view) has therefore never run: the first `deploy.sh dankserver --service jellyfin|plex` is an untested cutover of both media servers, and Jellyfin's read-only sidecar error is today's expected state, not a fault. Same commit, the scan-acceptance law from tonight's TV handoff: it logged `jellyfin scan: 401`, so Jellyfin never rescanned a 58k-move library until the request was re-issued with the identical key and header form (204). Anything that is not 204 means no scan was queued — re-issue, do not diagnose.
This commit is contained in:
1 parent
9eb85ea1f9
commit
1e1527d101
5 files changed
+39
-21
No files matched your search
@@ -851,6 +851,6 @@ history.
|
|||||||
| Benchmarking (only when ordered) | **Stop and mask the service first**, bench, unmask, restart, verify health. |
|
| Benchmarking (only when ordered) | **Stop and mask the service first**, bench, unmask, restart, verify health. |
|
||||||
| Deleting a file | **Move it to trash** — `.AGENTTRASH/` (in-repo) or `~/.AGENTTRASH/YYYYMMDD-topic`. Never `rm`. |
|
| Deleting a file | **Move it to trash** — `.AGENTTRASH/` (in-repo) or `~/.AGENTTRASH/YYYYMMDD-topic`. Never `rm`. |
|
||||||
| Relayout the movie library | **Census → plan → read the exceptions → `scripts/media-apply.py`** — renames only, journal before every move; never hand-rename, never copy. |
|
| Relayout the movie library | **Census → plan → read the exceptions → `scripts/media-apply.py`** — renames only, journal before every move; never hand-rename, never copy. |
|
||||||
| Making media metadata visible to both media servers | **Bind `/var/lib/media-meta/view`** — the overlay `scripts/media-view.sh` installs (array below, NVMe above). Never write the array; never delete through the view. |
|
| Making media metadata visible to both media servers | **Bind `/var/lib/media-meta/view`** — the overlay `scripts/media-view.sh` installs (array below, NVMe above). Never write the array; never delete through the view. **Not yet deployed on Dankserver: the live quadlets still bind the array read-only — see [[Media Metadata View]].** |
|
||||||
| Tests exist | **Run them** — verify tests pass after every change. Do not commit failing tests. |
|
| Tests exist | **Run them** — verify tests pass after every change. Do not commit failing tests. |
|
||||||
| Real secrets | **Never commit** — host `.env` files; `.env.example` holds placeholders only. |
|
| Real secrets | **Never commit** — host `.env` files; `.env.example` holds placeholders only. |
|
||||||
@@ -18,7 +18,7 @@ clients.
|
|||||||
public vhost `jelly.franksdank.cloud` behind the [[Anubis]] guard chain →
|
public vhost `jelly.franksdank.cloud` behind the [[Anubis]] guard chain →
|
||||||
[[Authentik]] outpost (post-auth route proxies `localhost:8096`).
|
[[Authentik]] outpost (post-auth route proxies `localhost:8096`).
|
||||||
|
|
||||||
## Facts (verified from the live unit, 2026-09-03)
|
## Facts (the repo quadlet; the live unit is still the pre-view generation — see Data layout)
|
||||||
|
|
||||||
| Item | Value |
|
| Item | Value |
|
||||||
|------|-------|
|
|------|-------|
|
||||||
@@ -26,7 +26,7 @@ public vhost `jelly.franksdank.cloud` behind the [[Anubis]] guard chain →
|
|||||||
| Image | `ghcr.io/jellyfin/jellyfin:latest` |
|
| Image | `ghcr.io/jellyfin/jellyfin:latest` |
|
||||||
| Network | `dankserver_appnet`; publishes 8096/tcp, 8920/tcp, 7359/udp, 1900/udp |
|
| Network | `dankserver_appnet`; publishes 8096/tcp, 8920/tcp, 7359/udp, 1900/udp |
|
||||||
| Data roots | `/home/dank/Containers/jellyfin/config:/config`, `/home/dank/Containers/jellyfin/cache:/cache` |
|
| Data roots | `/home/dank/Containers/jellyfin/config:/config`, `/home/dank/Containers/jellyfin/cache:/cache` |
|
||||||
| Media | `Volume=/var/lib/media-meta/view:/data` — the [[Media Metadata View]] overlay, **writable**; sidecars land on the NVMe, the [[Entropy Volume]] array underneath stays read-only and untouched |
|
| Media | `Volume=/var/lib/media-meta/view:/data` in git — the [[Media Metadata View]] overlay, **writable**, sidecars land on the NVMe and the [[Entropy Volume]] array underneath stays untouched. **The live unit is still pre-view: `/mnt/entropy:/data:ro`** — nothing is written into the library until the view is deployed |
|
||||||
| GPU | none — no `AddDevice`; CPU decode only (jellyfin-ffmpeg path) |
|
| GPU | none — no `AddDevice`; CPU decode only (jellyfin-ffmpeg path) |
|
||||||
| Env quirks | `DOTNET_SYSTEM_IO_DISABLEFILELOCKING=true`, `LD_PRELOAD=libjemalloc.so.2`, `MALLOC_TRIM_THRESHOLD_=131072` kept verbatim from the compose-era container (zero-drift cutover) |
|
| Env quirks | `DOTNET_SYSTEM_IO_DISABLEFILELOCKING=true`, `LD_PRELOAD=libjemalloc.so.2`, `MALLOC_TRIM_THRESHOLD_=131072` kept verbatim from the compose-era container (zero-drift cutover) |
|
||||||
| Health | `curl -f http://localhost:8096/health` every 30 s |
|
| Health | `curl -f http://localhost:8096/health` every 30 s |
|
||||||
@@ -36,23 +36,27 @@ public vhost `jelly.franksdank.cloud` behind the [[Anubis]] guard chain →
|
|||||||
```
|
```
|
||||||
/home/dank/Containers/jellyfin/config -> /config (JELLYFIN_DATA_DIR)
|
/home/dank/Containers/jellyfin/config -> /config (JELLYFIN_DATA_DIR)
|
||||||
/home/dank/Containers/jellyfin/cache -> /cache (JELLYFIN_CACHE_DIR, XDG_CACHE_HOME)
|
/home/dank/Containers/jellyfin/cache -> /cache (JELLYFIN_CACHE_DIR, XDG_CACHE_HOME)
|
||||||
/var/lib/media-meta/view -> /data (the SSD-backed overlay view)
|
/var/lib/media-meta/view -> /data (git: the overlay view)
|
||||||
|
/mnt/entropy -> /data (live today: the array, read-only)
|
||||||
```
|
```
|
||||||
|
|
||||||
Library folders point at `/data` paths, which are the view's paths: stable
|
Library folders point at `/data` paths: stable across container recreation and
|
||||||
across container recreation, and identical to the raw array layout below it.
|
identical to the raw array layout below. Which side of that path the container
|
||||||
|
is on is the open question on this host — the installed unit binds the read-only
|
||||||
|
array, and `scripts/media-view.sh status` reports `NOT MOUNTED` (2026-10-31).
|
||||||
|
|
||||||
`Local metadata` — save metadata to media folders — is enabled, and it works
|
`Local metadata` — save metadata to media folders — is enabled, and it is the
|
||||||
because `/data` is the [[Media Metadata View]] rather than the read-only array:
|
reason the view exists: `movie.nfo`, `tvshow.nfo`, `episode.nfo` and artwork
|
||||||
`movie.nfo`, `tvshow.nfo`, `episode.nfo` and artwork are written once, on the
|
written once on the NVMe upper layer are files [[Plex]] can read too. While
|
||||||
NVMe upper layer, and [[Plex]] reads the same files. Jellyfin is the fleet's
|
`/data` is bound read-only every item update logs the error below and no sidecar
|
||||||
only metadata writer.
|
is written; Jellyfin becomes the fleet's only metadata writer with the deploy
|
||||||
|
that raises the view.
|
||||||
|
|
||||||
**Failure signature:** `System.IO.IOException: Read-only file system :
|
**Failure signature:** `System.IO.IOException: Read-only file system :
|
||||||
'/data/Movies/<film>/movie.nfo'` means the container is binding the raw array
|
'/data/Movies/<film>/movie.nfo'` means the container is binding the raw array
|
||||||
instead of the view — the view is unmounted or the quadlet drifted. That used to
|
instead of the view. On Dankserver that is today's expected state — the view has
|
||||||
be the expected state (pre-view, every item update logged it); today it is a
|
never been installed (`scripts/media-view.sh status`). Once it is deployed, the
|
||||||
fault. Run `scripts/media-view.sh status`.
|
same line is a fault: the quadlet drifted or the mount died.
|
||||||
|
|
||||||
## Ports & access
|
## Ports & access
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,19 @@ The mount point is not a volume and holds nothing while unmounted. It sits insid
|
|||||||
merged door are one directory — and so the mount table grows no new `/mnt` entry
|
merged door are one directory — and so the mount table grows no new `/mnt` entry
|
||||||
next to the array.
|
next to the array.
|
||||||
|
|
||||||
|
**Not deployed on Dankserver — verified 2026-10-31.** `scripts/media-view.sh
|
||||||
|
status` answers `view: /var/lib/media-meta/view NOT MOUNTED`,
|
||||||
|
`/var/lib/media-meta` does not exist, and the installed quadlets under
|
||||||
|
`~/.config/containers/systemd/` still carry the pre-view binds — `/mnt/entropy:/data:ro`
|
||||||
|
for [[Jellyfin]], `/mnt/entropy:/mnt/entropy:ro` for [[Plex]]. Everything below
|
||||||
|
is the shape git describes, not the shape the servers run. The cutover is an
|
||||||
|
ordinary deploy: `deploy.sh dankserver --service jellyfin|plex` runs
|
||||||
|
`ensure_media_view` (`install` then `verify`) before it restarts the container,
|
||||||
|
so it must not be fired through the middle of a library scan. Until that deploy
|
||||||
|
lands there is no upper layer, so no relayout can leave ghost metadata and
|
||||||
|
`reap` has nothing to do — `media-apply.py` prints that follow-up only when the
|
||||||
|
view really is a mountpoint.
|
||||||
|
|
||||||
## Shape
|
## Shape
|
||||||
|
|
||||||
| Item | Value |
|
| Item | Value |
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ curl -s -K /tmp/jf.hdr -o /dev/null -w "jellyfin scan: %{http_code}\n"
|
|||||||
shred -u /tmp/jf.hdr
|
shred -u /tmp/jf.hdr
|
||||||
```
|
```
|
||||||
|
|
||||||
`204` is acceptance; the scan then runs on the server's own schedule.
|
`204` is acceptance — anything else is no scan queued, `401` included (a handoff logged `401` with the same key and form that answered `200` seconds later), so re-issue rather than diagnose. The scan then runs on the server's own schedule.
|
||||||
|
|
||||||
### Proving the move landed
|
### Proving the move landed
|
||||||
|
|
||||||
@@ -457,10 +457,10 @@ own journal, behind the music writer, and then refreshes the Plex section.
|
|||||||
- **Sidecar metadata does not follow a rename, because it lives on another
|
- **Sidecar metadata does not follow a rename, because it lives on another
|
||||||
filesystem.** The media servers write `.nfo` and artwork through the
|
filesystem.** The media servers write `.nfo` and artwork through the
|
||||||
[[Media Metadata View]] (NVMe upper layer); this tool moves bytes on the raw
|
[[Media Metadata View]] (NVMe upper layer); this tool moves bytes on the raw
|
||||||
array path. After any sweep or TV move, run `scripts/media-view.sh reap`, then
|
array path, so where that view is mounted a sweep or a move must be followed by
|
||||||
the same command with `--apply`: the sidecars left at the old coordinates become
|
`scripts/media-view.sh reap --apply` — stranded sidecars become ghost folders
|
||||||
ghost folders — artwork and an `.nfo` with no video — that both servers browse
|
that both servers browse and report as content. Whether this host mounts the
|
||||||
and report as content.
|
view at all is stated on that page; `media-apply.py` stays silent when it does not.
|
||||||
- **Nothing deletes through the metadata view.** A delete there writes an overlayfs
|
- **Nothing deletes through the metadata view.** A delete there writes an overlayfs
|
||||||
whiteout in the upper layer, which hides the array's copy without removing it;
|
whiteout in the upper layer, which hides the array's copy without removing it;
|
||||||
the file returns when the view is unmounted. Deletions in this pipeline happen
|
the file returns when the view is unmounted. Deletions in this pipeline happen
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public traffic can be fronted by Caddy like every other vhost.
|
|||||||
| Runtime | podman quadlet, `plex.service` (user manager) — migrated from the native `plexmediaserver.service` OS-package unit (2026-09) |
|
| Runtime | podman quadlet, `plex.service` (user manager) — migrated from the native `plexmediaserver.service` OS-package unit (2026-09) |
|
||||||
| Image | `lscr.io/linuxserver/plex:1.43.5.11029-6bf8af3f0-ls329` (pinned; verified == the registry's `latest` manifest digest at pin time: `sha256:8f655bad…e9e2334`) |
|
| Image | `lscr.io/linuxserver/plex:1.43.5.11029-6bf8af3f0-ls329` (pinned; verified == the registry's `latest` manifest digest at pin time: `sha256:8f655bad…e9e2334`) |
|
||||||
| Data root | `/var/lib/plexmediaserver` bind-mounted **in place** as `/config` (RW) — the migration moved zero bytes |
|
| Data root | `/var/lib/plexmediaserver` bind-mounted **in place** as `/config` (RW) — the migration moved zero bytes |
|
||||||
| Media | `/var/lib/media-meta/view` (the [[Media Metadata View]] overlay) bound at the same absolute path `/mnt/entropy` **read-only** — every library path in the PMS database stayed valid, zero re-scan |
|
| Media | git: `/var/lib/media-meta/view` (the [[Media Metadata View]] overlay) bound at the same absolute path `/mnt/entropy` **read-only**; live today: `/mnt/entropy` itself, read-only — the view has never been installed. Either way every library path in the PMS database stays valid, zero re-scan |
|
||||||
| Identity | fleet rootless model — container root maps to the `dank` user (1000:1000); the deployer chowns the data root on first cutover |
|
| Identity | fleet rootless model — container root maps to the `dank` user (1000:1000); the deployer chowns the data root on first cutover |
|
||||||
| Port | `32400/tcp` published on `0.0.0.0` — zero client reconfiguration |
|
| Port | `32400/tcp` published on `0.0.0.0` — zero client reconfiguration |
|
||||||
| Network | `dankserver_appnet`, alias `plex`; Tautulli reaches it at `http://plex:32400` |
|
| Network | `dankserver_appnet`, alias `plex`; Tautulli reaches it at `http://plex:32400` |
|
||||||
@@ -40,7 +40,8 @@ public traffic can be fronted by Caddy like every other vhost.
|
|||||||
The migration moved zero bytes: the container bind-mounts the native data
|
The migration moved zero bytes: the container bind-mounts the native data
|
||||||
root where it already lived, and the media archive re-mounts at
|
root where it already lived, and the media archive re-mounts at
|
||||||
`/mnt/entropy` inside the container — only the HOST side of that bind has moved
|
`/mnt/entropy` inside the container — only the HOST side of that bind has moved
|
||||||
(array, then the overlay view), so every absolute library path in the
|
(the array today; the overlay view in git has never been deployed), so every
|
||||||
|
absolute library path in the
|
||||||
PMS database keeps resolving. The one metadata change: rootless podman
|
PMS database keeps resolving. The one metadata change: rootless podman
|
||||||
cannot map the real host uid 997, so the fleet model applies — container
|
cannot map the real host uid 997, so the fleet model applies — container
|
||||||
root runs as the `dank` user, and the deployer adopts the data root with
|
root runs as the `dank` user, and the deployer adopts the data root with
|
||||||
|
|||||||
Reference in new issue
Block a user