docs(vault): the scan pages name their drivers, and a class no screen sees

Plex.md and Jellyfin.md keep every law and point at the driver that now carries
them, so the procedure has one owner and the pages keep the failure signatures.
Jellyfin.md also records where `jellyfin.db` actually lives on this host (the
quadlet's config bind, not the stock path) and demotes `immutable=1` on the
`ApiKeys` read to the `-shm` fallback, because the database is WAL and
`immutable` skips the log. Plex.md gains the live section keys and the
`scanned_at == scannedAt` fact.

Media Relayout.md's hand-off after an apply names both drivers and states the
ordering law: Jellyfin first and alone, since its one door walks every library
while Plex forks a scanner per section, and the array is what both share.

Media Duplicate Prune.md gains the guard the 2026-11-04 hold taught: 32 files
carrying real film titles were 8-20-second h264 clips (49 MiB, 25 of them in a
`_release-group watermarks/` folder), invisible to every duplicate screen
because nothing shares their size and the feature-length guard hides them. The
census that finds the next batch is a duration floor, not a hash.
This commit is contained in:
Frank committed 2026-11-04 11:29:03 -08:00
1 parent e7d7c6eea2
commit 95375683d5
4 files changed
+73 -59

No files matched your search

+23 -20
View File
@@ -309,33 +309,36 @@ scripts/jellyfin-providers.sh verify # plugin + key LENGTHS, never key bytes
## Triggering a scan
The scan key lives in `~/.config/secrets/jellyfin` (mode 600; the same value is
`JELLYFIN_API_KEY` in the ignored `dankserver/.env`). It rides a mode-600 curl
config, never a header argument, in the `MediaBrowser` form: 12.4 authenticates
only `Authorization: MediaBrowser Token="…"` and refuses a bare `X-Emby-Token`
for every key, valid or not. Before believing a 401, prove the key against the
server's own table — `select count(*) from ApiKeys where AccessToken = ?` on
`jellyfin.db` opened `mode=ro&immutable=1`. The backslashes below matter:
Run the driver from the deploy checkout: `scripts/media-refresh-jellyfin.py
trigger`, then `… wait`. It calls the library settled only on the rule below — the
task seen `Running` (or a run that finished while it watched) first, then
`--require-quiet` reads with no scan and no writes — and refuses by name when the
scan never started, because a watcher that saw no `Running` would otherwise report
the pre-scan library as the fresh one. `… status --after $(stat -c %Y <journal>)`
is the "has the scan reached my moves" answer, read from the task's `EndTimeUtc`.
```bash
umask 077
{ printf 'header = "Authorization: MediaBrowser Token=\\"%s\\""\n' \
"$(cat ~/.config/secrets/jellyfin)"
echo 'url = "http://127.0.0.1:8096/Library/Refresh"'
echo 'request = "POST"'; } >/tmp/jf.hdr
curl -s -K /tmp/jf.hdr -o /dev/null -w "jellyfin scan: %{http_code}\n"
shred -u /tmp/jf.hdr
```
It carries every law below. Hand-written, the door is a `curl -K` on a mode-600
config: the scan key lives in `~/.config/secrets/jellyfin` (mode 600; the same
value is `JELLYFIN_API_KEY` in the ignored `dankserver/.env`) and rides as
`Authorization: MediaBrowser Token="…"` — 12.4 refuses a bare `X-Emby-Token` for
every key, valid or not — never as a header argument, because
`/proc/<pid>/cmdline` is world-readable. `recentMedia` counts
`BaseItems.DateModified` rows in `jellyfin.db`, which on this host sits in the
quadlet's config bind at `/home/dank/Containers/jellyfin/config/data/`, not the
stock `~/.local/share/jellyfin`.
`204` is acceptance; anything else queued no scan, so re-issue the identical
request instead of diagnosing. A `401` from a key that answers `200` on a
hand-typed request is this block's quoting, not the server: bash `printf`
collapses `\"` to `"`, curl ends the quoted value at that quote, and Jellyfin
receives `Authorization: MediaBrowser Token=` with nothing after it (`\\"` answers
204, `\"` answers 401 — both run on this host).
Reading the progress with the same config trick takes **one** URL: a config `url`
line *plus* a command-line URL makes curl perform both transfers, and the second
dumps the whole `ScheduledTasks` body onto your stdout.
204, `\"` answers 401 — both run on this host). Before believing a 401, prove the
key against the server's own table — `select count(*) from ApiKeys where
AccessToken = ?` on `jellyfin.db` opened plain `mode=ro` (`immutable=1` only as
the fallback for a database whose `-shm` cannot be read; it skips the write-ahead
log, see [[Media Relayout]]). Reading progress with the same config trick takes
**one** URL: a config `url` line *plus* a command-line URL makes curl perform both
transfers, and the second dumps the whole `ScheduledTasks` body onto your stdout.
One accepted refresh is the whole procedure. A second one while a scan runs moves
`Scan Media Library` to `Cancelling`, and once the runner holds `Cancelling` every