#!/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