From 9468f30f92323fcb86c27bef37e2468c20e72914 Mon Sep 17 00:00:00 2001 From: Julien Lutran Date: Mon, 31 Aug 2026 20:21:32 +0200 Subject: [PATCH] =?UTF-8?q?jellyfin:=20scan=20on=20download=20completion?= =?UTF-8?q?=20=E2=80=94=20inotify=20cannot=20work=20over=20NFS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Finished downloads stopped appearing in Jellyfin after the 2026-08-30 storage move, and everything looked healthy: the file was on nas, visible through NFS inside the container, and /media/downloads is a configured library. The cause is that Jellyfin watches libraries with inotify, which only reports changes made through the local mount — transmission now writes on nas while jellyfin-server reads over NFS on nuc, so no event ever reaches it. EnableRealtimeMonitor is true and SupportsLibraryMonitor reports true, which is why it looks fine. Previously both shared one local dataset on nuc and it worked. transmission now calls Jellyfin's /Library/Refresh via script-torrent-done. The hook always exits 0 and never blocks (transmission runs it synchronously; a hanging hook stalls the daemon), and both the success and missing-key paths are tested. nuc is usually powered off, so a failed request is expected and logged rather than treated as an error — the scheduled scan catches up. Also records that nuc's mount is read-only, so reorganising downloads into movies/tv-shows must now happen on nas, and the ordered checklist for "my download is not in Jellyfin" — the first three checks all passed when this was hit, which is what made it confusing. Co-Authored-By: Claude Opus 5 (1M context) --- nas/transmission-bt.md | 67 ++++++++++++++++++++++++++++++++++++++++++ nuc/jellyfin-server.md | 40 +++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/nas/transmission-bt.md b/nas/transmission-bt.md index e3fe580..33e4b4f 100644 --- a/nas/transmission-bt.md +++ b/nas/transmission-bt.md @@ -199,3 +199,70 @@ incus exec transmission-bt -- bash -c "ping -c1 -W2 8.8.8.8 || echo kill-switch - The image server check can make `incus launch` hang on slow WAN — launching from the cached image fingerprint (`incus image list`) bypasses it. + +## Jellyfin library scan on completion (2026-08-31) + +Jellyfin cannot notice finished downloads by itself any more. It watches +libraries with **inotify**, but since the media moved to nas the writer +(transmission, here) and the reader (`jellyfin-server` on nuc, over NFS) +are on different machines — an inotify event never crosses that. Before +the move both shared one local dataset on nuc, so it just worked. + +So transmission tells Jellyfin explicitly, via +`script-torrent-done`: + +```json +"script-torrent-done-enabled": true, +"script-torrent-done-filename": "/usr/local/bin/jellyfin-scan.sh" +``` + +The hook POSTs to Jellyfin's `/Library/Refresh`: + +```sh +#!/bin/sh +KEY_FILE=/etc/jellyfin-scan.key +JF=http://192.168.0.5:8096 +NAME="${TR_TORRENT_NAME:-unknown}" +[ -r "$KEY_FILE" ] || { logger -t jellyfin-scan "no readable key file; skipped ($NAME)"; exit 0; } +KEY=$(tr -d " \t\r\n" < "$KEY_FILE") +if curl -fsS -m 15 -X POST -H "X-Emby-Token: $KEY" "$JF/Library/Refresh" >/dev/null 2>&1; then + logger -t jellyfin-scan "library scan requested after: $NAME" +else + logger -t jellyfin-scan "library scan request FAILED (nuc off?) after: $NAME" +fi +exit 0 +``` + +Design points, each of which matters: + +- **Always `exit 0`, never block.** transmission runs the hook + synchronously; a hanging or failing hook stalls the daemon. Both paths + are tested — success and unreadable-key both exit 0. +- **nuc is usually powered off.** The request then fails, logs + `FAILED (nuc off?)`, and Jellyfin picks the file up on its next + scheduled scan. Not an error worth alerting on. +- **The key file is `640 root:debian-transmission`** — the hook runs as + `debian-transmission`, so it must be group-readable, and nothing wider. +- Reachable despite the kill switch: `192.168.0.5` is on the directly + connected LAN, so it needs no default route. + +Verify: + +```sh +incus exec transmission-bt -- su -s /bin/sh debian-transmission \ + -c 'TR_TORRENT_NAME=selftest /usr/local/bin/jellyfin-scan.sh' +incus exec transmission-bt -- journalctl -t jellyfin-scan -n 3 +``` + +**Rotating the key**: create a new one in Jellyfin (Dashboard → API Keys), +then + +```sh +printf %s '' | incus exec transmission-bt -- sh -c \ + 'umask 027; cat > /etc/jellyfin-scan.key; chown root:debian-transmission /etc/jellyfin-scan.key' +``` + +⚠️ Edit `settings.json` only while the daemon is **stopped** — +transmission rewrites the whole file on shutdown and will silently +discard changes made underneath it. + diff --git a/nuc/jellyfin-server.md b/nuc/jellyfin-server.md index c2f6ba2..7bb4026 100644 --- a/nuc/jellyfin-server.md +++ b/nuc/jellyfin-server.md @@ -116,6 +116,46 @@ incus start jellyfin-server the dataset locally ([nas/transmission-bt.md](../nas/transmission-bt.md)), so nuc's mount is read-only and there is exactly one writer. +## ⚠️ Real-time monitoring does not work over NFS (2026-08-31) + +Libraries have `EnableRealtimeMonitor=true` and Jellyfin reports +`SupportsLibraryMonitor: true`, but **new files never appear on their +own**. Jellyfin watches with inotify, which only reports changes made +through the local mount; transmission writes them on **nas**, so nuc's +NFS client sees nothing. Jellyfin looks healthy and silently misses +everything until a scan. + +This is a regression from the 2026-08-30 storage move — before it, +transmission and jellyfin-server shared one local dataset on nuc and +inotify fired normally. + +**Fix in place:** transmission calls Jellyfin's `/Library/Refresh` when a +download completes — see +[nas/transmission-bt.md](../nas/transmission-bt.md). Downloads appear +within seconds; if nuc is powered off the request fails harmlessly and +the scheduled scan catches up. + +Manual scan (UI): Dashboard → Scheduled Tasks → **Scan Media Library**. +By API: + +```sh +curl -X POST -H "X-Emby-Token: " http://192.168.0.5:8096/Library/Refresh # expect 204 +``` + +Diagnosing "my download is not in Jellyfin", in order — the first three +were all fine when this was hit, which is what made it confusing: + +```sh +ls /export/media/downloads/ # on nas: file there? +incus exec jellyfin-server -- ls /media/downloads/ # visible through NFS? +incus exec jellyfin-server -- find /var/lib/jellyfin/root -name '*.mblink' -exec cat {} + # in a library path? +incus exec jellyfin-server -- cat /var/lib/jellyfin/data/ScheduledTasks/*.js | grep -o '"Name":"Scan Media Library".*' # when did it last scan? +``` + +⚠️ **nuc's mount is read-only.** Reorganising finished downloads into +`/media/movies` or `/media/tv-shows` can no longer be done from nuc — do +it on nas under `/export/media/`. + ## First-run configuration 1. Run the setup wizard; add libraries pointing at `/media/...`.