Skip to content

Instantly share code, notes, and snippets.

@loopyd
Last active April 9, 2026 05:15
Show Gist options
  • Select an option

  • Save loopyd/aebbd17d59f8aed7a890e704eb702de0 to your computer and use it in GitHub Desktop.

Select an option

Save loopyd/aebbd17d59f8aed7a890e704eb702de0 to your computer and use it in GitHub Desktop.
[bash] CUDA Version Manager - Easily Manage CUDA version on debian/ubuntu
#!/usr/bin/env bash
_S=0; _E=0; _U=0; _P=0
if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then
_S=1
[[ "$-" == *e* ]] && _E=1
[[ "$-" == *u* ]] && _U=1
[[ "$(set -o | awk '$1=="pipefail"{print $2}')" == on ]] && _P=1
fi
set -euo pipefail
N=$(basename "${BASH_SOURCE[0]}"); X=install; CV=("11.8" "12.1")
DF=""; MD=1; DR=0; QT=0; NC=0; FC=0; UV=""; RB=""; RC=()
PF="/etc/apt/preferences.d/cuda-repository-pin-600"
DP="/etc/apt/preferences.d/cuda-driver-block-pin"
SF="/etc/apt/sources.list.d/cuda-nvidia.list"; AR=""
ZR=""; ZI=""; ZW=""; ZE=""; ZK=""
setc() {
if ((NC)) || [[ ! -t 1 ]]; then
ZR=""; ZI=""; ZW=""; ZE=""; ZK=""
else
ZR=$(tput sgr0 || echo ""); ZI=$(tput setaf 2 || echo "")
ZW=$(tput setaf 3 || echo ""); ZE=$(tput setaf 1 || echo "")
ZK=$(tput setaf 4 || echo "")
fi
}
setc
cln() {
local s="$1"
s="$(printf '%s' "$s" | sed -E $'s/\x1B\[[0-9;?]*[ -/]*[@-~]//g; s/[^[:print:]\t]/ /g')"
s="${s#"${s%%[![:space:]]*}"}"; s="${s%"${s##*[![:space:]]}"}"
printf '%s' "$s"
}
log() {
local lvl="$1" col="$2" raw ln seen=0
((QT)) && [[ "$lvl" != ERR ]] && return 0
shift 2; raw="$*"
while IFS= read -r ln || [[ -n "$ln" ]]; do
ln="$(cln "$ln")"
[[ -z "$ln" ]] && continue
printf '%s | %b[%s] %s%b\n' "$(date +"%F %T")" "$col" "$lvl" "$ln" "$ZR"
seen=1
done < <(printf '%s\n' "$raw")
((seen)) || printf '%s | %b[%s] %s%b\n' "$(date +"%F %T")" "$col" "$lvl" "" "$ZR"
}
i() { log INFO "$ZI" "$*"; }
w() { log WARN "$ZW" "$*"; }
e() { log ERR "$ZE" "$*"; }
ok() { log OK "$ZK" "$*"; }
die() { e "$*"; exit 1; }
nv() { local a b; IFS='.' read -r a b _ <<<"${1:-}"; [[ -n "$a" && -n "$b" ]] && echo "$a.$b"; }
ac() {
local v
command -v nvidia-smi >/dev/null 2>&1 || return 1
v="$(nvidia-smi 2>/dev/null | sed -nE 's/.*CUDA Version: *([0-9]+\.[0-9]+).*/\1/p' | head -n 1)"
v="$(nv "$v" || true)"
[[ -n "$v" ]] || return 1
printf '%s' "$v"
}
us() {
cat <<EOF
CUDA Version Manager - Install and manage multiple CUDA versions on Debian/Ubuntu
Author: loopyd <loopyd@github.com> | GPL 3.0 License
Usage:
${N} install [options] [VERSION ...]
${N} uninstall [options] [VERSION ...]
${N} use [options] VERSION
${N} list
${N} help
Install options:
-v, --versions LIST Comma/space-separated versions (ex: "11.8,12.1")
-d, --default VER Default module version
-r, --dry-run Validate and print actions without system changes
-f, --force Allow cuDNN major switch (may remove existing cuDNN packages)
-m, --no-modules Skip environment-modules setup
Uninstall options:
-v, --versions LIST Comma/space-separated versions (ex: "11.8,12.1")
-r, --dry-run Validate and print actions without system changes
-m, --no-modules Skip environment-modules cleanup
Use options:
-r, --dry-run Print exports without changing shell
-h, --help Show help
Global options:
-q, --quiet Suppress non-error output
-n, --no-color Disable colored output
Examples:
${N} install --versions "11.8,12.1" --default 12.1
${N} install 12.1 --dry-run
${N} install # auto-detect via nvidia-smi when available
${N} uninstall --versions "12.1"
${N} uninstall 11.8 --dry-run
${N} use 11.8
${N} list
Notes:
- 'use' must be sourced to affect your current shell.
- Example: source ./${N} use 12.1
- NVIDIA apt cuDNN packages generally provide/conflict on shared virtual names, so one CUDA-major cuDNN target is installed at a time.
- Use -f/--force to switch cuDNN target major during install.
EOF
}
rt() {
local tries="$1" n=1 rc=0
shift
while true; do
"$@" && return 0
rc=$?
((n >= tries)) && return "$rc"
w "Command failed (attempt ${n}/${tries}): $*"
sleep 2
((n++))
done
}
au() {
local -n arr="$1"
local x="$2" y
[[ -n "$x" ]] || return 0
for y in "${arr[@]:-}"; do [[ "$y" == "$x" ]] && return 0; done
arr+=("$x")
}
eb() {
local rc line h=""
((MD)) || return 0
line='source /usr/share/modules/init/bash'
if [[ -n "${SUDO_USER:-}" && "${SUDO_USER}" != root ]]; then
h="$(getent passwd "$SUDO_USER" | cut -d: -f6 || true)"
fi
[[ -n "$h" ]] || h="${HOME:-/root}"
rc="${h}/.bashrc"
if [[ -f "$rc" ]] && grep -Fqx -- "$line" "$rc"; then
i "Bash init already configured for modules: $rc"
return 0
fi
if ((DR)); then
i "[dry-run] would append '$line' to $rc"
return 0
fi
touch "$rc"
printf '\n%s\n' "$line" >>"$rc"
if [[ -n "${SUDO_USER:-}" && "${SUDO_USER}" != root ]]; then
chown "$SUDO_USER":"$SUDO_USER" "$rc" 2>/dev/null || true
fi
ok "Added modules init to $rc"
}
pa() {
local -a metas=()
if ((DR)); then
i "[dry-run] would write driver-protection pin: $DP"
else
cat >"$DP" <<'EOF'
Package: libnvidia-* nvidia-* xserver-xorg-video-nvidia-*
Pin: release o=NVIDIA,l=NVIDIA CUDA
Pin-Priority: -1
EOF
fi
if [[ -f "$SF" ]] && compgen -G "/etc/apt/sources.list.d/cuda-*.list" >/dev/null; then
w "Removing legacy CUDA source list to avoid duplicate apt targets: $SF"
((DR)) || rm -f "$SF"
fi
mapfile -t metas < <(dpkg-query -W -f='${Package}\n' 2>/dev/null | grep -E -- '^cuda-[0-9]+-[0-9]+$' || true)
if ((${#metas[@]})); then
w "Found legacy CUDA meta-packages that may force driver changes: ${metas[*]}"
if ((DR)); then
i "[dry-run] would purge: ${metas[*]}"
else
apt-get -y purge "${metas[@]}" || true
fi
fi
if (( ! DR )) && dpkg --audit | grep -q .; then
die "dpkg reports broken packages. Run: apt-get -y --fix-broken install (driver repo pin is now in $DP)"
fi
}
oc() {
[[ -r /etc/os-release ]] || die "Cannot read /etc/os-release"
# shellcheck disable=SC1091
source /etc/os-release
[[ -n "${VERSION_ID:-}" ]] || die "VERSION_ID missing"
[[ "$(dpkg --print-architecture)" == "amd64" ]] || die "Only amd64/x86_64 is supported"
local major="${VERSION_ID%%.*}" uv_id="${UBUNTU_VERSION_ID:-}" uv_code="${UBUNTU_CODENAME:-}" up_rel="" up_code="" r
local -a cands=()
[[ "${ID:-}" == ubuntu ]] && au cands "ubuntu${VERSION_ID//./}"
[[ "${ID:-}" == debian ]] && au cands "debian${major}"
if [[ " ${ID_LIKE:-} ${ID:-} " == *" ubuntu "* ]]; then
[[ -n "$uv_id" ]] && au cands "ubuntu${uv_id//./}"
if [[ -r /etc/upstream-release/lsb-release ]]; then
up_rel="$(awk -F= '/^DISTRIB_RELEASE=/{gsub(/"/, "", $2); print $2; exit}' /etc/upstream-release/lsb-release || true)"
up_code="$(awk -F= '/^DISTRIB_CODENAME=/{gsub(/"/, "", $2); print $2; exit}' /etc/upstream-release/lsb-release || true)"
[[ -n "$up_rel" ]] && au cands "ubuntu${up_rel//./}"
fi
[[ -z "$uv_code" && -n "$up_code" ]] && uv_code="$up_code"
case "$uv_code" in noble) au cands ubuntu2404 ;; jammy) au cands ubuntu2204 ;; focal) au cands ubuntu2004 ;; bionic) au cands ubuntu1804 ;; esac
au cands ubuntu2404; au cands ubuntu2204; au cands ubuntu2004; au cands ubuntu1804
fi
if [[ " ${ID_LIKE:-} " == *" debian "* ]]; then
au cands "debian${major}"; au cands debian12; au cands debian11
fi
RB=""; RC=()
for r in "${cands[@]}"; do
if wget -q --spider "https://developer.download.nvidia.com/compute/cuda/repos/${r}/x86_64/cuda-${r}.pin"; then
RC+=("$r")
[[ -z "$RB" ]] && RB="$r"
fi
done
[[ -n "$RB" ]] || die "No supported NVIDIA CUDA repo found for ${ID:-unknown} ${VERSION_ID:-unknown}. Tried: ${cands[*]:-none}"
i "Detected distro ${ID:-unknown} ${VERSION_ID:-unknown}; candidate repos: ${RC[*]}"
}
vv() {
local v n; local -a out=()
((${#CV[@]})) || die "At least one CUDA version is required"
for v in "${CV[@]}"; do n="$(nv "$v")" || true; [[ -n "$n" ]] || die "Invalid CUDA version '$v'"; out+=("$n"); done
CV=("${out[@]}")
}
cvt() {
local v n found=0
vv
if [[ -z "$DF" ]]; then DF="${CV[0]}"; else DF="$(nv "$DF")" || true; [[ -n "$DF" ]] || die "Invalid --default format"; fi
for v in "${CV[@]}"; do [[ "$v" == "$DF" ]] && found=1 && break; done
((found)) || die "Default '$DF' is not in install list: ${CV[*]}"
}
svi() {
local list="$1"; shift; local -a pos=("$@")
[[ -n "$list" && ${#pos[@]} -gt 0 ]] && die "Use either --versions or positional versions, not both"
if [[ -n "$list" ]]; then list="${list//,/ }"; read -r -a CV <<<"$list"; return 0; fi
((${#pos[@]})) || return 1
CV=("${pos[@]}")
}
ps() {
local list=""
local act_set=0
local -a pos=()
if (($#)); then
case "$1" in
install|uninstall|use|list|help) X="$1"; act_set=1; shift ;;
-h|--help) us; exit 0 ;;
esac
fi
[[ "$X" == help ]] && { us; exit 0; }
while (($#)); do
case "$1" in
-h|--help) us; exit 0 ;;
-q|--quiet) QT=1; shift ;;
-n|--no-color) NC=1; setc; shift ;;
-r|--dry-run) DR=1; shift ;;
-f|--force) [[ "$X" == install ]] || die "Unknown option for this action: $1"; FC=1; shift ;;
-v|--versions) [[ "$X" == install || "$X" == uninstall ]] || die "Unknown option for this action: $1"; (($# > 1)) || die "Missing value for $1"; list="$2"; shift 2 ;;
--version) die "The use action takes VERSION positionally. Example: ${N} use 12.1" ;;
-d|--default) [[ "$X" == install ]] || die "Unknown option for this action: $1"; (($# > 1)) || die "Missing value for $1"; DF="$2"; shift 2 ;;
-m|--no-modules) [[ "$X" == install || "$X" == uninstall ]] || die "Unknown option for this action: $1"; MD=0; shift ;;
--) shift; while (($#)); do pos+=("$1"); shift; done ;;
-*) die "Unknown option: $1" ;;
*)
if (( ! act_set )); then
case "$1" in
install|uninstall|use|list|help) X="$1"; act_set=1; shift; continue ;;
esac
fi
pos+=("$1"); shift ;;
esac
done
case "$X" in
install)
if ! svi "$list" "${pos[@]}"; then
list="$(ac || true)"
if [[ -n "$list" ]]; then
CV=("$list")
[[ -z "$DF" ]] && DF="$list"
i "Auto-detected CUDA version from nvidia-smi: $list"
else
w "No version specified and CUDA auto-detection failed; using defaults: ${CV[*]}"
fi
fi
cvt ;;
uninstall)
svi "$list" "${pos[@]}" || die "Uninstall requires at least one CUDA version"
vv ;;
use)
[[ ${#pos[@]} -eq 1 ]] || die "The use action needs exactly one positional CUDA version"
UV="${pos[0]}"
UV="$(nv "$UV")" || true
[[ -n "$UV" ]] || die "Invalid CUDA version" ;;
list)
((${#pos[@]} == 0)) || die "The list action takes no positional arguments" ;;
*) die "Unknown action: $X" ;;
esac
}
rpk() {
local r="$1" b t
b="https://developer.download.nvidia.com/compute/cuda/repos/${r}/x86_64"
t="$(mktemp)"
if rt 2 wget -qO "$t" "$b/Packages.gz" && gzip -t "$t" >/dev/null 2>&1; then gzip -dc "$t"; rm -f "$t"; return 0; fi
rm -f "$t"
rt 2 wget -qO- "$b/Packages"
}
rhp() { local r="$1" p="$2"; rpk "$r" | grep -qm1 -E -- "^Package: ${p}$"; }
rha() { local p="$1" r; for r in "${RC[@]}"; do rhp "$r" "$p" && return 0; done; return 1; }
rkd() { local r="$1"; rpk "$r" | awk '/^Package: cuda-keyring$/{f=1} f&&/^Filename: /{print $2; exit}' | sed 's#^\./##'; }
rfm() {
local major="$1" r cand
for r in "${RC[@]}"; do
cand="$(rpk "$r" | awk -v M="$major" '
/^Package: cudnn[0-9]+-cuda-[0-9]+$/ {
pkg=$2
if (pkg ~ ("^cudnn[0-9]+-cuda-" M "$") ) {
gen=pkg
sub(/^cudnn/, "", gen)
sub(/-cuda-.*/, "", gen)
gen+=0
if (gen > best_gen) { best_gen=gen; best_pkg=pkg }
}
}
END { if (best_pkg != "") print best_pkg }
')"
[[ -n "$cand" ]] && { printf '%s' "$cand"; return 0; }
done
return 1
}
icm() {
dpkg-query -W -f='${Package}\n' 2>/dev/null \
| sed -nE 's/^cudnn[0-9]+-cuda-([0-9]+)(-[0-9]+)?$/\1/p' \
| sort -u | head -n1
}
pi() {
local pkg="$1" what="$2" sel="" r kdeb td
for r in "${RC[@]}"; do rhp "$r" "$pkg" && { sel="$r"; break; }; done
[[ -n "$sel" ]] || die "No package '$pkg' found. Tried repos: ${RC[*]}"
[[ "$sel" != "$RB" ]] && w "Using fallback repo $sel for ${what}"
i "Resolved package for ${what} (${sel}): ${pkg}"
((DR)) && { i "[dry-run] would install $pkg from repo $sel"; return 0; }
if [[ "$AR" != "$sel" ]]; then
rt 3 wget -qO "$PF" "https://developer.download.nvidia.com/compute/cuda/repos/${sel}/x86_64/cuda-${sel}.pin"
kdeb="$(rkd "$sel" || true)"; [[ -n "$kdeb" ]] || die "Unable to locate cuda-keyring package for repo $sel"
td="$(mktemp -d)"
rt 3 wget -qO "$td/cuda-keyring.deb" "https://developer.download.nvidia.com/compute/cuda/repos/${sel}/x86_64/${kdeb}"
dpkg -i "$td/cuda-keyring.deb"
rm -rf "$td"
rm -f "$SF"
if ! compgen -G "/etc/apt/sources.list.d/cuda-${sel}-*.list" >/dev/null; then
printf 'deb [signed-by=/usr/share/keyrings/cuda-archive-keyring.gpg] https://developer.download.nvidia.com/compute/cuda/repos/%s/x86_64/ /\n' "$sel" > "$SF"
fi
rt 3 apt-get update
AR="$sel"
fi
rt 3 apt-get -y install "$pkg"
}
i1() {
local v="$1" m s pkg
m="$(nv "$v")" || true
[[ -n "$m" ]] || { w "Skipping invalid version '$v'"; return; }
s="${m//./-}"; pkg="cuda-toolkit-${s}"
pi "$pkg" "CUDA $m"
}
u1() {
local v="$1" m s major p
local -a pkgs=() others=()
m="$(nv "$v")" || true
[[ -n "$m" ]] || { w "Skipping invalid version '$v'"; return; }
s="${m//./-}"
major="${m%%.*}"
mapfile -t pkgs < <(
dpkg-query -W -f='${Package}\n' 2>/dev/null \
| grep -E -- "^((cuda|cudnn|libcudnn|libcu|libnpp|libnv|gds|nsight)-.*-${s}(-.*)?|cuda-toolkit-${s}|cuda-${s})$" \
| sort -u || true
)
mapfile -t others < <(
dpkg-query -W -f='${Package}\n' 2>/dev/null \
| grep -E -- "^cuda-toolkit-${major}-[0-9]+$" \
| grep -Ev -- "^cuda-toolkit-${s}$" || true
)
if ((${#others[@]} == 0)); then
p="cuda-toolkit-${major}-config-common"
dpkg-query -W -f='${Status}' "$p" 2>/dev/null | grep -q -- '^install ok installed$' && au pkgs "$p"
fi
if ((${#pkgs[@]} == 0)); then
i "No installed CUDA packages found for ${m}"
return 0
fi
i "Removing CUDA ${m} packages: ${pkgs[*]}"
if ((DR)); then
i "[dry-run] would purge: ${pkgs[*]}"
else
rt 3 apt-get -y purge "${pkgs[@]}"
fi
}
ic() {
local m="$DF" major cur v vm pkg
local -a old_pkgs=()
local -a majors=()
m="$(nv "$m")" || true
[[ -n "$m" ]] || die "Unable to resolve cuDNN target from default CUDA version"
major="${m%%.*}"
for v in "${CV[@]}"; do
vm="$(nv "$v")" || true
[[ -n "$vm" ]] || continue
au majors "${vm%%.*}"
done
if ((${#majors[@]} > 1)); then
w "Multiple CUDA majors requested (${majors[*]}). cuDNN will target one major at a time via apt packages."
w "Using default CUDA ${major} as cuDNN target unless an existing cuDNN install is detected"
fi
cur="$(icm || true)"
if [[ -n "$cur" && "$cur" != "$major" ]]; then
if (( ! FC )); then
w "Detected installed cuDNN for CUDA ${cur}; skipping cuDNN switch to CUDA ${major} to avoid apt package removal churn"
w "Use -f/--force to switch cuDNN target major"
return 0
fi
mapfile -t old_pkgs < <(dpkg-query -W -f='${Package}\n' 2>/dev/null | grep -E -- "^(cudnn[0-9]+-cuda-${cur}(-[0-9]+)?|libcudnn[0-9]+(-[a-z0-9]+)*-cuda-${cur})$" || true)
if ((${#old_pkgs[@]})); then
w "Force mode: removing existing cuDNN packages for CUDA ${cur}: ${old_pkgs[*]}"
if ((DR)); then
i "[dry-run] would purge: ${old_pkgs[*]}"
else
rt 3 apt-get -y purge "${old_pkgs[@]}"
fi
else
w "Force mode enabled, but no installed cuDNN package set was found for CUDA ${cur}"
fi
fi
pkg="$(rfm "$major" || true)"
if [[ -n "$pkg" ]]; then
pi "$pkg" "cuDNN for CUDA ${major}"
return 0
fi
if rha "libcudnn8" && rha "libcudnn8-dev"; then
w "No versioned cuDNN meta found for CUDA ${major}; falling back to libcudnn8 packages"
pi "libcudnn8" "cuDNN runtime for CUDA ${m}"
pi "libcudnn8-dev" "cuDNN development files for CUDA ${m}"
return 0
fi
die "No supported cuDNN packages found for CUDA ${m} in repos: ${RC[*]}"
}
mt() {
local v="$1" p="$2" d='$'
cat >"$p" <<EOF
#%Module1.0
## cuda ${v} modulefile
proc ModulesHelp { } {
global version
puts stderr "\\tSets up environment for CUDA ${d}version\\n"
}
module-whatis "sets up environment for CUDA ${v}"
set version ${v}
set root /usr/local/cuda-${v}
setenv CUDA_HOME ${d}root
prepend-path PATH ${d}root/bin
prepend-path LD_LIBRARY_PATH ${d}root/extras/CUPTI/lib64
prepend-path LD_LIBRARY_PATH ${d}root/lib64
conflict cuda
EOF
}
cm() {
local d="/usr/share/modules/modulefiles/cuda" v
((DR)) && { i "[dry-run] would install environment-modules and write modulefiles in $d"; return 0; }
rt 3 apt-get -y install environment-modules
mkdir -p "$d"
for v in "${CV[@]}"; do mt "$v" "$d/$v"; done
cat >"$d/.version" <<EOF
#%Module
set ModulesVersion ${DF}
EOF
}
rmv() {
local d="/usr/share/modules/modulefiles/cuda" v p dv=""
local -a rem=()
((MD)) || { w "Skipping environment-modules cleanup (--no-modules)"; return 0; }
if ((DR)); then
for v in "${CV[@]}"; do i "[dry-run] would remove modulefile: ${d}/${v}"; done
return 0
fi
[[ -d "$d" ]] || return 0
for v in "${CV[@]}"; do rm -f "${d}/${v}"; done
shopt -s nullglob
for p in "$d"/*; do
[[ -f "$p" ]] || continue
[[ "${p##*/}" == ".version" ]] && continue
v="$(nv "${p##*/}")" || true
[[ -n "$v" ]] && au rem "$v"
done
shopt -u nullglob
if ((${#rem[@]})); then
dv="$(printf '%s\n' "${rem[@]}" | sort -V | tail -n1)"
cat >"$d/.version" <<EOF
#%Module
set ModulesVersion ${dv}
EOF
else
rm -f "$d/.version"
rmdir "$d" 2>/dev/null || true
fi
}
ap() {
local n="$1" x="$2" c
c="${!n-}"
case ":$c:" in *":$x:"*) return ;; esac
[[ -n "$c" ]] && printf -v "$n" '%s:%s' "$x" "$c" || printf -v "$n" '%s' "$x"
export "${n?}"
}
cpd() {
local r="$1" p
for p in "$r/extras/CUPTI/lib64" "$r/targets/x86_64-linux/lib" "$r/lib64"; do
[[ -d "$p" && -e "$p/libcupti.so" ]] && { printf '%s' "$p"; return 0; }
done
for p in "$r/extras/CUPTI/lib64" "$r/targets/x86_64-linux/lib" "$r/lib64"; do
[[ -d "$p" ]] && { printf '%s' "$p"; return 0; }
done
return 1
}
ue() {
local r="/usr/local/cuda-${UV}" cupti=""
cupti="$(cpd "$r" || true)"
if ((DR)); then
if (( ! QT )); then
local ld="$r/lib64"
[[ -n "$cupti" ]] && ld="$cupti:$ld"
cat <<EOF
export CUDA_HOME=$r
export PATH=$r/bin:\${PATH}
export LD_LIBRARY_PATH=$ld:\${LD_LIBRARY_PATH:-}
EOF
fi
ok "Dry-run complete for CUDA ${UV} env"
return 0
fi
[[ -d "$r" ]] || die "CUDA ${UV} is not installed at $r"
[[ -d "$r/bin" ]] || die "Missing $r/bin"
[[ -d "$r/lib64" ]] || die "Missing $r/lib64"
[[ "${BASH_SOURCE[0]}" != "$0" ]] || die "'use' must be sourced. Run: source ${BASH_SOURCE[0]} use ${UV}"
export CUDA_HOME="$r"
ap PATH "$r/bin"
if [[ -n "$cupti" ]]; then
ap LD_LIBRARY_PATH "$cupti"
else
w "CUPTI path not found under $r (checked extras/CUPTI/lib64 and targets/x86_64-linux/lib)"
fi
ap LD_LIBRARY_PATH "$r/lib64"
ok "CUDA ${UV} environment applied"
}
di() {
((DR)) || [[ ${EUID} -eq 0 ]] || die "Run as root (or use --dry-run)."
pa
oc
i "CUDA versions: ${CV[*]} (default: $DF)"
if ((DR)); then
i "Dry-run enabled: validating repo and package availability only"
i "Pin URL: https://developer.download.nvidia.com/compute/cuda/repos/${RB}/x86_64/cuda-${RB}.pin"
else
export DEBIAN_FRONTEND=noninteractive
rt 3 apt-get update
rt 3 apt-get -y install wget ca-certificates gzip sed
fi
local v
for v in "${CV[@]}"; do i1 "$v"; done
i "cuDNN is required and will be installed for default CUDA ${DF}"
ic
if ((MD)); then
((DR)) && i "[dry-run] would configure environment-modules"
((DR)) || cm
eb
else
w "Skipping module setup (--no-modules)"
fi
if ((DR)); then ok "Dry-run completed"; return 0; fi
ok "CUDA installation complete for versions: ${CV[*]}"
((QT)) && return 0
cat <<EOF
Next steps:
1. Open a new shell.
2. If 'module' is missing, run: source /usr/share/modules/init/bash
3. Check modules: module avail cuda
4. Load a version: module load cuda/${DF}
5. Verify: nvcc --version
Note: cuDNN is installed automatically for the default CUDA version.
EOF
}
du() {
((DR)) || [[ ${EUID} -eq 0 ]] || die "Run as root (or use --dry-run)."
export DEBIAN_FRONTEND=noninteractive
i "CUDA versions to uninstall: ${CV[*]}"
local v
for v in "${CV[@]}"; do u1 "$v"; done
rmv
ok "CUDA uninstall complete for versions: ${CV[*]}"
((QT)) && return 0
cat <<EOF
Optional cleanup:
1. Remove orphaned dependencies: sudo apt autoremove
2. Check remaining versions: ${N} list
EOF
}
dl() {
local p v link tgt
local -a found=()
shopt -s nullglob
for p in /usr/local/cuda-*; do
[[ -d "$p" ]] || continue
v="$(nv "${p##*/cuda-}")" || true
[[ -n "$v" ]] && au found "$v"
done
for p in /usr/share/modules/modulefiles/cuda/*; do
[[ -f "$p" ]] || continue
[[ "${p##*/}" == ".version" ]] && continue
v="$(nv "${p##*/}")" || true
[[ -n "$v" ]] && au found "$v"
done
shopt -u nullglob
if ((${#found[@]} == 0)); then
i "Installed CUDA versions: none found"
else
i "Installed CUDA versions:"
while IFS= read -r v; do i " - ${v}"; done < <(printf '%s\n' "${found[@]}" | sort -V)
fi
if [[ -L /usr/local/cuda ]]; then
link="$(readlink /usr/local/cuda || true)"
tgt="$(readlink -f /usr/local/cuda || true)"
v=""
if [[ "$tgt" == /usr/local/cuda-* ]]; then
v="$(nv "${tgt##*/cuda-}")" || true
fi
if [[ -n "$v" ]]; then
i "Default /usr/local/cuda -> ${link} (CUDA ${v})"
else
i "Default /usr/local/cuda -> ${link}"
fi
else
w "Default /usr/local/cuda symlink not found"
fi
}
mn() { ps "$@"; case "$X" in install) di ;; uninstall) du ;; use) ue ;; list) dl ;; *) us; die "Unsupported action: $X" ;; esac; }
zz() {
unset -f setc cln log i w e ok die nv ac us rt au eb pa oc vv cvt svi ps rpk rhp rha rkd rfm icm pi i1 u1 ic mt cm rmv ap cpd ue di du dl mn zz 2>/dev/null || true
unset N X CV DF MD DR QT NC FC UV RB RC PF DP SF AR ZR ZI ZW ZE ZK _S _E _U _P 2>/dev/null || true
}
if mn "$@"; then _R=0; else _R=$?; fi
if ((_S)); then
((_E)) || set +e
((_U)) || set +u
((_P)) || set +o pipefail
zz
return "$_R"
fi
exit "$_R"
@loopyd
Copy link
Copy Markdown
Author

loopyd commented Apr 9, 2026

(reserved)

I was annoyed for lack of CUDA version selector and how heavy python venv could get with CUDA binary dependencies.... so I made single bash file CUDA version manager for myself. Here you go!

It works like similar version manager with:

  • install to install a target CUDA/matching CuDNN major version
  • use to make the CUDA/CuDNN version active using environment-modules with config that the script injects for you.
  • uninstall to remove a target CUDA/matching CuDNN major version.

This requires working NVIDIA drivers already installed with nvidia-smi working, I don't automate that for you. This will pin drivers so that the installer avoids touching them in preflight.

If you don't specify a CUDA version to install/uninstall, you get the one nvidia-smi presents and its compatible CuDNN counterpart. This is a fast and convenient way to do that at your os bootstrap if you like.

Verbosity and testing options:

You have -r | --dry-run switch to test before trying for any errors, -n | --no-color to go without color on terminals that don't support it, and -q | --quiet for quiet mode to suppress output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment