#!/bin/sh
# Apply Linux Lite's NVIDIA kernel-compatibility patches to the installed
# open-kernel NVIDIA DKMS source, so the proprietary module builds on Linux
# Lite's mainline 7.x+ kernels.
#
# Why this is needed: LL ships a mainline kernel newer than the kernel Ubuntu's
# packaged NVIDIA driver was written for, so each new mainline bump tends to
# break NVIDIA's out-of-tree source against a fresh API:
#   - 7.2 removed strncpy() and renamed drm_atomic_state -> drm_atomic_commit
#     (+ uvm/nvswitch changes)              -> 0001-7.2-kernel-support.patch
#   - 7.3 changed dmem_cgroup_register_region() to take a struct *dmem_cgroup_init
#     instead of a u64 (mainline e11640b9cea4e) -> 0002-7.3-dmem-cgroup.patch
# Each patch is path-adapted for Ubuntu's flattened nvidia-*-open DKMS tree
# (applies at -p1) and is self-gating: the 7.3 one is #if-guarded to 7.3+, and
# all are applied only where they still fit, so a newer NVIDIA source that no
# longer needs one is simply skipped.
#
# NVIDIA's own package builds the module in its postinst, BEFORE this can run, so
# this runs reactively (dpkg trigger on /usr/src, and on install): it patches the
# source and rebuilds the module for every installed 7.x+ kernel. It is idempotent
# (a patch that already applied is detected by a clean reverse-apply and skipped)
# and never touches dpkg (trigger-reentrancy safe).
set -e

PATCHDIR=/usr/share/linuxlite/nvidia-kernel-patches
LOG=/var/log/linuxlite-nvidia-kernel-patch.log
[ -d "$PATCHDIR" ] || exit 0

# A kernel counts as "needs the patch" when its major version is >= 7 (LL's
# mainline kernels). Stock Ubuntu kernels (6.x) build NVIDIA fine and are skipped.
kver_needs_patch() {  # $1 = kernel version string
    maj=${1%%.*}
    case "$maj" in ''|*[!0-9]*) return 1;; esac
    [ "$maj" -ge 7 ] 2>/dev/null
}

# Is any 7.x+ kernel with headers installed? If not, nothing to do.
have_target=0
for kdir in /lib/modules/*/; do
    [ -e "$kdir/build" ] || continue
    kver_needs_patch "$(basename "$kdir")" && { have_target=1; break; }
done
[ "$have_target" = 1 ] || exit 0

patched_any=0
for src in /usr/src/nvidia-[0-9]*; do
    [ -d "$src" ] || continue
    [ -f "$src/nvidia/os-interface.c" ] || continue
    for PATCH in "$PATCHDIR"/*.patch; do
        [ -f "$PATCH" ] || continue
        # Already applied? A clean reverse-apply means yes -> skip (idempotent).
        patch -p1 -d "$src" -R --dry-run --fuzz=3 < "$PATCH" >/dev/null 2>&1 && continue
        # Applies forward cleanly? -> apply it. (A source that no longer needs
        # this patch fails the dry-run and is left untouched.)
        if patch -p1 -d "$src" --dry-run --fuzz=3 < "$PATCH" >/dev/null 2>&1; then
            if patch -p1 -d "$src" --fuzz=3 < "$PATCH" >/dev/null 2>&1; then
                echo "$(date '+%F %T') patched $src with $(basename "$PATCH")" >> "$LOG"
                patched_any=1
            fi
        fi
    done
done
[ "$patched_any" = 1 ] || exit 0

# Rebuild + install the NVIDIA DKMS module for every installed 7.x+ kernel, since
# NVIDIA's own postinst build ran against the UNpatched source and failed.
command -v dkms >/dev/null 2>&1 || exit 0
ver=$(ls -d /usr/src/nvidia-[0-9]*/ 2>/dev/null | sed -n 's|.*/nvidia-\([0-9][^/]*\)/$|\1|p' | head -1)
[ -n "$ver" ] || exit 0
for kdir in /lib/modules/*/; do
    [ -e "$kdir/build" ] || continue
    k=$(basename "$kdir")
    kver_needs_patch "$k" || continue
    dkms build   -m nvidia -v "$ver" -k "$k"          >>"$LOG" 2>&1 || true
    dkms install --force -m nvidia -v "$ver" -k "$k"  >>"$LOG" 2>&1 || true
done
exit 0
