Last active
March 12, 2026 07:44
-
-
Save digitaltrails/c06154deb43306baa6921cb24616c6e3 to your computer and use it in GitHub Desktop.
update/install/lock Nvidia OpenSUSE Open Gxx and kernel packages
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # nvidia_OpenSUSE_Gxx_updater - update/install/lock Nvidia OpenSUSE Open Gxx and kernel packages | |
| # ============================================================================================== | |
| # Copyright (C) 2026 Michael Hamilton | |
| # | |
| # This script attempts to identify the correct or current Nvidia | |
| # OpenSUSE packaged versions for the G06/G07 driver, | |
| # supporting packages, and kernel. If that succeeds, locks can | |
| # optionally be applied to keep the working combo in sync. If | |
| # a new viable combo is identified, the script can use zypper | |
| # to do version specific updates to precisely that combo. | |
| # | |
| # The script is careful to identify matching versions of packages | |
| # from the Nvidia/CUDA repos and OpenSUSE repos. This prevents | |
| # broken updates from occurring due to the participating repo's | |
| # latest package versions not being in sync. | |
| # | |
| # For example, the CUDA repo includes multiple versions of each | |
| # package. The latest CUDA packages are often ahead of OpenSUSE's | |
| # signed driver packages. Zypper isn't aware of the cross repo | |
| # version dependencies, it will install the latest versions from | |
| # each, which may be a non-function mismatch. This script | |
| # prevents these kind of mismatches. | |
| # | |
| # The script won't make any changes without first detailing them | |
| # and prompting for permission. | |
| # | |
| # The script defaults to using the OpenSUSE signed driver, but | |
| # the default can be overridden. | |
| # | |
| # The script has only been tested for the G06 and G07 drivers, I don't | |
| # have the environments necessary to test for any others. It may | |
| # be a viable approach for maintaining earlier drivers, but | |
| # modifications may be required. | |
| # | |
| # Usage | |
| # ===== | |
| # | |
| # It's a good idea to do a backup first (snap or whatever). | |
| # | |
| # First time use | |
| # -------------- | |
| # | |
| # Run the script once to put in place the locks. If it's been | |
| # some time since you've updated, you can elect not to perform | |
| # anything except the locking. | |
| # | |
| # Subsequent use | |
| # -------------- | |
| # | |
| # Once the locks are in place, the script can be run after | |
| # zypper-dup to separately update the kernel and driver, | |
| # or simply run it any time you think its time to update | |
| # the kernel and driver. For example: | |
| # | |
| # 0, Pre-zypper-dup backups (as per your normal processes). | |
| # | |
| # 1. Use zypper dup as per normal, the locks prevent | |
| # any driver or kernel updates, for example: | |
| # | |
| # zypper dup --auto-agree-with-licenses --no-allow-vendor-change | |
| # | |
| # 2. After a dup also run nvidia_open_Gxx_updater.bash which | |
| # will determine if a kernel+driver pairing is available, | |
| # and optionally perform the updates. | |
| # | |
| # nvidia_OpenSUSE_Gxx_updater.bash | |
| # | |
| # Note it will ask questions allowing you to alter or | |
| # confirm choices it has made. | |
| # | |
| # | |
| # GNU License | |
| # =========== | |
| # | |
| # This program is free software: you can redistribute it and/or modify it | |
| # under the terms of the GNU General Public License as published by the | |
| # Free Software Foundation, version 3. | |
| # | |
| # This program is distributed in the hope that it will be useful, but | |
| # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | |
| # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
| # more details. | |
| # | |
| # You should have received a copy of the GNU General Public License along | |
| # with this program. If not, see https://www.gnu.org/licenses/. | |
| # | |
| echo "INFO: OpenSUSE Open Gxx Nvidia driver update script - for desktop PC with a modern Nvidia card" | |
| echo "WARNING: This scripts adds zypper locks to prevent zypper dup from updating the kernel and driver" | |
| echo "" | |
| if [ "$1" == 'y' ] | |
| then | |
| DEFAULT_UPDATE_CONFIRM='y' | |
| else | |
| DEFAULT_UPDATE_CONFIRM='n' | |
| fi | |
| CONFIG_DIR="$HOME/.config/nvidia_open_Gxx_updater" | |
| mkdir -p "$CONFIG_DIR" | |
| CONFIG_FILE="$CONFIG_DIR/$HOSTNAME" | |
| if [ -f "$CONFIG_FILE" ] | |
| then | |
| echo "INFO: reading config from $CONFIG_FILE" | |
| cat < "$CONFIG_FILE" | |
| echo "" | |
| source "$CONFIG_FILE" | |
| fi | |
| confirm() { | |
| echo "" >&2 | |
| default_value="${DEFAULT:-no default}" | |
| if [ -n "$REMEMBER" ] | |
| then | |
| config_value=${!REMEMBER} | |
| if [ "$config_value" == "y" -o "$config_value" == "n" ] | |
| then | |
| echo "INFO: config $REMEMBER=$config_value" | |
| default_value="$config_value" | |
| fi | |
| fi | |
| while true; do | |
| read -p ">>>> $1 [y/n]? ($default_value) " yn | |
| if [ -z "$yn" -a "$default_value" != "no default" ] | |
| then | |
| yn="$default_value" | |
| fi | |
| case $yn in | |
| [Yy]* ) echo ""; value=0; break;; | |
| [Nn]* ) echo ""; value=1; break;; | |
| * ) echo "Please answer yes or no.";; | |
| esac | |
| done | |
| if [ -n "$REMEMBER" ] | |
| then | |
| [ -f $CONFIG_FILE ] && sed -i "/^$REMEMBER[=]/d" $CONFIG_FILE | |
| echo "$REMEMBER=$([ -n $value ] && echo 'y' || echo 'n')" >> $CONFIG_FILE | |
| fi | |
| return $value | |
| } | |
| prompt_for_input() { | |
| if [ -n "$REMEMBER" ] | |
| then | |
| config_value="${!REMEMBER}" | |
| if [ -n "$config_value" ] | |
| then | |
| echo "INFO: config $REMEMBER='$config_value'" >&2 | |
| default_value="$config_value" | |
| fi | |
| fi | |
| echo "" >&2 | |
| prompt=$1 | |
| shift | |
| default_value="${DEFAULT}" | |
| possible_values="" | |
| if [ $# -gt 1 ] | |
| then | |
| possible_values="$@" | |
| prompt="$prompt [$(echo $possible_values | sed 's| |/|g')]" | |
| fi | |
| while true | |
| do | |
| read -p ">>>> $prompt ($default_value) " value | |
| value=${value:-$default_value} | |
| if [ -n "$value" ] | |
| then | |
| if [ -z "$possible_values" ] | |
| then | |
| break | |
| fi | |
| if echo " $possible_values " | grep -q " $value " | |
| then | |
| break | |
| fi | |
| fi | |
| done | |
| echo "" >&2 | |
| if [ -n "$REMEMBER" ] | |
| then | |
| [ -f $CONFIG_FILE ] && sed -i "/^$REMEMBER[=]/d" $CONFIG_FILE | |
| echo "$REMEMBER='$value'" >> $CONFIG_FILE | |
| fi | |
| echo $value | |
| } | |
| drop_output() { | |
| cat > /dev/null | |
| } | |
| dedent() { | |
| local first_line | |
| # Read the first line and determine its leading whitespace | |
| IFS= read -r first_line | |
| local indent=$(echo "$first_line" | sed 's/^\([[:space:]]*\).*/\1/') | |
| # Process the first line and then the rest of the stream | |
| { echo "${first_line#"$indent"}"; sed "s/^$indent//"; } | |
| } | |
| nvidia_gpu=$(lspci | awk '$2 == "VGA" && $0 ~ /NVIDIA/ { sub(".*: ", ""); sub("NVIDIA Corporation ",""); print }') | |
| if [ -n "$nvidia_gpu" ] | |
| then | |
| echo "INFO: GPU: $nvidia_gpu" | |
| else | |
| echo "WARNING: lspci scan failed to identify any installed Nvidia GPU." | |
| fi | |
| # highest priority repo that refers to download.nvidia.com | |
| nvidia_repo_list=$(zypper repos -E -u -P | | |
| awk -F' +[|] +' ' | |
| $8 ~ /download.nvidia.com/ { | |
| repos[++count] = $2; | |
| priority[$2] = $7; | |
| } | |
| END { | |
| for (i=1; i<=count; i++) { | |
| printf("%s%s", sep, repos[i]); | |
| sep = " "; | |
| if (i > 1) { | |
| if (priority[repos[i]] == priority[repos[1]]) { | |
| printf("WARNING: more than one Nvidia repo with the same priority %s %s and %s!\n", | |
| priority[repos[1]], repos[1], repos[i]) > "/dev/stderr"; | |
| } | |
| else { | |
| printf("WARNING: more than one enabled Nvidia repo %s (priority %s) and %s (priority %s)!\n", | |
| repos[1], priority[repos[1]], repos[i], priority[repos[i]]) > "/dev/stderr"; | |
| } | |
| } | |
| } | |
| print ""; | |
| } | |
| ' | |
| ) | |
| if [ -n "$nvidia_repo_list" ] | |
| then | |
| nvidia_repo=$(echo "$nvidia_repo_list" | awk '{print $1;}') | |
| echo "INFO: Detected Nvidia proprietary repo: $nvidia_repo" | |
| if [ -n "$NVIDIA_REPO" -a "$nvidia_repo" != "$NVIDIA_REPO" ] | |
| then | |
| echo "WARNING: detected and config repos differ - $nvidia_repo != $NVIDIA_REPO - ignoring config." | |
| NVIDIA_REPO="" | |
| fi | |
| fi | |
| nvidia_repo=$(DEFAULT="$nvidia_repo" REMEMBER=NVIDIA_REPO prompt_for_input "Nvidia repo name?") | |
| possible_g_variants="G07 G06 G05 G04" | |
| g_variant=$(zypper se -i 'nvidia-open-driver*' | | |
| awk -F' +[|] +' '$2 ~ /^nvidia-open-driver/ { sub(".*G", "G", $2); sub("-.*", "", $2); print $2; exit 0 }') | |
| if [ -n "$g_variant" ] | |
| then | |
| echo "INFO: Nvidia driver OpenSUSE package variant $g_variant already installed." | |
| fi | |
| if [ -z "$g_variant" -a -n "$nvidia_gpu" ] | |
| then | |
| echo "INFO: Failed to detect an installed OpenSUSE Gxx package." | |
| echo "INFO: Trying to match an appropriate Gxx variant for a $nvidia_gpu" | |
| g_variant=$(awk -vgpu="$nvidia_gpu" ' | |
| BEGIN { | |
| g_driver["RTX [34]0[0-9]{2}"] = "G07"; | |
| g_driver["GTX 1[0-9]{3}"] = "G06"; | |
| g_driver["GTX [67][0-9]{2}"] = "G06"; | |
| g_driver["GT [2345][0-9]{2}"] = "G05"; | |
| for (gd in g_driver) { | |
| if (gpu ~ gd) { | |
| print g_driver[gd]; | |
| exit 0; | |
| } | |
| } | |
| } | |
| ') | |
| if [ -n "$g_variant" ] | |
| then | |
| echo "INFO: Matched OpenSUSE $g_variant package for a $nvidia_gpu" | |
| else | |
| echo "INFO: Failed to match an OpenSUSE Gxx package, defaulting to G07 (the latest)." | |
| fi | |
| fi | |
| g_variant=$(DEFAULT="${g_variant:-G07}" REMEMBER=GXX_VARIANT prompt_for_input "Nvidia driver OpenSUSE package variant?" $possible_g_variants) | |
| if [ "$g_variant" != "G06" -a "$g_variant" != "G07" ] | |
| then | |
| echo "ERROR: This script has only been tested with open G06 and G07 variants. The $g_variant has not been tested." | |
| exit 1 | |
| fi | |
| echo "INFO: Nvidia repo: $nvidia_repo" | |
| echo "INFO: Nvidia driver OpenSUSE package variant: $g_variant" | |
| kernel=$(uname -r) | |
| kernel_version=$(echo $kernel | sed s/-.*$//) | |
| kernel_variant=$(echo $kernel | sed s/^[0-9.-]*//) | |
| kernel_package_name=kernel-$kernel_variant | |
| case $g_variant in | |
| 'G06' | 'G07' ) | |
| nvidia_driver_prefix=nvidia-open-driver | |
| supporting_packages="nvidia-video-Gxx nvidia-compute-utils-Gxx nvidia-persistenced" | |
| if zypper --no-refresh lr --show-enabled-only $nvidia_repo | grep '^URI' | grep -q cuda | |
| then | |
| k_variant="signed-cuda-kmp-$kernel_variant" | |
| else | |
| k_variant="signed-kmp-$kernel_variant" | |
| fi | |
| nvidia_driver_name="${nvidia_driver_prefix}-${g_variant}-${k_variant}" | |
| supporting_packages="nvidia-video-Gxx nvidia-compute-utils-Gxx nvidia-persistenced" | |
| ;; | |
| 'G04' | 'G05') | |
| nvidia_driver_name="nvidia-open-${g_variant}-${k_variant}" | |
| supporting_packages="x11-video-nvidiaGxx nvidia-computeGxx" | |
| ;; | |
| *) | |
| echo "ERROR: unsupported Gxx variant $g_variant" | |
| exit 1 | |
| ;; | |
| esac | |
| echo "INFO: OpenSUSE Nvidia Gxx driver default: $nvidia_driver_name" | |
| echo "INFO: Supporting packages: $supporting_packages" | |
| nvidia_driver_name=$(DEFAULT="$nvidia_driver_name" REMEMBER=NVIDIA_DRIVER_NAME prompt_for_input "Nvidia driver name: ") | |
| echo "INFO: Linux kernel package: $kernel_package_name" | |
| echo "INFO: Nvidia driver OpenSUSE package: $nvidia_driver_name" | |
| # need to match with the running kernel - assumes the running kernel came from a package | |
| installed_kernel_version=$kernel_version | |
| echo "INFO: Current kernel version: $installed_kernel_version" | |
| installed_nvidia_version=$(zypper search --details -i $nvidia_driver_name | awk -F' +[|] +' '$3 ~ /package/ { sub(/-.*$/, "", $4); print $4; exit 0 }') | |
| if [ -n $installed_version ] | |
| then | |
| echo "INFO: Installed version of $nvidia_driver_name is $installed_nvidia_version" | |
| else | |
| echo "INFO: Driver not currently installed." | |
| fi | |
| available_nvidia_version=$(zypper --no-refresh info $nvidia_driver_name | awk -F' +[:] +' '$1 == "Version" { sub(/-.*$/, "", $2); print $2; exit 0 }') | |
| if [ -n $available_nvidia_version ] | |
| then | |
| echo "INFO: Available version of $nvidia_driver_name is $available_nvidia_version" | |
| else | |
| echo "INFO: Driver not currently available!" | |
| exit -1 | |
| fi | |
| if [ -n $available_nvidia_version ] | |
| then | |
| required_kernel_version=$(echo $available_nvidia_version | sed 's/[^_]*_k//;s/_.*//') | |
| echo "INFO: Required kernel is $kernel_package_name $required_kernel_version" | |
| if [ -n "$(zypper --no-refresh se -s --match-exact $kernel_package_name | awk -vkv="$required_kernel_version" -F' +[|] +' '$4 ~ "^" kv {print}')" ] | |
| then | |
| echo "INFO: Required $kernel_package_name $required_kernel_version is available" | |
| else | |
| echo "WARNING: required kernel $kernel_package_name $required_kernel_version is unavailable from zypper." | |
| possible_kernel_version=$(zypper --no-refresh se -s --match-exact $kernel_package_name | awk -F' +[|] +' -vrkv=$required_kernel_version ' | |
| BEGIN { | |
| prefix=rkv; sub("[.].*$", "", prefix); # Remove last part of kernel version number | |
| } | |
| $4 ~ "^" prefix { | |
| split(rkv, rkvparts, "."); | |
| split($4, akvparts, "."); | |
| if (akvparts[1] == rkvparts[1] && akvparts[2] == rkvparts[2]) { | |
| if (akvparts[3] > rkvparts[3]) { # Available kernel last-part of version > required last-part | |
| sub("-.*", "", $4); | |
| print $4; # Possible alternative kernel | |
| exit 0; | |
| } | |
| } | |
| }') | |
| if [ -n "$possible_kernel_version" ] | |
| then | |
| echo "INFO: Kernel $possible_kernel_version may work (often minor version changes will work OK)." | |
| if confirm "Do you want to try with $kernel_package_name $possible_kernel_version instead of $required_kernel_version?" | |
| then | |
| required_kernel_version=$possible_kernel_version | |
| else | |
| echo "ERROR: No possible kernel, cannot continue." | |
| exit 1 | |
| fi | |
| else | |
| echo "ERROR: No matching kernel and no more up to date kernel available, cannot continue." | |
| exit 1 | |
| fi | |
| fi | |
| fi | |
| nvidia_release=$(echo $available_nvidia_version | awk -F_ '{print $1}') | |
| if [ -n "$supporting_packages" ] | |
| then | |
| final_list="" | |
| for package in $supporting_packages | |
| do | |
| package_g_variant=$(echo $package | sed s/Gxx/$g_variant/) | |
| package_fully_qualified="$package_g_variant-$nvidia_release" | |
| if zypper --no-refresh search -r "$nvidia_repo" $package_fully_qualified > /dev/null | |
| then | |
| if zypper --no-refresh search -ir "$nvidia_repo" --match-exact $package_fully_qualified > /dev/null | |
| then | |
| echo "INFO: Supporting $package_fully_qualified is already installed (from $nvidia_repo repo)" | |
| else | |
| echo "INFO: Supporting $package_fully_qualified is available (from $nvidia_repo repo)" | |
| final_list="$final_list $package" | |
| fi | |
| else | |
| echo "ERROR: Supporting $package_fully_qualified is not available from $nvidia_repo repo" | |
| exit 1 | |
| fi | |
| done | |
| supporting_packages_with_updates="$final_list" | |
| if [ -n "$supporting_packages_with_updates" ] | |
| then | |
| echo "INFO: Supporting packages with updates: $supporting_packages_with_updates" | |
| else | |
| echo "INFO: Supporting packages already up to date for $available_nvidia_version." | |
| fi | |
| fi | |
| if [ "$required_kernel_version" == "$kernel_version" ] | |
| then | |
| echo "INFO: Required Linux kernel $required_kernel_version already installed" | |
| elif [ -n "$required_kernel_version" ] | |
| then | |
| echo "INFO: Linux kernel requires upgrading to $required_kernel_version before updating the Nvidia driver" | |
| else | |
| echo "ERROR: Cannot match Nvidia driver $available_nvidia_version to Linux kernel $required_kernel_version" | |
| exit 1 | |
| fi | |
| if [ "$available_nvidia_version" == "$installed_nvidia_version" -a -z "$supporting_packages_with_updates" ] | |
| then | |
| DEFAULT="n" confirm "No Nvidia updates required/available, proceed anyway (to setup locks perhaps)?" || exit 0 | |
| fi | |
| kernel_lock="kernel-*" | |
| kernel_supporting_packages="" | |
| g_variant_lock="*nvidia*${g_variant}*" | |
| g_supporting_packages_locks=$(echo "$supporting_packages" | sed 's/[a-zA-Z0-9_-]*Gxx[a-zA-Z0-9_-]*//g') | |
| if DEFAULT='y' REMEMBER=LOCK_KERNEL_AND_DRIVER confirm "Lock down Linux kernel $kernel_lock $kernel_supporting_packages and $g_variant packages to keep them in sync?" | |
| then | |
| set -x | |
| zypper al "$kernel_lock" $kernel_supporting_packages "$g_variant_lock" $g_supporting_packages_locks | |
| { set +x; } 2>/dev/null | |
| fi | |
| if DEFAULT='y' REMEMBER=LOCK_OTHER_GXX confirm "Lock down other Gxx packages to keep them from erroneously being picked up by zypper dup?" | |
| then | |
| for other_variant in $possible_g_variants | |
| do | |
| if [ "$other_variant" != "$g_variant" ] | |
| then | |
| other_variant_lock="*nvidia*${other_variant}*" | |
| set -x | |
| zypper al "$other_variant_lock" | |
| { set +x; } 2>/dev/null | |
| fi | |
| done | |
| fi | |
| package_specifiers=( | |
| "$kernel_package_name" == "$required_kernel_version" | |
| "${kernel_package_name}-devel" == "$required_kernel_version" | |
| "$nvidia_driver_name" == "$available_nvidia_version") | |
| for package in $supporting_packages | |
| do | |
| package_g_variant=$(echo $package | sed s/Gxx/$g_variant/) | |
| control_variable=$(awk -vp="$package_g_variant" 'BEGIN {p = toupper(p); sub("-", "_", p); print "" p "_UPDATE"; exit 0}') | |
| package_specifiers+=("$package_g_variant" == "$nvidia_release") | |
| done | |
| echo "" | |
| echo "INFO: Package list: " "${package_specifiers[@]}" | |
| echo "" | |
| echo "INFO: Proposed zypper commands:" | |
| cat <<EOF | |
| zypper --quiet rl "$kernel_lock" $kernel_supporting_packages "$g_variant_lock" $g_supporting_packages_locks | |
| zypper --no-refresh install --no-allow-vendor-change ${package_specifiers[@]} | |
| zypper --quiet al "$kernel_lock" $kernel_supporting_packages "$g_variant_lock" $g_supporting_packages_locks | |
| EOF | |
| if DEFAULT="$DEFAULT_UPDATE_CONFIRM" confirm "Perform updates?" | |
| then | |
| echo "INFO: note that zypper will only update packages that actually require updating" | |
| set -x | |
| zypper --quiet rl "$kernel_lock" $kernel_supporting_packages "$g_variant_lock" $g_supporting_packages_locks | |
| zypper --no-refresh install --no-allow-vendor-change ${package_specifiers[@]} | |
| zypper --quiet al "$kernel_lock" $kernel_supporting_packages "$g_variant_lock" $g_supporting_packages_locks | |
| { set +x; } 2>/dev/null | |
| fi | |
| echo "" | |
| echo "INFO: finished" | |
| exit 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment