Files
home-server/macos/Jellyfin-NFS.md
2026-02-01 23:37:38 -05:00

8.4 KiB
Raw Blame History

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 networkaware 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 networkaware 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, networkaware)

sudo tee /Library/LaunchDaemons/com.local.mountmedia.plist > /dev/null <<'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.local.mountmedia</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/sbin/mount_media_nfs.sh</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>StartInterval</key>
  <integer>300</integer>
  <key>KeepAlive</key>
  <dict>
    <key>NetworkState</key>
    <true/>
  </dict>
  <key>StandardOutPath</key>
  <string>/var/log/mount_media.out</string>
  <key>StandardErrorPath</key>
  <string>/var/log/mount_media.err</string>
</dict>
</plist>
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.

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 clients 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 doesnt 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; its not needed here.
  • Adjust SERVER, MOUNT, and StartInterval to suit your environment.