nuc: media over NFS from nas, on-demand role, i915 and input traps
nuc keeps only what needs its iGPU. blocky, privoxy and transmission-bt moved to nas so the box can be powered off when not watching Jellyfin or using the Spotify kiosk. - /srv/media is now an NFSv4 mount from nas; jellyfin-server reads it with shift=false (idmapped mounts are unsupported on NFS, as the container doc already noted for CIFS) and readonly=true - replication to nas is a systemd timer with Persistent=true, not cron — an on-demand host misses its 03:30 window and cron cannot catch up Two failures documented in full, both diagnosed from the wrong layer first: - booting with the TV connected and powered on kills the i915 probe (drm_WARN_ON in intel_modeset_setup_hw_state), so /dev/dri never appears, snd_hda_intel deferred-probes forever holding the PCI device lock, and incusd blocks in sriov_numvfs_show — no container starts at all, including LAN DNS. Identical on 6.12.107 and 6.12.105. - the kiosk input gid mismatch is real but was NOT the cause of the 2026-08-30 outage (flat K400 batteries were); seatd opens input devices as root, so kiosk group membership is not on that path. Records the one-line raw capture that settles hardware-vs-software immediately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
66c7bca28f
commit
203728674f
+145
-1
@@ -116,6 +116,15 @@ EOF'
|
||||
# --- Kiosk user + seat management ----------------------------------------------
|
||||
incus exec "$CNAME" -- bash -c 'id kiosk >/dev/null 2>&1 || useradd -m -G video,render,input,audio kiosk'
|
||||
|
||||
# ⚠️ Group names are NOT enough. The host (Debian) and the container
|
||||
# (Ubuntu) allocate dynamic system gids independently, so the container's
|
||||
# `input` group does not necessarily have the same gid as the group that
|
||||
# owns /dev/input/* on the host. Bind the kiosk user to the *numeric*
|
||||
# host gid, whatever it is called inside:
|
||||
HOST_INPUT_GID="$(stat -c %g /dev/input/event0)"
|
||||
incus exec "$CNAME" -- usermod -aG "$HOST_INPUT_GID" kiosk
|
||||
incus exec "$CNAME" -- id kiosk # must list $HOST_INPUT_GID
|
||||
|
||||
# In a container seatd must NOT bind the seat to a VT (there is no usable VT;
|
||||
# it would try to open the host's active tty and hang the compositor forever).
|
||||
incus exec "$CNAME" -- mkdir -p /etc/systemd/system/seatd.service.d
|
||||
@@ -245,10 +254,17 @@ it works regardless of which USB port the dongle lands on.
|
||||
`/etc/udev/rules.d/99-jellyfin-kiosk-recover.rules`:
|
||||
|
||||
```
|
||||
# Logitech Unifying receiver (re)plugged -> recover the jellyfin-client kiosk
|
||||
# A Logitech receiver was (re)plugged -> recover the jellyfin-client kiosk.
|
||||
# c52b = Unifying receiver (K400); c539 = Lightspeed receiver (G603).
|
||||
ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="046d", ATTR{idProduct}=="c52b", RUN+="/usr/bin/systemctl --no-block start jellyfin-kiosk-recover.service"
|
||||
ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="046d", ATTR{idProduct}=="c539", RUN+="/usr/bin/systemctl --no-block start jellyfin-kiosk-recover.service"
|
||||
```
|
||||
|
||||
⚠️ The match is per product ID, so **a receiver not listed here will not
|
||||
auto-recover** — the kiosk must be restarted by hand after plugging it in
|
||||
(`incus exec jellyfin-client -- systemctl restart jellyfin-kiosk`). Add
|
||||
the new id here when introducing different input hardware.
|
||||
|
||||
`/etc/systemd/system/jellyfin-kiosk-recover.service` — oneshot, so a burst of
|
||||
udev events during one plug merges into a single restart (natural debounce):
|
||||
|
||||
@@ -285,6 +301,134 @@ udevadm control --reload-rules && systemctl daemon-reload
|
||||
journalctl -t jellyfin-kiosk-recover -f
|
||||
```
|
||||
|
||||
## ⚠️ Never boot nuc with the display active (2026-08-30)
|
||||
|
||||
**Symptom:** after a reboot, *no* Incus container starts. `incus list`
|
||||
answers but `incus start <anything>` hangs forever, `systemctl status
|
||||
incus` sits in `activating (start-post)`, and LAN DNS is down because
|
||||
blocky never came up. Nothing in the incus logs explains it.
|
||||
|
||||
**Cause — nothing to do with incus.** If the TV/projector is connected
|
||||
**and powered on** when nuc boots, firmware hands i915 an already-lit
|
||||
pipe. The driver's state readback then trips a series of warnings and
|
||||
the probe never completes:
|
||||
|
||||
```
|
||||
drm_WARN_ON(!pll_active) intel_ddi.c:4019 intel_ddi_get_clock
|
||||
drm_WARN_ON(p0 == 0 || p1 == 0 || p2 == 0) intel_dpll_mgr.c:2878
|
||||
drm_WARN_ON(pixel_rate == 0) skl_watermark.c:1729
|
||||
```
|
||||
(all inside `intel_modeset_setup_hw_state` ← `intel_display_driver_probe_nogem`)
|
||||
|
||||
The cascade:
|
||||
|
||||
1. i915 probe dies → **`/dev/dri` never appears** (no GPU at all)
|
||||
2. `snd_hda_intel` waits forever for i915's audio component →
|
||||
permanent **deferred probe** holding the PCI device lock on `0000:00:1f.3`
|
||||
3. incusd reads that device's `sriov_numvfs` while enumerating
|
||||
resources → blocks in **D state** → the daemon never signals ready,
|
||||
so nothing autostarts and every `incus start` hangs
|
||||
|
||||
**Reproduced identically on 6.12.107 and 6.12.105** — it is the display
|
||||
path, not a kernel regression. Do not waste time pinning kernels.
|
||||
|
||||
**Diagnosis, in order:**
|
||||
|
||||
```sh
|
||||
ls /dev/dri/ # empty = i915 probe failed
|
||||
cat /sys/kernel/debug/devices_deferred # snd_hda_intel entry = the deadlock
|
||||
ps -eLo pid,tid,stat,wchan:26,comm | awk '$3 ~ /D/' # incusd in sriov_numvfs_show
|
||||
dmesg -T | grep -E 'drm_WARN_ON|deferred probe pending'
|
||||
```
|
||||
|
||||
**Fix:** disconnect HDMI (or power the display fully off — not standby),
|
||||
reboot, then **hotplug the cable back in**. Connecting after boot goes
|
||||
through normal connector detection instead of firmware state readback
|
||||
and works fine.
|
||||
|
||||
In-place recovery is *not* possible: `modprobe -r i915` fails (module in
|
||||
use by the wedged probe) and `modprobe i915` times out. A reboot is the
|
||||
only way out.
|
||||
|
||||
**After hotplugging, restart the kiosk** — `cage` started with zero
|
||||
outputs and will not pick the display up on its own:
|
||||
|
||||
```sh
|
||||
incus exec jellyfin-client -- systemctl restart jellyfin-kiosk
|
||||
cat /sys/class/drm/card0-HDMI-A-2/status # expect: connected
|
||||
```
|
||||
|
||||
Note the HDA controller this wedges is only used for **HDMI audio**,
|
||||
which this setup does not use — audio goes to the Pioneer USB DAC via
|
||||
`/etc/asound.conf`. It is pure collateral damage, but it takes the whole
|
||||
host down with it.
|
||||
|
||||
## Input gid mismatch — latent, fix it anyway (2026-08-30)
|
||||
|
||||
> ⚠️ **This was not the cause of the 2026-08-30 outage.** That turned out
|
||||
> to be a flat/switched-off K400 — a G603 on the same port and the same
|
||||
> `event0` worked immediately. The mismatch below is real and worth
|
||||
> correcting, but with `LIBSEAT_BACKEND=seatd` it is **seatd (running as
|
||||
> root) that opens input devices** and passes the fd to cage, so the
|
||||
> kiosk user's group membership is not on the critical path for input.
|
||||
> Fix it for the direct-open fallback path, not as a debugging lead.
|
||||
>
|
||||
> **Before suspecting software, prove the hardware emits anything:**
|
||||
> ```sh
|
||||
> timeout 60 cat /dev/input/eventN | wc -c # press keys; 0 bytes = nothing reached the kernel
|
||||
> ```
|
||||
> That one check would have saved an hour.
|
||||
>
|
||||
> **Dead K400 batteries are invisible from the host.** This K400 exposes
|
||||
> no `hidpp_battery_*` node under `/sys/class/power_supply/`, so charge
|
||||
> cannot be read. Worse, the receiver still lists the keyboard as a paired
|
||||
> peer (`0003:046D:4024.*` under the `C52B` receiver) whether or not it is
|
||||
> awake, and `/dev/input/event0` plus a `Logitech K400` entry in
|
||||
> `/proc/bus/input/devices` are present either way — so every software
|
||||
> check looks perfectly healthy. **Zero bytes from the raw capture is the
|
||||
> only signal.** Swapping in a different receiver on the same port is the
|
||||
> quickest A/B confirmation.
|
||||
|
||||
**Symptom (if it ever does bite):** kiosk renders but input does nothing,
|
||||
with no errors — `WLR_LIBINPUT_NO_DEVICES=1` keeps cage alive rather than
|
||||
failing loudly.
|
||||
|
||||
**Cause:** `/dev/input/*` is `crw-rw---- root:<host input gid>`. The
|
||||
install script adds `kiosk` to the group *named* `input` inside the
|
||||
container, but Debian (host) and Ubuntu (container) allocate dynamic
|
||||
system gids independently:
|
||||
|
||||
```
|
||||
host /dev/input/event0 gid 996 -> "input"
|
||||
container "input" group gid 995 <- kiosk was here
|
||||
container gid 996 -> "systemd-timesync"
|
||||
```
|
||||
|
||||
So the kiosk user was in the wrong group and could not open any input
|
||||
device. The udev side was fine — check it first to rule it out:
|
||||
`cat /run/udev/data/c13:64` should show `E:ID_INPUT=1` etc.
|
||||
|
||||
**Diagnose:**
|
||||
|
||||
```sh
|
||||
stat -c '%n %a %u:%g' /dev/input/event0 # host gid
|
||||
incus exec jellyfin-client -- id kiosk # does it include that gid?
|
||||
# open() test — do NOT use `head`/`cat`, reading an event device blocks
|
||||
# with no pending events and looks like a permission failure:
|
||||
incus exec jellyfin-client -- su -s /bin/bash kiosk -c 'exec 3< /dev/input/event0 && echo OPEN_OK'
|
||||
```
|
||||
|
||||
**Fix** (persists in the container's `/etc/group`):
|
||||
|
||||
```sh
|
||||
HOST_INPUT_GID="$(stat -c %g /dev/input/event0)"
|
||||
incus exec jellyfin-client -- usermod -aG "$HOST_INPUT_GID" kiosk
|
||||
incus exec jellyfin-client -- systemctl restart jellyfin-kiosk
|
||||
```
|
||||
|
||||
Re-check this after any host reinstall — the host's `input` gid is
|
||||
dynamically allocated and can come back different.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
```sh
|
||||
|
||||
Reference in New Issue
Block a user