diff --git a/CHANGELOG.md b/CHANGELOG.md index 75c7d45..8a29218 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add `nvidia-ml-py` 13.595.45 to support list. +- Add support for open kernel-module driver packages (e.g., `nvidia-driver-595-open`) in `install-nvidia-driver.sh` with new `--proprietary` and `--open` flags by [@XuehaiPan](https://github.com/XuehaiPan). ### Changed diff --git a/README.md b/README.md index 59d7b0e..dd04997 100644 --- a/README.md +++ b/README.md @@ -128,8 +128,9 @@ git clone --depth=1 https://github.com/XuehaiPan/nvitop.git && cd nvitop # Optional for SSH users sudo chvt 3 # or use keyboard shortcut: Ctrl-LeftAlt-F3 -bash install-nvidia-driver.sh --package=nvidia-driver-470 # install the R470 driver from ppa:graphics-drivers +bash install-nvidia-driver.sh --package=nvidia-driver-595 # install the R595 driver from ppa:graphics-drivers bash install-nvidia-driver.sh --latest # install the latest driver from ppa:graphics-drivers +bash install-nvidia-driver.sh --latest --open # install the latest open-kernel-module driver ```

diff --git a/install-nvidia-driver.sh b/install-nvidia-driver.sh index 85ff182..ba83f68 100755 --- a/install-nvidia-driver.sh +++ b/install-nvidia-driver.sh @@ -1,14 +1,17 @@ #!/bin/bash # ============================================================================== # -# Usage: bash install-nvidia-driver.sh [--package=PKG] [--upgrade-only] [--latest] [--dry-run] [--yes] [--help] +# Usage: bash install-nvidia-driver.sh [--package=PKG] [--upgrade-only] [--latest] +# [--proprietary | --open] [--dry-run] [--yes] [--help] # # Examples: # # bash install-nvidia-driver.sh -# bash install-nvidia-driver.sh --package=nvidia-driver-470 +# bash install-nvidia-driver.sh --package=nvidia-driver-595 +# bash install-nvidia-driver.sh --package=nvidia-driver-595-open # bash install-nvidia-driver.sh --upgrade-only # bash install-nvidia-driver.sh --latest +# bash install-nvidia-driver.sh --latest --open # # ============================================================================== # This file is part of nvitop, the interactive NVIDIA-GPU process viewer. @@ -28,7 +31,7 @@ # limitations under the License. # ============================================================================== -# shellcheck disable=SC2016,SC2312 +# shellcheck disable=SC2016,SC2310,SC2312 set -u set -e @@ -66,16 +69,22 @@ tty_green="$(tty_mkbold 32)" tty_yellow="$(tty_mkbold 33)" tty_white="$(tty_mkbold 37)" tty_bold="$(tty_mkbold 39)" +tty_dim="$(tty_escape 90)" tty_reset="$(tty_escape 0)" function usage() { cat < 595 + # nvidia-driver-595-open -> 595 + local d="${1#nvidia-driver-}" + echo "${d%-open}" +} + +function driver-flavor() { + # nvidia-driver-595 -> proprietary + # nvidia-driver-595-open -> open + if [[ "$1" == *-open ]]; then + echo 'open' + else + echo 'proprietary' + fi +} + +function toggle-open-suffix() { + # nvidia-driver-595 -> nvidia-driver-595-open + # nvidia-driver-595-open -> nvidia-driver-595 + if [[ "$1" == *-open ]]; then + echo "${1%-open}" + else + echo "$1-open" + fi +} + +function driver-available() { + # Predicate: is "$1" present in the global AVAILABLE_DRIVERS array? + local d + for d in "${AVAILABLE_DRIVERS[@]}"; do + if [[ "${d}" == "$1" ]]; then + return 0 + fi + done + return 1 +} + function apt-candidate-version() { apt-cache policy "$1" | grep -F 'Candidate' | awk '{ print $2 }' } @@ -339,7 +407,7 @@ fi # shellcheck disable=SC2207 AVAILABLE_DRIVERS=($( apt-cache search --names-only nvidia-driver | - awk '$1 ~ /^nvidia-driver-([0-9]+)$/ { print $1 }' | + awk '$1 ~ /^nvidia-driver-([0-9]+)(-open)?$/ { print $1 }' | sort -V )) @@ -347,22 +415,80 @@ if [[ "${#AVAILABLE_DRIVERS[@]}" -eq 0 ]]; then abort "No available drivers found from APT." fi -LATEST_DRIVER="${AVAILABLE_DRIVERS[-1]}" -LATEST_DRIVER_VERSION="$(apt-candidate-version "${LATEST_DRIVER}")" +# `sort -V` orders `nvidia-driver-595` before `nvidia-driver-595-open`, so picking +# `${AVAILABLE_DRIVERS[-1]}` would silently flip the default to open. Pick per-flavor instead. +LATEST_PROPRIETARY_DRIVER='' +LATEST_OPEN_DRIVER='' +for driver in "${AVAILABLE_DRIVERS[@]}"; do + if [[ "$(driver-flavor "${driver}")" == 'open' ]]; then + LATEST_OPEN_DRIVER="${driver}" + else + LATEST_PROPRIETARY_DRIVER="${driver}" + fi +done -INSTALLED_DRIVER="$(apt-list-packages | awk '$2 ~ /nvidia-driver-([0-9]+)$/ { print $2 }')" +INSTALLED_DRIVER="$(apt-list-packages | awk '$2 ~ /nvidia-driver-([0-9]+)(-open)?$/ { print $2 }')" if [[ -n "${INSTALLED_DRIVER}" ]]; then INSTALLED_DRIVER_VERSION="$(apt-installed-version "${INSTALLED_DRIVER}")" INSTALLED_DRIVER_CANDIDATE_VERSION="$(apt-candidate-version "${INSTALLED_DRIVER}")" + INSTALLED_FLAVOR="$(driver-flavor "${INSTALLED_DRIVER}")" else INSTALLED_DRIVER_VERSION='' INSTALLED_DRIVER_CANDIDATE_VERSION='' + INSTALLED_FLAVOR='' fi +# Effective flavor for picking LATEST_DRIVER: explicit `--proprietary`/`--open` wins; otherwise +# preserve the installed driver's flavor; fresh hosts fall through to the `FLAVOR` default. +EFFECTIVE_FLAVOR="${FLAVOR}" +if [[ -z "${FLAVOR_EXPLICIT}" && -n "${INSTALLED_FLAVOR}" ]]; then + EFFECTIVE_FLAVOR="${INSTALLED_FLAVOR}" +fi + +if [[ "${EFFECTIVE_FLAVOR}" == 'open' ]]; then + LATEST_DRIVER="${LATEST_OPEN_DRIVER}" + LATEST_FALLBACK="${LATEST_PROPRIETARY_DRIVER}" +else + LATEST_DRIVER="${LATEST_PROPRIETARY_DRIVER}" + LATEST_FALLBACK="${LATEST_OPEN_DRIVER}" +fi +# Resolution-time fallback only matters when the script needs LATEST_DRIVER to compute +# REQUESTED_DRIVER. An explicit `--package=PKG` is the user's authoritative override and is +# allowed through even when the preserved flavor has no APT candidates. +if [[ -z "${LATEST_DRIVER}" && -z "${REQUESTED_DRIVER}" ]]; then + if [[ -n "${FLAVOR_EXPLICIT}" ]]; then + abort "No \`${EFFECTIVE_FLAVOR}\` driver candidates available from APT. Drop \`--${FLAVOR}\`, run \`sudo apt-get update\` to refresh the package cache, or pass \`--package=PKG\` to override." + fi + if [[ -n "${INSTALLED_DRIVER}" ]]; then + # Never silently switch flavor on an installed system: falling back to the opposite + # kernel-module flavor would be a root-level migration the user did not ask for. + if [[ "${EFFECTIVE_FLAVOR}" == 'open' ]]; then + opposite_flavor='proprietary' + else + opposite_flavor='open' + fi + abort "No \`${EFFECTIVE_FLAVOR}\` driver candidates available from APT to upgrade \`${INSTALLED_DRIVER}\`. Run \`sudo apt-get update\` to refresh the cache, pass \`--${opposite_flavor}\` to switch flavors explicitly, or pass \`--package=PKG\` to override." + fi + # Fresh-install fallback: pick whichever flavor the PPA publishes rather than refusing. + warn "No \`${EFFECTIVE_FLAVOR}\` driver candidates available; falling back to \`${LATEST_FALLBACK}\` ($(driver-flavor "${LATEST_FALLBACK}") flavor)." + LATEST_DRIVER="${LATEST_FALLBACK}" +fi +LATEST_DRIVER_VERSION="$(apt-candidate-version "${LATEST_DRIVER}")" + if [[ -z "${REQUESTED_DRIVER}" ]]; then if [[ -n "${LATEST}" || -z "${INSTALLED_DRIVER}" ]]; then REQUESTED_DRIVER="${LATEST_DRIVER}" REQUESTED_DRIVER_VERSION="${LATEST_DRIVER_VERSION}" + elif [[ -n "${FLAVOR_EXPLICIT}" && "${INSTALLED_FLAVOR}" != "${FLAVOR}" ]]; then + # Standalone `--open` / `--proprietary` (no `--latest`, no `--package`) on an + # opposite-flavor install: switch to the same-major counterpart, e.g. + # `--open` on `nvidia-driver-595` -> `nvidia-driver-595-open`. + counterpart="$(toggle-open-suffix "${INSTALLED_DRIVER}")" + if ! driver-available "${counterpart}"; then + abort "Cannot switch \`${INSTALLED_DRIVER}\` to ${FLAVOR} flavor: \`${counterpart}\` is not available from APT. Pass \`--latest\` to switch to the latest ${FLAVOR} branch instead." + fi + REQUESTED_DRIVER="${counterpart}" + REQUESTED_DRIVER_VERSION="$(apt-candidate-version "${REQUESTED_DRIVER}")" else REQUESTED_DRIVER="${INSTALLED_DRIVER}" REQUESTED_DRIVER_VERSION="${INSTALLED_DRIVER_CANDIDATE_VERSION}" @@ -372,14 +498,52 @@ else if [[ -z "${REQUESTED_DRIVER_VERSION}" ]]; then abort "Unable to locate package ${REQUESTED_DRIVER}." fi + requested_flavor="$(driver-flavor "${REQUESTED_DRIVER}")" + if [[ -n "${FLAVOR_EXPLICIT}" && "${requested_flavor}" != "${FLAVOR}" ]]; then + suggested="$(toggle-open-suffix "${REQUESTED_DRIVER}")" + conflict_msg="Option \`--${FLAVOR}\` conflicts with package \`${REQUESTED_DRIVER}\` (${requested_flavor} flavor)." + if driver-available "${suggested}"; then + conflict_msg+=" Did you mean \`${suggested}\`?" + fi + abort "${conflict_msg}" + fi +fi + +# `--upgrade-only` promises to keep the installed driver package, so reject any combination that +# would change the package name (including a flavor flag that rewrote `REQUESTED_DRIVER` to the +# counterpart, or an explicit `--package=` pointing at a different driver). Without this guard the +# later install path would purge the existing NVIDIA stack and reinstall the new package — a +# root-level system change masquerading as an upgrade. +if [[ -n "${UPGRADE_ONLY}" && -n "${INSTALLED_DRIVER}" && "${INSTALLED_DRIVER}" != "${REQUESTED_DRIVER}" ]]; then + abort "Option \`--upgrade-only\` cannot change the driver package from \`${INSTALLED_DRIVER}\` to \`${REQUESTED_DRIVER}\`. Drop \`--upgrade-only\` to switch packages." fi ohai "Available NVIDIA drivers:" for driver in "${AVAILABLE_DRIVERS[@]}"; do + flavor="$(driver-flavor "${driver}")" prefix=" " if [[ "${driver}" == "${REQUESTED_DRIVER}" ]]; then prefix="--> " fi + + # Each flavor's branch tip gets a flavor-explicit annotation so "latest" cannot be confused + # with the user's effective `--latest` target under flavor preservation. + suffix='' + if [[ "${driver}" == "${LATEST_PROPRIETARY_DRIVER}" ]]; then + suffix=' (latest proprietary)' + elif [[ "${driver}" == "${LATEST_OPEN_DRIVER}" ]]; then + suffix=' (latest open)' + fi + + # Dim other-flavor rows: picking one would require a purge+reinstall flavor switch. The + # REQUESTED_DRIVER row is exempt so the `--> ` arrow keeps priority on a deliberate switch. + dim='' + if [[ -n "${INSTALLED_FLAVOR}" && + "${flavor}" != "${INSTALLED_FLAVOR}" && + "${driver}" != "${REQUESTED_DRIVER}" ]]; then + dim=1 + fi + if [[ "${driver}" == "${INSTALLED_DRIVER}" ]]; then if [[ "${driver}" != "${REQUESTED_DRIVER}" ]]; then prefix="<-- " @@ -397,10 +561,12 @@ for driver in "${AVAILABLE_DRIVERS[@]}"; do else echo "${prefix}${tty_bold}${driver} [${INSTALLED_DRIVER_VERSION}]${tty_reset} ${tty_yellow}[installed]${tty_reset} (upgradable to [${INSTALLED_DRIVER_CANDIDATE_VERSION}])" fi + elif [[ -n "${dim}" ]]; then + echo "${prefix}${tty_dim}${driver} [$(apt-candidate-version "${driver}")]${suffix}${tty_reset}" elif [[ "${driver}" == "${LATEST_DRIVER}" ]]; then - echo "${prefix}${tty_green}${driver} [${LATEST_DRIVER_VERSION}]${tty_reset} (latest)" + echo "${prefix}${tty_green}${driver} [${LATEST_DRIVER_VERSION}]${tty_reset}${suffix}" else - echo "${prefix}${driver} [$(apt-candidate-version "${driver}")]" + echo "${prefix}${driver} [$(apt-candidate-version "${driver}")]${suffix}" fi done @@ -422,12 +588,24 @@ echo if [[ -z "${INSTALLED_DRIVER}" ]]; then ohai "Install the NVIDIA driver ${REQUESTED_DRIVER} [${REQUESTED_DRIVER_VERSION}]." -elif [[ "${REQUESTED_DRIVER#nvidia-driver-}" -ge "${INSTALLED_DRIVER#nvidia-driver-}" ]]; then +elif [[ "$(driver-major "${REQUESTED_DRIVER}")" -ge "$(driver-major "${INSTALLED_DRIVER}")" ]]; then ohai "Upgrade the NVIDIA driver from ${INSTALLED_DRIVER} [${INSTALLED_DRIVER_VERSION}] to ${REQUESTED_DRIVER} [${REQUESTED_DRIVER_VERSION}]." else ohai "Downgrade the NVIDIA driver from ${INSTALLED_DRIVER} [${INSTALLED_DRIVER_VERSION}] to ${REQUESTED_DRIVER} [${REQUESTED_DRIVER_VERSION}]." fi +REQUESTED_FLAVOR="$(driver-flavor "${REQUESTED_DRIVER}")" +if [[ -n "${INSTALLED_FLAVOR}" && "${INSTALLED_FLAVOR}" != "${REQUESTED_FLAVOR}" ]]; then + ohai "Switching kernel-module flavor from ${INSTALLED_FLAVOR} to ${REQUESTED_FLAVOR}." +fi + +# Preflight the install plan before any teardown so an unsatisfiable APT transaction aborts +# while the existing driver is still working. `--allow-change-held-packages` lets the simulate +# plan past the existing apt-mark holds (the real `apt-mark unhold` runs in the teardown block). +if [[ -n "${INSTALLED_DRIVER}" && "${INSTALLED_DRIVER}" != "${REQUESTED_DRIVER}" ]]; then + exec_cmd "sudo apt-get install --simulate --allow-change-held-packages --yes ${REQUESTED_DRIVER}" +fi + DM_SERVICES=() if [[ -n "$(sudo lsof -t /dev/nvidia* 2>/dev/null || true)" ]]; then for dm in gdm3 lightdm; do @@ -504,7 +682,8 @@ if [[ -n "${INSTALLED_DRIVER}" ]]; then # Upgrade the existing driver packages exec_cmd "sudo apt-get install --only-upgrade --yes ${NVIDIA_PACKAGES}" else - # Uninstall the existing driver packages + # The install plan was already validated above with `apt-get install --simulate` (before + # any destructive teardown), so this purge is safe to run. exec_cmd "sudo apt-get purge --yes ${NVIDIA_PACKAGES}" fi fi