# Jellyfin + macOS: Persistent NFS Mount (Fix for Libraries Randomly “Clearing”) This README documents the working fix I applied when Jellyfin (on a Mac mini) periodically “lost” or cleared my Movies/TV libraries that live on a NAS mounted over NFS. It includes the exact commands, files, and rationale so I can reproduce it later. --- ## Problem Summary - Symptom: Every day or two, Jellyfin showed empty Movies/TV libraries. - Media location: NFS share at `/Volumes/media` from NAS `192.168.50.105:/media`. - Root cause: macOS was using autofs (`/- /etc/auto_nfs`). autofs can unmount after inactivity or brief network blips. When the mount disappears during a Jellyfin scan/file-watch, Jellyfin sees files as missing and removes them from its DB. ## Solution Summary - Stop using autofs for this path. - Create a persistent mount at boot using a LaunchDaemon and a small network‑aware mount script. - The script: - Is idempotent: does nothing if already mounted. - Checks NAS reachability first. - Logs to `/var/log/mount_media.(out|err)`. - Optionally restarts Jellyfin (Homebrew service) if the mount comes back. --- ## Prerequisites / Assumptions - macOS with admin (sudo) access. - NFS server: `192.168.50.105` exporting `/media` (adjust as needed). - Mount point: `/Volumes/media` (adjust as needed). - Jellyfin installed (optional Homebrew service restart in script). > Tip: If your NAS requires privileged source ports for NFSv4, `resvport` helps. The script falls back to `noresvport` if needed. --- ## Steps (copy/paste commands) ### 1) Disable autofs for this path and unmount any automounted share ``` # Backup and comment out the direct map for NFS sudo cp /etc/auto_master /etc/auto_master.bak.$(date +%F_%H%M%S) sudo sed -i.bak 's|^/- /etc/auto_nfs|#/- /etc/auto_nfs|' /etc/auto_master # Reload automountd (will unmount /Volumes/media if it was automounted) sudo automount -vc # Ensure the mountpoint is not currently mounted (ignore errors if already unmounted) sudo umount /Volumes/media 2>/dev/null || sudo umount -f /Volumes/media 2>/dev/null || true ``` > Note: If `chown`/`chmod` say “Operation not permitted,” the path is still mounted (or your NAS has root-squash). Unmount first. --- ### 2) Create the network‑aware mount script ``` sudo mkdir -p /usr/local/sbin sudo tee /usr/local/sbin/mount_media_nfs.sh > /dev/null <<'SH' #!/bin/sh set -eu LOG="/var/log/mount_media.out" ERR="/var/log/mount_media.err" MOUNT="/Volumes/media" SERVER="192.168.50.105:/media" HOST="${SERVER%%:*}" # Ensure mountpoint exists [ -d "$MOUNT" ] || mkdir -p "$MOUNT" # If already mounted as NFS, exit quietly if mount -t nfs | awk '{print $3}' | grep -qx "$MOUNT"; then echo "$(date) already mounted: $MOUNT" >>"$LOG" exit 0 fi # Preflight: only try to mount when NFS port is reachable if ! /usr/bin/nc -G 2 -z "$HOST" 2049 >/dev/null 2>&1; then echo "$(date) NAS not reachable on 2049, skipping mount" >>"$LOG" exit 0 fi echo "$(date) mounting $SERVER -> $MOUNT" >>"$LOG" /sbin/mount -t nfs -o resvport,hard,nfsvers=4.0 "$SERVER" "$MOUNT" >>"$LOG" 2>>"$ERR" || \ /sbin/mount -t nfs -o noresvport,hard,nfsvers=4.0 "$SERVER" "$MOUNT" >>"$LOG" 2>>"$ERR" # Verify mount succeeded via mount(8) if mount -t nfs | awk '{print $3}' | grep -qx "$MOUNT"; then echo "$(date) mount OK: $MOUNT" >>"$LOG" # Optional: restart Jellyfin if installed via Homebrew if command -v brew >/dev/null 2>&1 && brew services list | grep -q '^jellyfin\b'; then echo "$(date) restarting Jellyfin (brew services)" >>"$LOG" brew services restart jellyfin >>"$LOG" 2>>"$ERR" || true fi else echo "$(date) mount FAILED" >>"$ERR" exit 1 fi SH sudo chmod 755 /usr/local/sbin/mount_media_nfs.sh ``` --- ### 3) Create the LaunchDaemon (mount at boot, re-check periodically, network‑aware) ``` sudo tee /Library/LaunchDaemons/com.local.mountmedia.plist > /dev/null <<'PLIST' Label com.local.mountmedia ProgramArguments /usr/local/sbin/mount_media_nfs.sh RunAtLoad StartInterval 300 KeepAlive NetworkState StandardOutPath /var/log/mount_media.out StandardErrorPath /var/log/mount_media.err PLIST sudo chown root:wheel /Library/LaunchDaemons/com.local.mountmedia.plist sudo chmod 644 /Library/LaunchDaemons/com.local.mountmedia.plist sudo plutil -lint /Library/LaunchDaemons/com.local.mountmedia.plist sudo launchctl bootout system /Library/LaunchDaemons/com.local.mountmedia.plist 2>/dev/null || true sudo launchctl bootstrap system /Library/LaunchDaemons/com.local.mountmedia.plist sudo launchctl enable system/com.local.mountmedia sudo launchctl kickstart -k system/com.local.mountmedia ``` --- ### 4) Run once and verify ``` # Run once now (idempotent; logs "already mounted" if present) sudo /usr/local/sbin/mount_media_nfs.sh # LaunchDaemon status sudo launchctl print system/com.local.mountmedia | egrep 'state|last exit|PID' || true # Mount status (should NOT say "automounted") mount | grep " on /Volumes/media " # NFS mount parameters nfsstat -m | sed -n '/\/Volumes\/media/,+12p' # Script logs tail -n 100 /var/log/mount_media.out /var/log/mount_media.err 2>/dev/null || true ``` --- ### 5) Jellyfin settings - Temporarily disable “Enable real-time monitoring” for libraries under `/Volumes/media` until you confirm the mount stays stable. - Then run “Scan Library Files” to repopulate anything previously removed. --- ### 6) Reboot test (recommended) ``` sudo shutdown -r now ``` After reboot: ``` mount | grep " on /Volumes/media " || echo "Not mounted yet" sudo launchctl print system/com.local.mountmedia | egrep 'state|last exit' || true tail -n 100 /var/log/mount_media.out /var/log/mount_media.err 2>/dev/null || true ``` --- ## Rationale for Key Choices - Persistent mount (LaunchDaemon) instead of autofs: - autofs can unmount after inactivity; Jellyfin then removes items it thinks are gone. - LaunchDaemon ensures the mount is present before scans and remains mounted. - NFS options: - `hard`: Blocks I/O until server responds, avoiding spurious “file missing” errors. - `nfsvers=4.0`: Matches typical NAS defaults and the client’s chosen version. - `resvport` then fallback `noresvport`: Some servers require privileged ports; the script tries both. - Network preflight: - Check TCP/2049 reachability to avoid “Network is unreachable” failures (exit code 51) at boot or during link flaps. - Logging: - `/var/log/mount_media.out` and `.err` make it easy to correlate with Jellyfin logs. --- ## Troubleshooting - “Operation not permitted” when `chown`/`chmod`: - The path is mounted over NFS, and root-squash likely prevents ownership changes from the client. Unmount first or change ownership on the NAS. - LaunchDaemon errors: - Validate plist: `sudo plutil -lint /Library/LaunchDaemons/com.local.mountmedia.plist` - Service state: `sudo launchctl print system/com.local.mountmedia` - Mount health: - `nfsstat -m` should show vers=4.0, hard, resvport/noresvport. - Network/power: - Prevent system sleep that drops the NIC; enable “Wake for network access.” --- ## Optional: If you must keep autofs Increase the autofs timeout so it doesn’t unmount on brief inactivity (less ideal than the LaunchDaemon approach): ``` sudo cp /etc/auto_master /etc/auto_master.bak.$(date +%F_%H%M%S) sudo sed -i.bak -E 's|^/-[[:space:]]+/etc/auto_nfs$|/- -timeout=604800 /etc/auto_nfs|' /etc/auto_master sudo automount -vc ``` --- ## Reverting To revert to autofs: ``` # Stop and remove LaunchDaemon sudo launchctl bootout system /Library/LaunchDaemons/com.local.mountmedia.plist sudo rm -f /Library/LaunchDaemons/com.local.mountmedia.plist # Restore /etc/auto_master (uncomment direct map) and reload sudo sed -i.bak 's|^#/- /etc/auto_nfs|/- /etc/auto_nfs|' /etc/auto_master sudo automount -vc ``` --- ## Notes - Change permissions/ownership on the NFS export from the NAS, not the macOS client (root-squash). - `showmount` may fail against NFSv4-only servers; it’s not needed here. - Adjust `SERVER`, `MOUNT`, and `StartInterval` to suit your environment.