jellyfin: scan on download completion — inotify cannot work over NFS

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) <noreply@anthropic.com>
This commit is contained in:
Julien Lutran
2026-08-31 20:21:36 +02:00
co-authored by Claude Opus 5
parent 1721d7b05d
commit 9468f30f92
2 changed files with 107 additions and 0 deletions
+67
View File
@@ -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 — - The image server check can make `incus launch` hang on slow WAN —
launching from the cached image fingerprint (`incus image list`) launching from the cached image fingerprint (`incus image list`)
bypasses it. 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 '<new-key>' | 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.
+40
View File
@@ -116,6 +116,46 @@ incus start jellyfin-server
the dataset locally ([nas/transmission-bt.md](../nas/transmission-bt.md)), the dataset locally ([nas/transmission-bt.md](../nas/transmission-bt.md)),
so nuc's mount is read-only and there is exactly one writer. 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: <key>" 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 ## First-run configuration
1. Run the setup wizard; add libraries pointing at `/media/...`. 1. Run the setup wizard; add libraries pointing at `/media/...`.