Commit Graph
3 Commits
Author SHA1 Message Date
dank 6d79709cc0 fix(deploy): make the view's upper base before installing the view
`ensure_media_view` ran `install` on a host where the documented base directory
did not exist, and `install` refuses by design ("put it on the SSD first") — so
tonight's cutover would have died at the restart step with nobody beside the
box. `media-view.sh ensure-base` now makes the documented directory, and the
deployer calls it first; two seams cover it (the subcommand's behaviour, and the
ordering invariant inside `ensure_media_view`).

Docs flip with the deploy: the view is live on Dankserver (both media servers
verified resolving real media through it), so every "never been deployed" claim
in the view, Plex, Jellyfin and AGENTS pages is corrected, and three laws join
them — a container restart discards the in-flight library scan and nothing
resumes it; Jellyfin 12.4 authenticates only the `MediaBrowser Token` form, so a
401 says nothing about the key until the `ApiKeys` table is read; and Plex
answers 32400 over TLS only, which makes a plaintext health probe read as dead.
2026-10-31 19:33:16 -07:00
Frank 3b29e173c2 refactor(media): mount the view inside /var/lib/media-meta instead of under /mnt
The merged door was /mnt/media, which read like a third volume and split the host
state across two trees. It is now /var/lib/media-meta/view, so the array layer,
the SSD upper layer, the kernel workdir, the reap trash and the merged tree are
one directory — and the mount table gains no new /mnt entry beside the array.

systemd names a mount unit after its ESCAPED path, so the repo copy keeps the
readable name media-meta-view.mount while `install` writes the host file as
var-lib-media\x2dmeta-view.mount; `plan` prints both and the page records them, so
nobody hunts for a unit that "does not exist". install also creates the mount
point itself, because systemd will not.

The seam now additionally pins that the unit's Where= is exactly the path both
containers bind — guard and bind cannot drift apart. The Quadlet Lifecycle
phantom-path example is corrected too: it named an invented /mnt/media/*, while
the real phantom is the legacy /opt/jellyfin/* that dankserver/compose.yaml still
carries for a service whose roots moved to /home/dank/Containers/jellyfin.
2026-10-30 22:05:31 -07:00
Frank 3e4b4f24f7 feat(media): share one metadata set between Jellyfin and Plex over an SSD overlay
Both media servers read a Kodi-format sidecar (movie.nfo / tvshow.nfo /
episode.nfo) only when it sits next to the media file, and the library lives on
the RAID6 array, deliberately mounted read-only for every container and slow to
seek for a thousand tiny XML reads. So neither server binds the array any more:
both bind /mnt/media, an OverlayFS merge of the array (lowerdir, never written)
with /var/lib/media-meta on the NVMe (upperdir, where every write lands).

Jellyfin binds the view rw at /data and is the fleet's only metadata writer;
Plex binds it ro at /mnt/entropy, the container path its own database already
recorded, so only the host side of that bind moved and its library paths keep
resolving.

scripts/media-view.sh plans, installs, verifies, reports and reaps the view. It
refuses to mount over an unmounted array, refuses an upper layer on rotational
storage, and refuses a non-root install. deploy-remote.sh raises the view before
it restarts either media server, both units carry
ConditionPathIsMountPoint=/mnt/media so a missing view skips the start instead of
letting a scan clear the library database, and the planner routes the installer
and dankserver/host/** to the jellyfin+plex pair.

tests/media_overlay.bats (11 seams) pins the resolved shape, both refusals, the
two binds and their guard, the deployer's ordering, the planner mapping, and the
reap behaviour against a fixture (ghosts listed, then moved to the SSD trash --
files that exist only above are sidecars, not ghosts, so only directories are
candidates).
2026-10-30 21:41:51 -07:00