diff --git a/.github/workflows/auto-update-matrix.yml b/.github/workflows/auto-update-matrix.yml new file mode 100644 index 0000000..7a38905 --- /dev/null +++ b/.github/workflows/auto-update-matrix.yml @@ -0,0 +1,253 @@ +name: Auto-detect and update build matrix + +on: + schedule: + - cron: '0 4 * * *' # Daily at 04:00 UTC + workflow_dispatch: + +permissions: + contents: write + actions: write + +concurrency: + group: auto-update-matrix-${{ github.ref }} + cancel-in-progress: true + +jobs: + detect-and-update: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Install dependencies + run: sudo apt-get -y install jq tidy wget + + - name: Detect NVIDIA driver versions + id: nvidia + run: | + RAW_DATA="$(wget -qO- https://www.nvidia.com/en-us/drivers/unix/ | tidy -quiet -wrap 4096 2>/dev/null | grep -A8 "Linux x86_64/AMD64/EM64T")" + PRB="$(echo "${RAW_DATA}" | grep -i "Latest Production Branch" | grep -oE '\b[0-9]+\.[0-9]+(\.[0-9]+)?\b')" + NFB="$(echo "${RAW_DATA}" | grep -i "Latest New Feature Branch" | grep -oE '\b[0-9]+\.[0-9]+(\.[0-9]+)?\b')" + BETA="$(echo "${RAW_DATA}" | grep -i "Latest Beta" | grep -oE '\b[0-9]+\.[0-9]+(\.[0-9]+)?\b')" + LEGACY="$(echo "${RAW_DATA}" | grep -i "Latest Legacy" | grep "(4" | head -1 | grep -oE '\b[0-9]+\.[0-9]+(\.[0-9]+)?\b')" + + # Fallback to NVIDIA developer forum if fetch or parse failed + if [ -z "${RAW_DATA}" ] || { [ -z "${PRB}" ] && [ -z "${NFB}" ] && [ -z "${BETA}" ]; }; then + RAW_DATA="$(wget -qO- https://forums.developer.nvidia.com/t/current-graphics-driver-releases/28500 | tidy -quiet -wrap 4096 2>/dev/null || true)" + PRB="$(echo "${RAW_DATA}" | grep -i "^Current production branch" | grep -oE '\b[0-9]+\.[0-9]+(\.[0-9]+)?\b')" + NFB="$(echo "${RAW_DATA}" | grep -i "^Current new feature branch" | grep -oE '\b[0-9]+\.[0-9]+(\.[0-9]+)?\b')" + BETA="$(echo "${RAW_DATA}" | grep -i "^Current beta" | grep -oE '\b[0-9]+\.[0-9]+(\.[0-9]+)?\b')" + LEGACY="$(echo "${RAW_DATA}" | grep -i -A1 '>Legacy releases' | tail -1 | grep -oE '\b[0-9]+\.[0-9]+(\.[0-9]+)?\b')" + fi + + if [ -z "${RAW_DATA}" ] || { [ -z "${PRB}" ] && [ -z "${NFB}" ]; }; then + echo "::error::Failed to fetch NVIDIA driver data from both sources" + exit 1 + fi + + echo "prb=${PRB}" >> "$GITHUB_OUTPUT" + echo "nfb=${NFB}" >> "$GITHUB_OUTPUT" + echo "beta=${BETA}" >> "$GITHUB_OUTPUT" + echo "legacy=${LEGACY}" >> "$GITHUB_OUTPUT" + echo "Detected: PRB=${PRB} NFB=${NFB} BETA=${BETA} LEGACY=${LEGACY}" + + - name: Detect latest Unraid kernel versions + id: kernels + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Get kernel tags from upstream releases (format: 6.x.y-Unraid) + # Keep the latest kernel per major.minor branch (e.g. 6.12.x, 6.17.x, 6.18.x) + # so all active Unraid versions are covered + ALL_TAGS=$(gh api repos/unraid/unraid-nvidia-driver/releases \ + --paginate --jq '.[].tag_name' 2>/dev/null \ + | grep -E '^[0-9]+\.[0-9]+\.[0-9]+-Unraid$' \ + | sort -t. -k1,1nr -k2,2nr -k3,3nr) + + if [ -z "$ALL_TAGS" ]; then + echo "::warning::No kernel tags found, keeping current kernel_versions" + echo "kernels=" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # Pick the latest patch version per major.minor branch, keep top 3 + KERNEL_TAGS=$(echo "$ALL_TAGS" | awk -F'[.-]' '!seen[$1"."$2]++' | head -3) + KERNEL_JSON=$(echo "$KERNEL_TAGS" | jq -R -c -s 'split("\n") | map(select(length > 0))') + echo "kernels=${KERNEL_JSON}" >> "$GITHUB_OUTPUT" + echo "Detected kernels: ${KERNEL_JSON}" + + - name: Detect container runtime versions + id: runtimes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + CONTAINER_TOOLKIT=$(gh api repos/unraid/nvidia-container-toolkit/releases/latest --jq '.tag_name' 2>/dev/null || echo "") + LIBNVIDIA=$(gh api repos/unraid/libnvidia-container/releases/latest --jq '.tag_name' 2>/dev/null || echo "") + + # Ensure libnvidia-container matches toolkit version to prevent mismatches. + # Both repos release in sync; if versions differ, use the toolkit version + # and verify the matching libnvidia-container release exists. + if [ -n "${CONTAINER_TOOLKIT}" ] && [ -n "${LIBNVIDIA}" ] && [ "${CONTAINER_TOOLKIT}" != "${LIBNVIDIA}" ]; then + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/unraid/libnvidia-container/releases/tags/${CONTAINER_TOOLKIT}") + if [ "${HTTP_CODE}" == "200" ]; then + LIBNVIDIA="${CONTAINER_TOOLKIT}" + fi + fi + + echo "toolkit=${CONTAINER_TOOLKIT}" >> "$GITHUB_OUTPUT" + echo "libnvidia=${LIBNVIDIA}" >> "$GITHUB_OUTPUT" + echo "Detected: toolkit=${CONTAINER_TOOLKIT} libnvidia=${LIBNVIDIA}" + + - name: Detect latest GCC tag + id: gcc + run: | + TOKEN=$(curl -s "https://ghcr.io/token?scope=repository:ich777/unraid_kernel:pull" | jq -r '.token') + GCC_TAG=$(curl -s -H "Authorization: Bearer $TOKEN" "https://ghcr.io/v2/ich777/unraid_kernel/tags/list" \ + | jq -r '.tags[]' | grep '^gcc_' | sort -V | tail -1) + + if [ -z "${GCC_TAG}" ]; then + echo "::warning::Could not detect GCC tag, keeping current" + GCC_TAG=$(jq -r '.gcc_tag' build-matrix.json) + fi + + echo "gcc_tag=${GCC_TAG}" >> "$GITHUB_OUTPUT" + echo "Detected GCC tag: ${GCC_TAG}" + + - name: Update build-matrix.json + env: + NEW_PRB: ${{ steps.nvidia.outputs.prb }} + NEW_NFB: ${{ steps.nvidia.outputs.nfb }} + NEW_BETA: ${{ steps.nvidia.outputs.beta }} + NEW_KERNELS: ${{ steps.kernels.outputs.kernels }} + NEW_GCC: ${{ steps.gcc.outputs.gcc_tag }} + run: | + CUR_PRB=$(jq -r '.branches.production.driver_version' build-matrix.json) + CUR_NFB=$(jq -r '.branches.newfeature.driver_version' build-matrix.json) + CUR_BETA=$(jq -r '.branches.beta.driver_version' build-matrix.json) + + jq --arg new_prb "${NEW_PRB}" \ + --arg new_nfb "${NEW_NFB}" \ + --arg new_beta "${NEW_BETA}" \ + --arg new_gcc "${NEW_GCC}" \ + --arg cur_prb "${CUR_PRB}" \ + --arg cur_nfb "${CUR_NFB}" \ + --arg cur_beta "${CUR_BETA}" \ + --argjson new_kernels "${NEW_KERNELS:-null}" \ + ' + # Update gcc_tag + (if ($new_gcc != "") then .gcc_tag = $new_gcc else . end) + + # Update kernel versions if detected + | (if $new_kernels != null and ($new_kernels | length > 0) then + .kernel_versions = $new_kernels + else . end) + + # Rotate production: old version → extra_builds, set new version + | (if ($new_prb != "") and ($cur_prb != "") and ($cur_prb != "null") and ($cur_prb != $new_prb) then + (if (.extra_builds | map(.driver_version) | index($cur_prb) | not) then + .extra_builds += [{"driver_version": $cur_prb, "module_types": ["proprietary", "opensource"]}] + else . end) + | .branches.production.driver_version = $new_prb + else . end) + + # Rotate newfeature: old version → extra_builds, set new version + | (if ($new_nfb != "") and ($cur_nfb != "") and ($cur_nfb != "null") and ($cur_nfb != $new_nfb) then + (if (.extra_builds | map(.driver_version) | index($cur_nfb) | not) then + .extra_builds += [{"driver_version": $cur_nfb, "module_types": ["proprietary", "opensource"]}] + else . end) + | .branches.newfeature.driver_version = $new_nfb + else . end) + + # Rotate beta: old version → extra_builds, set new version + | (if ($new_beta != "") and ($cur_beta != "") and ($cur_beta != "null") and ($cur_beta != $new_beta) then + (if (.extra_builds | map(.driver_version) | index($cur_beta) | not) then + .extra_builds += [{"driver_version": $cur_beta, "module_types": ["proprietary", "opensource"]}] + else . end) + | .branches.beta.driver_version = $new_beta + else . end) + + # Remove from extra_builds any version that is now a branch version + | . as $root | .extra_builds = [ + .extra_builds[] | + select( + .driver_version != ($root.branches.production.driver_version // "") and + .driver_version != ($root.branches.newfeature.driver_version // "") and + .driver_version != ($root.branches.beta.driver_version // "") + ) + ] + + # Deduplicate extra_builds + | .extra_builds = (.extra_builds | unique_by(.driver_version)) + ' build-matrix.json > build-matrix.json.tmp + mv build-matrix.json.tmp build-matrix.json + + echo "Updated build-matrix.json:" + cat build-matrix.json + + - name: Update versions.json + env: + NEW_PRB: ${{ steps.nvidia.outputs.prb }} + NEW_NFB: ${{ steps.nvidia.outputs.nfb }} + NEW_BETA: ${{ steps.nvidia.outputs.beta }} + NEW_LEGACY: ${{ steps.nvidia.outputs.legacy }} + TOOLKIT: ${{ steps.runtimes.outputs.toolkit }} + LIBNVIDIA: ${{ steps.runtimes.outputs.libnvidia }} + run: | + CUR_PRB=$(jq -r '.branches.production.current' versions.json) + CUR_NFB=$(jq -r '.branches.newfeature.current' versions.json) + + jq --arg prb "${NEW_PRB}" \ + --arg nfb "${NEW_NFB}" \ + --arg beta "${NEW_BETA}" \ + --arg legacy "${NEW_LEGACY}" \ + --arg last_prb "${CUR_PRB}" \ + --arg last_nfb "${CUR_NFB}" \ + --arg toolkit "${TOOLKIT}" \ + --arg libnvidia "${LIBNVIDIA}" \ + ' + (if ($prb != "") then + .branches.production.current = $prb + | (if ($prb != $last_prb) then .branches.production.last_prb = $last_prb else . end) + else . end) + | (if ($nfb != "") then + .branches.newfeature.current = $nfb + | (if ($nfb != $last_nfb) then .branches.newfeature.last_nfb = $last_nfb else . end) + else . end) + | (if ($beta != "") then .branches.beta.current = $beta else . end) + | (if ($legacy != "") then .branches.legacy.current = $legacy else . end) + | (if ($toolkit | test("^[0-9]+(\\\\.[0-9]+)+$")) then .runtimes.containertoolkit.current = $toolkit else . end) + | (if ($libnvidia | test("^[0-9]+(\\\\.[0-9]+)+$")) then .runtimes.libnvidia.current = $libnvidia else . end) + ' versions.json > versions.json.tmp + mv versions.json.tmp versions.json + + - name: Commit and push if changed + id: commit + run: | + if git diff --quiet; then + echo "No changes detected" + echo "changed=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + git config user.name 'github-actions[bot]' + git config user.email 'github-actions[bot]@users.noreply.github.com' + git add build-matrix.json versions.json + git diff --cached --quiet && { echo "No staged changes"; echo "changed=false" >> "$GITHUB_OUTPUT"; exit 0; } + git commit -m "auto-update: detect new driver/kernel versions" + git push + echo "changed=true" >> "$GITHUB_OUTPUT" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Trigger build workflow + if: steps.commit.outputs.changed == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "Changes committed, triggering build workflow..." + gh workflow run "Build NVIDIA Driver Packages" --repo ${{ github.repository }} --ref ${{ github.ref_name }} diff --git a/.github/workflows/build-nvidia.yml b/.github/workflows/build-nvidia.yml new file mode 100644 index 0000000..6064465 --- /dev/null +++ b/.github/workflows/build-nvidia.yml @@ -0,0 +1,234 @@ +name: Build NVIDIA Driver Packages + +on: + workflow_dispatch: + +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + gcc_tag: ${{ steps.set-matrix.outputs.gcc_tag }} + steps: + - uses: actions/checkout@v4 + - id: set-matrix + run: | + MATRIX=$(jq -c ' + # Builds from branch definitions + [ + .kernel_versions[] as $k | + .branches | to_entries[] | + .value.module_types[] as $m | + { + driver_version: .value.driver_version, + module_type: $m, + kernel_version: $k, + branch: .key + } + ] + + + # Extra builds for pin-to-specific-version (outside branch structure) + [ + .kernel_versions[] as $k | + (.extra_builds // [])[] | + .module_types[] as $m | + { + driver_version: .driver_version, + module_type: $m, + kernel_version: $k, + branch: "extra" + } + ] + ' build-matrix.json) + echo "matrix=${MATRIX}" >> "$GITHUB_OUTPUT" + echo "gcc_tag=$(jq -r '.gcc_tag' build-matrix.json)" >> "$GITHUB_OUTPUT" + + build: + needs: prepare + runs-on: ubuntu-latest + timeout-minutes: 120 + strategy: + fail-fast: false + matrix: + include: ${{ fromJson(needs.prepare.outputs.matrix) }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Check if package already exists + id: check-existing + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if [ "${{ matrix.module_type }}" == "opensource" ]; then + PKG_PREFIX="nvos" + else + PKG_PREFIX="nvidia" + fi + PKG_NAME="${PKG_PREFIX}-${{ matrix.driver_version }}-${{ matrix.kernel_version }}-1.txz" + TAG="${{ matrix.kernel_version }}" + + if gh release view "${TAG}" --repo ${{ github.repository }} --json assets --jq ".assets[].name" 2>/dev/null | grep -qx "${PKG_NAME}"; then + echo "Package ${PKG_NAME} already exists in release ${TAG}, skipping build" + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "Package ${PKG_NAME} not found, proceeding with build" + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - name: Free up disk space + if: steps.check-existing.outputs.skip != 'true' + run: | + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc + df -h / + + - name: Create output directory + if: steps.check-existing.outputs.skip != 'true' + run: mkdir -p output + + - name: Build package in container + id: build + if: steps.check-existing.outputs.skip != 'true' + run: | + docker pull ghcr.io/ich777/unraid_kernel:${{ needs.prepare.outputs.gcc_tag }} + EXIT_CODE=0 + docker run --rm \ + --entrypoint bash \ + -v "$GITHUB_WORKSPACE":/workspace \ + -v "$GITHUB_WORKSPACE/output":/output \ + ghcr.io/ich777/unraid_kernel:${{ needs.prepare.outputs.gcc_tag }} \ + -c ' + set -euo pipefail + + DRIVER_VERSION="${{ matrix.driver_version }}" + KERNEL_VERSION="${{ matrix.kernel_version }}" + MODULE_TYPE="${{ matrix.module_type }}" + + if [ "${MODULE_TYPE}" == "opensource" ]; then + PKG_PREFIX="nvos" + BUILD_ARG="opensource" + else + PKG_PREFIX="nvidia" + BUILD_ARG="" + fi + + echo "=== Building ${PKG_PREFIX}-${DRIVER_VERSION} (${MODULE_TYPE}) for kernel ${KERNEL_VERSION} ===" + + export UNAME="${KERNEL_VERSION}" + export CPU_COUNT="$(nproc)" + export DATA_DIR="/tmp/nvidia-build" + mkdir -p "${DATA_DIR}" + + # --------------------------------------------------------------- + # Step 1: Prepare kernel sources + # --------------------------------------------------------------- + echo "=== Downloading kernel sources ===" + KERNEL_TAR="linux-${KERNEL_VERSION}.tar.xz" + KERNEL_URL="https://github.com/ich777/unraid_kernel/releases/download/${KERNEL_VERSION}/${KERNEL_TAR}" + wget -q -O "/tmp/${KERNEL_TAR}" "${KERNEL_URL}" + mkdir -p "/usr/src/linux-${KERNEL_VERSION}" + tar xf "/tmp/${KERNEL_TAR}" -C "/usr/src/linux-${KERNEL_VERSION}" + cd "/usr/src/linux-${KERNEL_VERSION}" + + echo "=== Preparing kernel headers ===" + make oldconfig &1 | tail -5 + make modules_prepare -j${CPU_COUNT} 2>&1 | tail -5 + + mkdir -p "/lib/modules/${UNAME}" + ln -sf "/usr/src/linux-${KERNEL_VERSION}" "/lib/modules/${UNAME}/build" + ln -sf "/usr/src/linux-${KERNEL_VERSION}" "/lib/modules/${UNAME}/source" + cd / + + # --------------------------------------------------------------- + # Step 2: Create makepkg shim + # --------------------------------------------------------------- + mkdir -p "${DATA_DIR}/bzroot-extracted-${UNAME}/sbin" + printf "#!/bin/bash\n# Grab last argument as output file\nfor PKG; do true; done\ntar cJf \"\${PKG}\" . 2>/dev/null; exit 0\n" > "${DATA_DIR}/bzroot-extracted-${UNAME}/sbin/makepkg" + chmod +x "${DATA_DIR}/bzroot-extracted-${UNAME}/sbin/makepkg" + + # --------------------------------------------------------------- + # Step 3: Build using compile.sh + # --------------------------------------------------------------- + echo "=== Loading compile.sh and building ===" + cd /workspace + + real_wget="$(which wget)" + mkdir -p /usr/local/bin + printf "#!/bin/bash\nexec ${real_wget} \"\${@/--show-progress/}\"" > /usr/local/bin/wget + chmod +x /usr/local/bin/wget + + set +u + source source/compile.sh "${DRIVER_VERSION}" ${BUILD_ARG} + nvidia_driver "${DRIVER_VERSION}" ${BUILD_ARG} + set -u + + # --------------------------------------------------------------- + # Step 4: Copy output artifacts + # --------------------------------------------------------------- + echo "=== Copying build artifacts ===" + find /tmp -name "${PKG_PREFIX}-*.txz" -exec cp {} /output/ \; + find /tmp -name "${PKG_PREFIX}-*.txz.md5" -exec cp {} /output/ \; + + echo "=== Build artifacts ===" + ls -la /output/ + ' || EXIT_CODE=$? + + # Check if build produced output artifacts + if [ -z "$(ls output/*.txz 2>/dev/null)" ]; then + if [ "${EXIT_CODE:-0}" -ne 0 ]; then + echo "::warning::Driver ${{ matrix.driver_version }} failed to build for kernel ${{ matrix.kernel_version }} (exit code ${EXIT_CODE}), skipping" + echo "incompatible=true" >> "$GITHUB_OUTPUT" + else + echo "::error::Build succeeded but no .txz package produced" + exit 1 + fi + else + echo "incompatible=false" >> "$GITHUB_OUTPUT" + fi + + - name: Verify build output + if: steps.check-existing.outputs.skip != 'true' && steps.build.outputs.incompatible != 'true' + run: | + ls -la output/ + if [ -z "$(ls output/*.txz 2>/dev/null)" ]; then + echo "ERROR: No .txz package found in output!" + exit 1 + fi + cd output + for f in *.txz; do + if [ -f "${f}.md5" ]; then + EXPECTED="$(cat "${f}.md5")" + ACTUAL="$(md5sum "${f}" | awk '{print $1}')" + echo "${f}: expected=${EXPECTED} actual=${ACTUAL}" + if [ "${EXPECTED}" != "${ACTUAL}" ]; then + echo "CHECKSUM MISMATCH!" + exit 1 + fi + echo "Checksum OK" + fi + done + + - name: Upload artifact + if: steps.check-existing.outputs.skip != 'true' && steps.build.outputs.incompatible != 'true' + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.module_type == 'opensource' && 'nvos' || 'nvidia' }}-${{ matrix.driver_version }}-${{ matrix.kernel_version }} + path: output/* + retention-days: 90 + + - name: Create or update GitHub Release + if: steps.check-existing.outputs.skip != 'true' && steps.build.outputs.incompatible != 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG="${{ matrix.kernel_version }}" + gh release view "${TAG}" --repo ${{ github.repository }} >/dev/null 2>&1 || \ + gh release create "${TAG}" --repo ${{ github.repository }} \ + --title "Drivers for kernel ${TAG}" \ + --notes "NVIDIA driver packages for Unraid kernel ${TAG}" + for f in output/*; do + gh release upload "${TAG}" "${f}" --repo ${{ github.repository }} --clobber + done + echo "=== Release ${TAG} updated ===" + gh release view "${TAG}" --repo ${{ github.repository }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f11b75 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ diff --git a/build-matrix.json b/build-matrix.json new file mode 100644 index 0000000..993fdb3 --- /dev/null +++ b/build-matrix.json @@ -0,0 +1,61 @@ +{ + "gcc_tag": "gcc_14.2.0", + "kernel_versions": [ + "6.18.20-Unraid", + "6.18.19-Unraid", + "6.12.54-Unraid" + ], + "branches": { + "production": { + "driver_version": "595.58.03", + "module_types": [ + "proprietary", + "opensource" + ] + }, + "newfeature": { + "driver_version": "590.48.01", + "module_types": [ + "proprietary", + "opensource" + ] + }, + "beta": { + "driver_version": "595.45.04", + "module_types": [ + "proprietary", + "opensource" + ] + } + }, + "extra_builds": [ + { + "driver_version": "575.64.05", + "module_types": [ + "proprietary", + "opensource" + ] + }, + { + "driver_version": "580.126.18", + "module_types": [ + "proprietary", + "opensource" + ] + }, + { + "driver_version": "580.142", + "module_types": [ + "proprietary", + "opensource" + ] + }, + { + "driver_version": "590.44.01", + "module_types": [ + "proprietary", + "opensource" + ] + } + ] +} diff --git a/source/usr/local/emhttp/plugins/nvidia-driver/include/download.sh b/source/usr/local/emhttp/plugins/nvidia-driver/include/download.sh index 9d46829..970d509 100644 --- a/source/usr/local/emhttp/plugins/nvidia-driver/include/download.sh +++ b/source/usr/local/emhttp/plugins/nvidia-driver/include/download.sh @@ -235,9 +235,22 @@ fi #Begin Check check -#Check for old packages that are not suitable for this Kernel and not suitable for the current Nvidia driver version -rm -rf $(ls -d /boot/config/plugins/nvidia-driver/packages/* 2>/dev/null | grep -v "${KERNEL_V%%-*}") -rm -f $(ls /boot/config/plugins/nvidia-driver/packages/${KERNEL_V%%-*}/* 2>/dev/null | grep -v "$LAT_PACKAGE") +# SEC: Safe cleanup of old kernel directories. +# Original used unquoted command substitution with rm -rf, which could +# delete everything if variables were empty (grep -v "" matches all lines). +# Using a loop with explicit checks prevents accidental mass deletion. +while IFS= read -r dir; do + [[ "$(basename "$dir")" != "${KERNEL_V%%-*}" ]] && rm -rf "$dir" +done < <(find /boot/config/plugins/nvidia-driver/packages -mindepth 1 -maxdepth 1 -type d 2>/dev/null) + +# SEC: Only clean up old packages if we know the current package name. +# Without this guard, an empty $LAT_PACKAGE would cause grep -v "" to +# match nothing, and all files would be deleted from the packages dir. +if [ -n "$LAT_PACKAGE" ]; then + while IFS= read -r file; do + [[ "$(basename "$file")" != "$LAT_PACKAGE" && "$(basename "$file")" != "${LAT_PACKAGE}.md5" ]] && rm -f "$file" + done < <(find "/boot/config/plugins/nvidia-driver/packages/${KERNEL_V%%-*}" -mindepth 1 -maxdepth 1 -type f 2>/dev/null) +fi #Display message to reboot server both in Plugin and WebUI echo diff --git a/source/usr/local/emhttp/plugins/nvidia-driver/include/exec.sh b/source/usr/local/emhttp/plugins/nvidia-driver/include/exec.sh index a20bc17..9986f5a 100644 --- a/source/usr/local/emhttp/plugins/nvidia-driver/include/exec.sh +++ b/source/usr/local/emhttp/plugins/nvidia-driver/include/exec.sh @@ -5,30 +5,30 @@ KERNEL_V="$(uname -r)" PACKAGE="nvidia" CURENTTIME=$(date +%s) CHK_TIMEOUT=300 +FETCH_VERSIONS() { + DRIVERS="$(wget -qO- https://api.github.com/repos/unraid/unraid-nvidia-driver/releases/tags/${KERNEL_V} | jq -r '.assets[].name' | grep -E -v '\.md5$' | sort -V)" + echo -n "$(grep ${PACKAGE} <<< "$DRIVERS" | awk -F "-" '{print $2}' | sort -V | uniq)" > /tmp/nvidia_driver + echo -n "$(grep nvos <<< "$DRIVERS" | awk -F "-" '{print $2}' | sort -V | uniq)" > /tmp/nvos_driver + if [ ! -s /tmp/nvidia_driver ]; then + echo -n "$(modinfo nvidia | grep "version:" | awk '{print $2}' | head -1)" > /tmp/nvidia_driver + fi +} if [ -f /tmp/nvidia_driver ]; then FILETIME=$(stat /tmp/nvidia_driver -c %Y) DIFF=$(expr $CURENTTIME - $FILETIME) if [ $DIFF -gt $CHK_TIMEOUT ]; then - DRIVERS="$(wget -qO- https://api.github.com/repos/unraid/unraid-nvidia-driver/releases/tags/${KERNEL_V} | jq -r '.assets[].name' | grep -E -v '\.md5$' | sort -V)" - echo -n "$(grep ${PACKAGE} <<< "$DRIVERS" | awk -F "-" '{print $2}' | sort -V | tail -10)" > /tmp/nvidia_driver - echo -n "$(grep nvos <<< "$DRIVERS" | awk -F "-" '{print $2}' | sort -V | tail -1)" > /tmp/nvos_driver - echo -n "$(wget -qO- https://api.github.com/repos/unraid/unraid-nvidia-driver/releases/tags/${KERNEL_V} | jq -r '.assets[].name' | grep "${PACKAGE}" | grep -E -v '\.md5$' | awk -F "-" '{print $2}' | sort -V | tail -10)" > /tmp/nvidia_driver - if [ ! -s /tmp/nvidia_driver ]; then - echo -n "$(modinfo nvidia | grep "version:" | awk '{print $2}' | head -1)" > /tmp/nvidia_driver - fi + FETCH_VERSIONS fi else - DRIVERS="$(wget -qO- https://api.github.com/repos/unraid/unraid-nvidia-driver/releases/tags/${KERNEL_V} | jq -r '.assets[].name' | grep -E -v '\.md5$' | sort -V)" - echo -n "$(grep ${PACKAGE} <<< "$DRIVERS" | awk -F "-" '{print $2}' | sort -V | tail -10)" > /tmp/nvidia_driver - echo -n "$(grep nvos <<< "$DRIVERS" | awk -F "-" '{print $2}' | sort -V | tail -10)" > /tmp/nvos_driver - if [ ! -s /tmp/nvidia_driver ]; then - echo -n "$(modinfo nvidia | grep "version:" | awk '{print $2}' | head -1)" > /tmp/nvidia_driver - fi + FETCH_VERSIONS fi -# Check if driver version 580 is in /tmp/nvos_driver -if [ ! $(grep "580" /tmp/nvidia_driver) ]; then - LEGACY_DRIVER="$(echo "$DRIVERS" | awk -F "-" '{print $2}' | grep "580")" - if [ ! -z "${LEGACY_DRIVER}" ]; then +# FIX: Read available versions from cache file instead of $DRIVERS variable. +# $DRIVERS is only populated inside FETCH_VERSIONS() which may not run +# if the cache is still fresh. Reading from the file ensures the 580 +# legacy driver check works regardless of whether the cache was refreshed. +if ! grep -q "580" /tmp/nvidia_driver 2>/dev/null; then + LEGACY_DRIVER="$(grep "580" /tmp/nvos_driver 2>/dev/null | head -1)" + if [ -n "${LEGACY_DRIVER}" ]; then sed -i "1s/^/${LEGACY_DRIVER}\n/" /tmp/nvidia_driver fi fi @@ -47,8 +47,16 @@ fi } function update_version(){ +# SEC: Validate input to prevent command injection via sed. +# exec.sh is called with $@ dispatcher, so any string passed from the web UI +# ends up as ${1} here. Without validation, a crafted version string like +# "1.0; rm -rf /" would execute arbitrary commands through sed. +if [[ ! "${1}" =~ ^[a-zA-Z0-9._]+$ ]]; then + echo "ERROR: Invalid version string" + exit 1 +fi sed -i "/driver_version=/c\driver_version=${1}" "/boot/config/plugins/nvidia-driver/settings.cfg" -if [[ "${1}" != "latest" && "${1}" != "latest_prb" && "${1}" != "latest_nfb" ]]; then +if [[ "${1}" != "latest" && "${1}" != "latest_prb" && "${1}" != "latest_nfb" && "${1}" != "latest_beta" ]]; then sed -i "/update_check=/c\update_check=false" "/boot/config/plugins/nvidia-driver/settings.cfg" echo -n "$(crontab -l | grep -v '/usr/local/emhttp/plugins/nvidia-driver/include/update-check.sh &>/dev/null 2>&1' | crontab -)" fi @@ -68,10 +76,22 @@ function get_nfb(){ echo -n "$(comm -12 <(cat /tmp/nvidia_driver | awk -F '.' '{printf "%d.%03d.%d\n", $1,$2,$3}' | awk -F '.' '{printf "%d.%03d.%02d\n", $1,$2,$3}') <(echo "$(cat /tmp/nvidia_branches | jq -r '.branches.newfeature[]' | sort -V | awk -F '.' '{printf "%d.%03d.%d\n", $1,$2,$3}' | awk -F '.' '{printf "%d.%03d.%02d\n", $1,$2,$3}')") | tail -1 | awk -F '.' '{printf "%d.%02d.%02d\n", $1,$2,$3}' | awk '{sub(/\.0+$/,"")}1')" } +function get_beta(){ +echo -n "$(comm -12 <(cat /tmp/nvidia_driver | awk -F '.' '{printf "%d.%03d.%d\n", $1,$2,$3}' | awk -F '.' '{printf "%d.%03d.%02d\n", $1,$2,$3}') <(echo "$(cat /tmp/nvidia_branches | jq -r '.branches.beta.current' | sort -V | awk -F '.' '{printf "%d.%03d.%d\n", $1,$2,$3}' | awk -F '.' '{printf "%d.%03d.%02d\n", $1,$2,$3}')") | tail -1 | awk -F '.' '{printf "%d.%02d.%02d\n", $1,$2,$3}' | awk '{sub(/\.0+$/,"")}1')" +} + function get_nos(){ echo -n "$(cat /tmp/nvos_driver | sort -V | tail -1)" } +function get_gpu_arch(){ +echo -n "$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader 2>/dev/null | head -1)" +} + +function get_cuda_version(){ +echo -n "$(nvidia-smi 2>/dev/null | grep 'CUDA Version' | grep -oE '[0-9]+\.[0-9]+' | tail -1)" +} + function get_selected_version(){ echo -n "$(cat /boot/config/plugins/nvidia-driver/settings.cfg | grep "driver_version" | cut -d '=' -f2)" } @@ -96,6 +116,12 @@ echo -n "$(cat /boot/config/plugins/nvidia-driver/settings.cfg | grep "update_ch } function change_update_check(){ +# SEC: Whitelist boolean values to prevent command injection. +# Only "true" or "false" are valid — anything else is rejected. +if [[ "${1}" != "true" && "${1}" != "false" ]]; then + echo "ERROR: Invalid value for update_check" + exit 1 +fi sed -i "/update_check=/c\update_check=${1}" "/boot/config/plugins/nvidia-driver/settings.cfg" if [ "${1}" == "true" ]; then if [ ! "$(crontab -l | grep "/usr/local/emhttp/plugins/nvidia-driver/include/update-check.sh")" ]; then @@ -107,4 +133,13 @@ fi } -$@ +# SEC: Restrict callable functions to prevent arbitrary code execution. +# The web UI calls this script via shell_exec("... exec.sh function_name args"). +# Without a whitelist, any bash function (or command) could be invoked. +ALLOWED_FUNCTIONS="update update_version get_latest_version get_prb get_nfb get_beta get_nos get_gpu_arch get_cuda_version get_selected_version get_installed_version get_license update_check change_update_check" +if [[ " ${ALLOWED_FUNCTIONS} " == *" ${1} "* ]]; then + "$@" +else + echo "ERROR: Unknown function '${1}'" + exit 1 +fi diff --git a/source/usr/local/emhttp/plugins/nvidia-driver/include/update-check.sh b/source/usr/local/emhttp/plugins/nvidia-driver/include/update-check.sh index f503b68..08815b0 100644 --- a/source/usr/local/emhttp/plugins/nvidia-driver/include/update-check.sh +++ b/source/usr/local/emhttp/plugins/nvidia-driver/include/update-check.sh @@ -77,6 +77,19 @@ elif [ "${SET_DRV_V}" == "latest_nos" ]; then fi fi -#Check for old packages that are not suitable for this Kernel and not suitable for the current Nvidia driver version -rm -rf $(ls -d /boot/config/plugins/nvidia-driver/packages/* 2>/dev/null | grep -v "${KERNEL_V%%-*}") -rm -f $(ls /boot/config/plugins/nvidia-driver/packages/${KERNEL_V%%-*}/* 2>/dev/null | grep -v "$LAT_PACKAGE") +# SEC: Safe cleanup of old kernel directories. +# Original used unquoted command substitution with rm -rf, which could +# delete everything if variables were empty (grep -v "" matches all lines). +# Using a loop with explicit checks prevents accidental mass deletion. +while IFS= read -r dir; do + [[ "$(basename "$dir")" != "${KERNEL_V%%-*}" ]] && rm -rf "$dir" +done < <(find /boot/config/plugins/nvidia-driver/packages -mindepth 1 -maxdepth 1 -type d 2>/dev/null) + +# SEC: Only clean up old packages if we know the current package name. +# Without this guard, an empty $LAT_PACKAGE would cause grep -v "" to +# match nothing, and all files would be deleted from the packages dir. +if [ -n "$LAT_PACKAGE" ]; then + while IFS= read -r file; do + [[ "$(basename "$file")" != "$LAT_PACKAGE" && "$(basename "$file")" != "${LAT_PACKAGE}.md5" ]] && rm -f "$file" + done < <(find "/boot/config/plugins/nvidia-driver/packages/${KERNEL_V%%-*}" -mindepth 1 -maxdepth 1 -type f 2>/dev/null) +fi diff --git a/source/usr/local/emhttp/plugins/nvidia-driver/nvidia-driver.page b/source/usr/local/emhttp/plugins/nvidia-driver/nvidia-driver.page index 0f1c890..242fba7 100644 --- a/source/usr/local/emhttp/plugins/nvidia-driver/nvidia-driver.page +++ b/source/usr/local/emhttp/plugins/nvidia-driver/nvidia-driver.page @@ -5,425 +5,418 @@ Icon="nvidia-driver.png" /dev/null'); +$gpu_support = json_decode(trim($gpu_support_json), true); +$gpu_rows = ($gpu_support && isset($gpu_support['ok']) && $gpu_support['ok']) ? $gpu_support['rows'] : array(); -//Get value from update check -$update_check = shell_exec("/usr/local/emhttp/plugins/nvidia-driver/include/exec.sh update_check"); +$has_legacy_only = false; +$has_open_only = false; +foreach ($gpu_rows as $row) { + $support = $row['kernel_module_support'] ?? 'unknown'; + if ($support === 'proprietary-only') $has_legacy_only = true; + if ($support === 'open-only') $has_open_only = true; +} +$is_conflict = $has_legacy_only && $has_open_only; +$force_opensource = $has_open_only && !$has_legacy_only; +$force_legacy = $has_legacy_only && !$has_open_only; -$available_versions = $version_data['available_versions']; -$candidate_versions = $version_data['candidate_versions']; -$eachlines = $available_versions; +$module_type_display = 'Unknown'; +if ($module_license === 'OPENSOURCE') $module_type_display = 'Open Source'; +elseif ($module_license === 'PROPRIETARY') $module_type_display = 'Legacy (Proprietary)'; -foreach (array(470, 390, 340) as $legacy_major) { - $legacy_best = nvidia_best_version_for_branch($available_versions, $legacy_major); - if ($legacy_best !== null) { - $candidate_versions[] = $legacy_best; - } +if (empty($installed_v)) { + $installed_v = trim(shell_exec("nvidia-smi 2>/dev/null | grep 'Driver Version' | grep -oE '[0-9]+\\.[0-9]+\\.?[0-9]*' | head -1")); } +if (empty($installed_v)) $installed_v = 'Not installed'; -$candidate_versions = array_values(array_unique($candidate_versions)); -$latest_v_norm = nvidia_extract_version(trim((string)$latest_v)); -$latest_prb_v_norm = nvidia_extract_version(trim((string)$latest_prb_v)); -$latest_nfb_v_norm = nvidia_extract_version(trim((string)$latest_nfb_v)); -$latest_nos_v_norm = nvidia_extract_version(trim((string)$latest_nos_v)); - -//Get installed driver version -$cur_drv_v = shell_exec("nvidia-smi | grep \"Driver Version\" | cut -d ' ' -f3"); -if (empty($cur_drv_v)) { - $cur_drv_v = shell_exec("nvidia-settings --help | grep -oP '(?<=version )[\d\.]+'"); - if (empty($cur_drv_v)) { - $cur_drv_v = "ERROR: No driver found!"; - } +$eachlines = array(); +if (file_exists("/tmp/nvidia_driver")) { + $eachlines = file("/tmp/nvidia_driver", FILE_IGNORE_NEW_LINES); + if ($eachlines === false) $eachlines = array(); +} +$nvos_versions = array(); +if (file_exists("/tmp/nvos_driver")) { + $nvos_lines = file("/tmp/nvos_driver", FILE_IGNORE_NEW_LINES); + if ($nvos_lines !== false) $nvos_versions = $nvos_lines; } -//Get Unraid driver version -$unraid_version = parse_ini_file('/etc/unraid-version'); +$is_opensource_selected = ($force_opensource || $module_license === 'OPENSOURCE' || $selected_v === 'latest_nos'); ?> - - - - - -

Nvidia Driver Package

-
- -
- -
-

Nvidia Info:

-

Nvidia Driver Version: -

- -

Open Source Kernel Module: - Yes

- -

Open Source Kernel Module: - No

- - -

Installed GPU(s):
-


-

GPU Driver Support:

-
-

Loading supported drivers...

-
-

- - GPU-based driver filtering is enabled. -

-
- - - - - Driver Update Notification - - -
- - -
- -

The Driver Update Notification, checks once a day (between 8 am and 10 am) whether a new version of the selected branch is available and, if necessary, downloads the new driver and sends a notification.

-

The server needs to be restarted in order to install the new driver.


- - - -
+$(function() { filterVersions(); }); + -
-

Select preferred driver version:

-
- - - - - - - - - - - - - -
Latest Versions: -

/>latest:

- -

/>Production Branch:

- -

Production Branch: Not found for this Unraid version!

- - -

/>New Feature Branch:

- -

New Feature Branch: Not found for this Unraid version!

- - -

/>Open Source Driver: - Please see readme below!' : ''; ?>

- -

Open Source Driver: Not found for this Unraid version!

- -
Available Versions: - -

- /> - -

- -
- - -
- -

ATTENTION: If you set the version to 'latest', 'Production Branch' or 'New Feature Branch' you either have to enable the Driver Update Notification (this will download the driver automatically) or if you disabled the Driver Update Notification you manually have to click the button "Update & Download" to download a newer driver.
Please keep in mind, to install a newer driver you have to reboot your server.

-
-

Latest versions:

-
-
+ if (!empty($nvos_versions)) { + $nvos_sorted = $nvos_versions; rsort($nvos_sorted, SORT_NATURAL); + foreach ($nvos_sorted as $nv) { + $ver = trim($nv); + if (!empty($ver)) echo mk_option($selected_v, $ver, 'v'.$ver, 'data-type="opensource"'); + } + } + ?> + + +
+
+ + +
+ +
+ Branch descriptions +
+
Latest
Always downloads the latest driver version regardless of branch.
+
Production
Long lifecycle, ISV certified, optimal stability. Recommended for production.
+
New Feature
Short lived branch (1-2 releases) with new features between Production releases.
+
Beta
Cutting-edge features. May contain issues. Not for production use.
+
Open Source
Open Source kernel modules (MIT/GPL) + proprietary libraries. Turing and newer only. Required for Blackwell GPUs. + +
For GeForce/Workstation: create /boot/config/modprobe.d/nvidia.conf with options nvidia NVreg_OpenRmEnableUnsupportedGpus=1 + +
+
+
+ + + +
+

Auto-Update Notification

+
+
+ + + +
+
+ +

Checks once daily (8-10am) for new versions on the selected branch. Downloads automatically and sends a notification. Reboot required to install.

+
+ + + parent.window.location.reload();'; +if (isset($_POST['changeUPDcheck'])) { + shell_exec("/usr/local/emhttp/plugins/nvidia-driver/include/exec.sh change_update_check " . escapeshellarg($_POST["updata_check_selected"])); + echo ''; } ?>