-
-
Save digitaltrails/c06154deb43306baa6921cb24616c6e3 to your computer and use it in GitHub Desktop.
| #!/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/. | |
| # | |
| log_filename="update-Gxx-$(date '+%Y-%m-%d_%H%M%S').log" | |
| exec > >(tee -a log.txt) 2>&1 | |
| echo "INFO: logging to $log_filename" | |
| 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 | |
| flush_input() { | |
| while read -t 0 discard; do | |
| read discard | |
| done | |
| } | |
| confirm() { | |
| flush_input | |
| 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=$([ "$value" -eq 0 ] && echo 'y' || echo 'n')" >> $CONFIG_FILE | |
| fi | |
| return $value | |
| } | |
| prompt_for_input() { | |
| flush_input | |
| if [ -n "$REMEMBER" ] | |
| then | |
| config_value="${!REMEMBER}" | |
| if [ -n "$config_value" ] | |
| then | |
| echo "INFO: value from config $REMEMBER='$config_value'" >&2 | |
| default_value="$config_value" | |
| fi | |
| fi | |
| echo "" >&2 | |
| prompt=$1 | |
| shift | |
| default_value="${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 [ -z "$g_variant" ] | |
| then | |
| echo "INFO: Checking for non open Gxx driver variant" | |
| g_variant=$(zypper se -i 'nvidia-driver*' | | |
| awk -F' +[|] +' '$2 ~ /^nvidia-driver/ { sub(".*G", "G", $2); sub("-.*", "", $2); print $2; exit 0 }') | |
| fi | |
| 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_nvidia_version" ] | |
| then | |
| echo "INFO: Installed version of $nvidia_driver_name is $installed_nvidia_version" | |
| else | |
| echo "INFO: Driver $nvidia_driver_name is 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 | |
| purge_after="" | |
| echo "" | |
| echo "WARN: Note that locking will prevent the normal purge-kernels job from cleaning up old kernels" | |
| if DEFAULT='n' REMEMBER=DO_PURGE_KERNELS confirm "Perform zypper purge-kernels after upgrade before appling locks?" | |
| then | |
| purge_after="zypper purge-kernels" | |
| fi | |
| 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[@]} | |
| $purge_after | |
| 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[@]} | |
| eval $purge_after | |
| 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 |
1.No mismatch detection/recovery — A mismatch_check mode (or check on every run) that compares
...
Thanks for taking the time to send some feedback and contribute some suggestions.
When I have some time I read through all your suggestions and corrections.
Some quick thoughts...
I'm not sure I'm comfortable with a automated/non-interactive use of the script.
At my current level of understanding, I'm not sure I have sufficiently covered off all the ways things could go wrong. The rapid evolution of Nvidia's driver and OpenSUSE's packaging of it, seems to constantly provide new ambushes. So I feel a bit wary about full automation.
I could see a mismatch pre-check would be good to have.
I'm not sure how much further time to invest in it. It seems to me that the meta-packaging of the driver is working toward a foolproof solution, which would make this script redundant. On the other hand I'm not sure how long the meta-packing will take to mature. Plus my level of understanding of meta-packages may be placing too much confidence in their ability to address all the issues.
- Minor: confirm() config serialization looks off — echo "$REMEMBER=$([ -n $value ] && echo 'y' || echo 'n')" — v a l u e i s a r e t u r n c o d e ( 0 / 1 ) , n o t a s t r i n g , s o − n a l w a y s s e e s i t a s n o n − e m p t y . T h e l o g i c s h o u l d b e ([ "$value" -eq 0 ] && echo 'y' || echo 'n').
I fixed this. Funny the existing code seemed to behave - anyway I made the change and it seems to work too.
Plus I fixed an error where the remembered value wasn't overriding the default.
Still pondering the auto-aspect.
I'm concentrating on another pet-project at the moment (vdu_controls) so I haven't given this one much time and attention. I would certainly like to add a mismatch check. I will probably look at that first.
At my current level of understanding, I'm not sure I have sufficiently covered off all the ways things could go wrong. The rapid evolution of Nvidia's driver and OpenSUSE's packaging of it, seems to constantly provide new ambushes. So I feel a bit wary about full automation.
Yeah, ok so the script just mess up when it tried to replace the non-signed G06 with the signed version. So it definitely has problems it it's used to switch drivers. Or maybe the latest G06 doesn't work with this GPU - anyway, clearly some error cases are not covered.
Fixed reporting of existing nvidia driver version.
Note: locking down the kernels prevents purge-kernels from doing its job, so you need to periodically manually unlock purge kernels.
Add an option to also do a purge-kernels. Logged everything to a file.
1.No mismatch detection/recovery — A mismatch_check mode (or check on every run) that compares installed KMP driver version against installed userspace version would catch the broken state immediately and either suggest or perform the corrective downgrade.$value is a return code (0/1), not a string, so -n always sees it as non-empty. The logic should be $ ([ "$value" -eq 0 ] && echo 'y' || echo 'n').
2. Non-interactive mode needs documentation — The y argument sets DEFAULT_UPDATE_CONFIRM=y, and piping /dev/null as stdin makes all read prompts fall through to saved config defaults. This makes fully automated (systemd timer) operation possible, but it's undocumented. Worth a note in the usage header.
3. Duplicate NVIDIA repo warning but no fix — The script correctly warns about multiple enabled NVIDIA repos pointing to the same URL, but leaves the user to resolve it manually. A suggestion to disable the lower-priority duplicate would help.
4. Minor: confirm() config serialization looks off — echo "$REMEMBER=$([ -n $value ] && echo 'y' || echo 'n')" —
Overall it's a well-designed script that fills a genuine gap in the openSUSE NVIDIA experience — the locking approach is exactly right.
I have worked with claude to make the script automated so I don't need to remember to run the script. Here is what it has given me. Hope this helps you.
"Here are specific, actionable suggestions with code:
Rather than relying on the y argument + < /dev/null trick, add a proper flag and detect TTY:
Near the top, after argument parsing
AUTO_MODE=false
for arg in "$@"; do
case $arg in
--auto|-a) AUTO_MODE=true; DEFAULT_UPDATE_CONFIRM='y' ;;
--check|-c) CHECK_ONLY=true ;;
esac
done
Also detect if stdin is not a TTY (e.g. systemd, cron)
if [ ! -t 0 ]; then
AUTO_MODE=true
DEFAULT_UPDATE_CONFIRM='y'
fi
confirm() {
... existing config lookup ...
In auto mode: use saved/default value without prompting
if $AUTO_MODE; then
if [ "$default_value" == "no default" ]; then
echo "ERROR: No saved config for '$REMEMBER' — run interactively first" >&2
exit 1
fi
echo "INFO: auto-mode using $REMEMBER=$default_value" >&2
[ "$default_value" == "y" ] && return 0 || return 1
fi
... existing read loop ...
}
Same pattern for prompt_for_input():
prompt_for_input() {
... existing config lookup ...
if $AUTO_MODE; then
if [ -z "$default_value" ]; then
echo "ERROR: No saved config for '$REMEMBER' — run interactively first" >&2
exit 1
fi
echo "INFO: auto-mode using $REMEMBER='$default_value'" >&2
echo "$default_value"
return
fi
... existing read loop ...
}
Fail fast if the config file doesn't exist and --auto was requested:
if $AUTO_MODE && [ ! -f "$CONFIG_FILE" ]; then
echo "ERROR: Auto mode requires saved config. Run interactively first:" >&2
echo " $(basename $0)" >&2
exit 1
fi
Makes it easy for a calling script (like nvidia-kmp-check) to know what happened:
Exit codes
EXIT_OK=0 # Nothing to do or update succeeded
EXIT_NO_MATCH=2 # KMP/userspace pair not yet available
EXIT_MISMATCH=3 # Installed versions don't match (broken state)
EXIT_NEED_REBOOT=4 # Update applied, reboot required
EXIT_ERROR=1 # General error
A simple --log FILE option so calling scripts don't need to redirect:
LOG_FILE=""
... parse --log from args ...
log() {
echo "$@"
[ -n "$LOG_FILE" ] && echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >> "$LOG_FILE"
}
Summary of invocation modes this enables:
First-time interactive setup (saves config)
nvidia_OpenSUSE_Gxx_updater.bash
Check what would happen, no changes
nvidia_OpenSUSE_Gxx_updater.bash --check
Fully automated (systemd/cron) - uses saved config, no prompts
nvidia_OpenSUSE_Gxx_updater.bash --auto --log /var/log/nvidia-kmp-update.log
The key design principle is: interactive first run saves config → all subsequent auto runs use it, or fail loudly if it's missing — rather than relying on stdin tricks that are fragile and undocumented."