#!/bin/sh # Put the NVIDIA device nodes in the video group, mode 0660. # # Called from udev when the driver binds, and by umbra-nvidia-devices.service at # boot. Idempotent, and safe to run when the nodes are not there yet. # # WHY THIS ISN'T JUST A CHMOD LOOP # # The nvidia driver creates /dev/nvidia* in the kernel. It does not register them # in sysfs and it does not let udev process them, so a rule matching the device # nodes never runs - `udevadm info --name=/dev/nvidiactl` answers "Unknown # device", and the driver's own 71-nvidia.rules sets no MODE and no GROUP. That is # why they arrive as 0666. # # Which leaves a scheduling problem: nothing udev manages announces when these # nodes appear, so a boot-time oneshot ordered after `systemd-udev-settle` runs # *before* the driver has finished creating them. That is exactly what happened on # 2026-10-01: the unit ran at 05:38:24, found nothing, exited 0, and the nodes # came up 0666 a moment later. The fix has to be eventual rather than ordered, # which is what the wait below is for. # # --strict exists because the first version of this script ended in an # unconditional `exit 0`, so "I fixed the permissions" and "there were no # permissions to fix" were indistinguishable from the outside. Under systemd # that is a unit reporting success while having done nothing. With --strict, a run # that found no nodes fails loudly instead. set -e # Overridable so the waiting and the strictness can be tested without root and # without a GPU. The first attempt at testing --strict pointed CANARY at a # non-existent path and it still exited 0, because the fix loop globbed /dev # directly and the override only affected the wait - so the test proved nothing. DEVDIR="${UMBRA_NVIDIA_DEVDIR:-/dev}" WAIT=0 STRICT=0 for argument in "$@"; do case "$argument" in --wait=*) WAIT="${argument#--wait=}" ;; --wait) WAIT=30 ;; --strict) STRICT=1 ;; esac done # /dev/nvidiactl is the canary: it is created by the core nvidia module, before # the uvm and modeset ones, so waiting for it means the rest are close behind. CANARY="$DEVDIR/nvidiactl" waited=0 while [ ! -e "$CANARY" ] && [ "$waited" -lt "$WAIT" ]; do sleep 1 waited=$((waited + 1)) done fixed=0 for node in "$DEVDIR"/nvidiactl "$DEVDIR"/nvidia[0-9]* "$DEVDIR"/nvidia-modeset \ "$DEVDIR"/nvidia-uvm "$DEVDIR"/nvidia-uvm-tools; do [ -e "$node" ] || continue chgrp video "$node" 2>/dev/null || true chmod 0660 "$node" 2>/dev/null || true fixed=$((fixed + 1)) done # The capability nodes carry a privileged bit and nothing here needs to set it. for node in "$DEVDIR"/nvidia-caps/nvidia-cap*; do [ -e "$node" ] || continue chgrp video "$node" 2>/dev/null || true chmod 0440 "$node" 2>/dev/null || true done # Verify rather than assume. `chmod` on a device node can fail - read-only /dev, # a node held open - and a script that exits 0 anyway reports a boundary that is # not there. wrong="" for node in "$DEVDIR"/nvidiactl "$DEVDIR"/nvidia[0-9]* "$DEVDIR"/nvidia-modeset \ "$DEVDIR"/nvidia-uvm "$DEVDIR"/nvidia-uvm-tools; do [ -e "$node" ] || continue mode=$(stat -c '%a' "$node" 2>/dev/null || echo "?") group=$(stat -c '%G' "$node" 2>/dev/null || echo "?") if [ "$mode" != "660" ] || [ "$group" != "video" ]; then wrong="$wrong $node($mode,$group)" fi done if [ -n "$wrong" ]; then echo "nvidia-restrict-permissions: still not restricted:$wrong" >&2 exit 1 fi if [ "$fixed" -eq 0 ]; then if [ "$STRICT" -eq 1 ]; then echo "nvidia-restrict-permissions: waited ${WAIT}s and found no /dev/nvidia* nodes" >&2 exit 1 fi echo "nvidia-restrict-permissions: no nodes yet; a later run will catch them" >&2 exit 0 fi echo "nvidia-restrict-permissions: restricted $fixed node(s)${wrong:+ (waited ${waited}s)}" exit 0