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>
8.2 KiB
jellyfin-server
Jellyfin media server in an unprivileged Incus container on nuc.
- Image:
images:ubuntu/24.04, Jellyfin from the official repo (repo.jellyfin.org) - IP:
192.168.0.5(LAN bridge) — web UI/API onhttp://192.168.0.5:8096 - iGPU render node (
/dev/dri/renderD128) passed for QSV/VAAPI hardware transcoding - Media library: host
/srv/media— since 2026-08-30 an NFSv4 mount fromnas(192.168.0.4:/media, datasettank/media), mounted at/mediain the container withshift=falseandreadonly=true. See nas/nas-install.md §8. - Port 8096 additionally proxied to the host address (
webproxy device)
Install script
Run as root on the Incus host: MEDIA_DIR=/srv/media ./install.sh
#!/usr/bin/env bash
# Adapted from:
# https://forgejo.benoit.jp.net/benoitjpnet/Laminar/src/branch/main/cfg/jobs/jellyfin.run
set -euxo pipefail
CNAME="${CNAME:-jellyfin-server}"
IMAGE="${IMAGE:-images:ubuntu/24.04}"
MEDIA_DIR="${MEDIA_DIR:-/srv/media}" # host directory containing your media library
[ -d "$MEDIA_DIR" ] || { echo "MEDIA_DIR=$MEDIA_DIR does not exist on the host"; exit 1; }
incus launch "$IMAGE" "$CNAME"
# Wait until the container has working DNS/network
for i in $(seq 1 30); do
incus exec "$CNAME" -- getent hosts repo.jellyfin.org >/dev/null 2>&1 && break
sleep 2
done
incus config set "$CNAME" environment.DEBIAN_FRONTEND=noninteractive
incus exec "$CNAME" -- timedatectl set-timezone Europe/Paris
incus config set "$CNAME" environment.DEBCONF_NONINTERACTIVE_SEEN=true
# --- Jellyfin from the official repository -----------------------------------
incus exec "$CNAME" -- apt-get update
incus exec "$CNAME" -- apt-get upgrade -y
incus exec "$CNAME" -- apt-get install -y --no-install-recommends curl gnupg ca-certificates
incus exec "$CNAME" -- mkdir -p /etc/apt/keyrings
incus exec "$CNAME" -- bash -c 'curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | gpg --dearmor -o /etc/apt/keyrings/jellyfin.gpg'
incus exec "$CNAME" -- bash -c 'cat > /etc/apt/sources.list.d/jellyfin.sources <<EOF
Types: deb
URIs: https://repo.jellyfin.org/ubuntu
Suites: noble
Components: main
Architectures: amd64
Signed-By: /etc/apt/keyrings/jellyfin.gpg
EOF'
incus exec "$CNAME" -- apt-get update
incus exec "$CNAME" -- apt-get install -y jellyfin
# Optional: OpenCL runtime, only needed for HDR tone-mapping during transcodes
incus exec "$CNAME" -- apt-get install -y --no-install-recommends intel-opencl-icd || true
# --- iGPU render node for hardware transcoding (QSV/VAAPI) -------------------
RENDER_GID="$(incus exec "$CNAME" -- getent group render | cut -d: -f3)"
incus config device add "$CNAME" igpu gpu gid="$RENDER_GID"
incus exec "$CNAME" -- usermod -aG render,video jellyfin
# --- Media library (read-only is fine unless you let Jellyfin save NFO/artwork
# next to the files) --------------------------------------------------
# NOTE: shift=true requires idmapped-mount support on MEDIA_DIR's filesystem —
# fine on local fs (ext4/btrfs/xfs), NOT supported on CIFS/NFS mounts.
incus config device add "$CNAME" media disk source="$MEDIA_DIR" path=/media shift=true
# --- Expose the web UI/API on the host's LAN address -------------------------
incus config device add "$CNAME" web proxy listen=tcp:0.0.0.0:8096 connect=tcp:127.0.0.1:8096
incus config set "$CNAME" boot.autostart=true
incus restart "$CNAME"
echo "Done. Open http://<host-ip>:8096 to run the setup wizard."
Media over NFS (2026-08-30)
The library moved to nas when the 4 TB left nuc's USB enclosure. The
container keeps the same path, so everything below still applies — only
the mount underneath /srv/media changed.
# nuc host: /etc/fstab
192.168.0.4:/media /srv/media nfs4 ro,_netdev,soft,timeo=100,retrans=3 0 0
shift=true cannot be used: idmapped mounts are not supported on
NFS (nor CIFS). Per the troubleshooting note below, dropping the shift is
enough for a read-only library — the export uses all_squash so files
carry synthetic world-readable ownership:
incus stop jellyfin-server # shift cannot be hot-applied
incus config device set jellyfin-server media shift=false
incus config device set jellyfin-server media readonly=true
incus start jellyfin-server
⚠️ Two traps:
- Boot ordering. Add
remote-fs.targetto nuc's/etc/systemd/system/incus.service.d/after-zfs.conf, or the container starts against an empty mountpoint and Jellyfin shows an empty library (and may prune the library metadata). softis deliberate. A hung nas should fail Jellyfin's reads, not wedge nuc's processes in uninterruptible sleep the way the suspendedusb4tpool did (usb4t-dropouts.md).
transmission-bt is no longer on nuc — it moved to nas and writes to
the dataset locally (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. 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:
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:
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
- Run the setup wizard; add libraries pointing at
/media/.... - Dashboard → Playback → Transcoding:
- Hardware acceleration: Intel QuickSync (QSV) (fallback: VA-API),
device
/dev/dri/renderD128. - Enable hardware decoding for the codecs you use; Alder Lake-N does H.264/HEVC/VP9/AV1 decode and H.264/HEVC encode.
- Enable "Low-Power" encoders only if GuC/HuC is loaded (see below).
- Hardware acceleration: Intel QuickSync (QSV) (fallback: VA-API),
device
Troubleshooting / notes
Failed to setup device mount "media": idmapping abilities are required but aren't supported on system— the media source is on a filesystem without idmapped-mount support (CIFS/NFS). Either move the media to a local fs, or drop the shift (incus config device set jellyfin-server media shift=false; on CIFS files are world-readable synthetic ownership, enough for a read-only library). Hot-applyingshifton a running container fails — stop it first.- QSV "low-power" encode fails — Alder Lake-N needs GuC/HuC firmware
submission. On the host:
echo 'options i915 enable_guc=3' > /etc/modprobe.d/i915.conf,update-initramfs -u, reboot; verify withdmesg | grep -i 'guc\|huc'. Or just untick the low-power options. - GPU check:
incus exec jellyfin-server -- ls -l /dev/dri. - Pin a version with
apt-get install jellyfin=<ver>+ubu2404for reproducibility; keep major versions in sync with the client's JMP. - To bake a reusable image instead: run the script, then
incus publish jellyfin-server --alias jellyfin-server-<ver>.