# 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 on `http://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 from `nas`** (`192.168.0.4:/media`, dataset `tank/media`), mounted at `/media` in the container with **`shift=false`** and `readonly=true`. See [nas/nas-install.md](../nas/nas-install.md) §8. - Port 8096 additionally proxied to the host address (`web` proxy device) ## Install script Run as root on the Incus host: `MEDIA_DIR=/srv/media ./install.sh` ```bash #!/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 <: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. ```sh # 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: ```sh 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.target` to 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). - **`soft` is deliberate.** A hung nas should fail Jellyfin's reads, not wedge nuc's processes in uninterruptible sleep the way the suspended `usb4t` pool did ([usb4t-dropouts.md](usb4t-dropouts.md)). `transmission-bt` is no longer on nuc — it moved to nas and writes to 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/...`. 2. 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). ## 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-applying `shift` on 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 with `dmesg | 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=+ubu2404` for 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-`.