fix(media): order the sidecar reap only where the view is mounted

Every apply and rollback printed "next: scripts/media-view.sh reap", but on
Dankserver there is nothing to reap: the metadata view has never been installed
(`media-view.sh status` -> NOT MOUNTED, `/var/lib/media-meta` absent) and the
live quadlets still bind the array read-only. All three relayout arms tonight
issued that order — 242,084 moves — and it pointed at a directory that does not
exist. Where no overlay is mounted the servers bind the array itself, so the
sidecars moved with the media.

`--metadata-view` (default /var/lib/media-meta/view) names the overlay and the
note now rides only when that path is a mountpoint. Red first: two new cases in
tests/media_relayout.bats (apply and rollback); the shared helper names a real
mountpoint so the pre-existing presence assertion keeps its meaning. 37 ok.
This commit is contained in:
Frank committed 2026-10-31 03:13:53 -07:00
1 parent 96944f0b17
commit 9eb85ea1f9
2 files changed
+55 -6

No files matched your search

+25 -5
View File
@@ -32,6 +32,12 @@ cross a filesystem (rename cannot do it and a silent copy at 24 TB would not).
--force relaxes only the guards that are opinion (journal already rolled back,
paths outside --root); it never allows overwriting a file.
--metadata-view names the overlay the media servers read their library through
(default /var/lib/media-meta/view). The sidecar follow-up printed after an apply
or rollback is conditioned on that path being a mountpoint: where no view is
mounted the servers bind the array itself, the sidecars moved with the media,
and a reap order would point at a directory that is not there.
The journal defaults to `media-relayout.jsonl` BESIDE the library root, never
inside it: a log inside the library is a file Jellyfin will index.
"""
@@ -114,13 +120,24 @@ def read_plan(path, root, force):
return mkdirs, keeps, moves
# A relayout moves bytes on the raw array; the sidecars the media servers wrote
# live in the SSD overlay and stay at the old coordinates. Naming the follow-up
# here is the point of use for that fact — see the Media Metadata View page.
# The media servers may read the library through the SSD overlay view
# (scripts/media-view.sh). A relayout moves bytes on the raw array, so sidecars
# the servers wrote through that view stay at the old coordinates — naming the
# follow-up here is the point of use for that fact, but only where the view
# exists: where it is not mounted the servers bind the array itself, the
# sidecars moved with the media, and a reap order points at a directory that
# is not there. See the Media Metadata View page for the host's state.
METADATA_VIEW = "/var/lib/media-meta/view"
METADATA_HINT = ("note: sidecars written through the metadata view did not follow"
" these moves — next: scripts/media-view.sh reap")
def metadata_note(args):
"""Print the sidecar follow-up only when the overlay is really mounted."""
if os.path.ismount(args.metadata_view):
print(METADATA_HINT)
def do_apply(args, root, journal, mkdirs, keeps, moves):
"""Preflight everything, then folders, then renames. Never a partial start."""
problems = []
@@ -202,7 +219,7 @@ def do_apply(args, root, journal, mkdirs, keeps, moves):
f" {dirs_new} folders created, {len(keeps)} files stayed put")
print(f"journal: {journal.path} — undo with: media-apply.py rollback"
f" --journal {journal.path} --root {root} --apply")
print(METADATA_HINT)
metadata_note(args)
return 0
@@ -386,7 +403,7 @@ def do_rollback(args, root, journal):
t=datetime.datetime.now(datetime.timezone.utc).isoformat())
print(f"rolled back: {undone} files back where the census found them,"
f" {dirs_gone} empty folders removed, {recreated} swept folders recreated")
print(METADATA_HINT)
metadata_note(args)
return 0
@@ -398,6 +415,9 @@ def main():
ap.add_argument("--journal")
ap.add_argument("--apply", action="store_true", help="actually change the library")
ap.add_argument("--force", action="store_true", help="relax the opinion guards")
ap.add_argument("--metadata-view", default=METADATA_VIEW,
help="overlay the media servers read through; the sidecar"
" note prints only when this path is a mountpoint")
args = ap.parse_args()
root = os.path.abspath(args.root)