diff --git a/scripts/install.sh b/scripts/install.sh index 9c232400f..9f5adf359 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -2,104 +2,118 @@ # This script installs Ollama on Linux. # It detects the current operating system architecture and installs the appropriate version of Ollama. -set -eu +NVIDIA_REPOS_URL='https://developer.download.nvidia.com/compute/cuda/repos' -red="$( (/usr/bin/tput bold || :; /usr/bin/tput setaf 1 || :) 2>&-)" -plain="$( (/usr/bin/tput sgr0 || :) 2>&-)" +available() { command -v "$1" >/dev/null; } -status() { echo ">>> $*" >&2; } -error() { echo "${red}ERROR:${plain} $*"; exit 1; } -warning() { echo "${red}WARNING:${plain} $*"; } +red="$({ + /usr/bin/tput bold || : + /usr/bin/tput setaf 1 || : +} 2>&-)" +plain="$({ /usr/bin/tput sgr0 || :; } 2>&-)" +yellow="$({ + /usr/bin/tput bold || : + /usr/bin/tput setaf 11 || : +} 2>&-)" -TEMP_DIR=$(mktemp -d) -cleanup() { rm -rf $TEMP_DIR; } -trap cleanup EXIT - -available() { command -v $1 >/dev/null; } -require() { - local MISSING='' - for TOOL in $*; do - if ! available $TOOL; then - MISSING="$MISSING $TOOL" - fi - done - - echo $MISSING +status() { printf '>>> %s \n' "$*" >&2; } +error() { + printf '%sERROR:%s %s\n' "$red" "$*" "$plain" >&2 + exit 1 } +warning() { printf '%sWARNING:%s %s\n' "$yellow" "$*" "${plain}" >&2; } [ "$(uname -s)" = "Linux" ] || error 'This script is intended to run on Linux only.' -ARCH=$(uname -m) -case "$ARCH" in - x86_64) ARCH="amd64" ;; - aarch64|arm64) ARCH="arm64" ;; - *) error "Unsupported architecture: $ARCH" ;; +arch=$(uname -m) +case "$arch" in +x86_64) arch="amd64" ;; +aarch64 | arm64) arch="arm64" ;; +*) error "Unsupported architecture: $arch" ;; esac -IS_WSL2=false - -KERN=$(uname -r) -case "$KERN" in - *icrosoft*WSL2 | *icrosoft*wsl2) IS_WSL2=true;; - *icrosoft) error "Microsoft WSL1 is not currently supported. Please use WSL2 with 'wsl --set-version 2'" ;; - *) ;; +case "$(uname -r)" in +*icrosoft*WSL2 | *icrosoft*wsl2) is_wsl2() { :; } ;; +*icrosoft) error "Microsoft WSL1 is not currently supported. Please use WSL2 with 'wsl --set-version 2'" ;; +*) is_wsl2() { false; } ;; esac -VER_PARAM="${OLLAMA_VERSION:+?version=$OLLAMA_VERSION}" - -SUDO= -if [ "$(id -u)" -ne 0 ]; then - # Running as root, no need for sudo - if ! available sudo; then - error "This script requires superuser permissions. Please re-run as root." - fi - - SUDO="sudo" -fi - -NEEDS=$(require curl awk grep sed tee xargs) -if [ -n "$NEEDS" ]; then - status "ERROR: The following tools are required but missing:" - for NEED in $NEEDS; do - echo " - $NEED" +require() { + rc=0 + for tool; do + if ! available "$tool"; then + rc=1 + printf %s\\n "$tool" + fi done - exit 1 + return $rc +} + +ver_param="${OLLAMA_VERSION:+version=$OLLAMA_VERSION}" + +if [ "$(id -u)" -ne 0 ]; then + if ! available sudo; then + error 'This script requires superuser permissions. Please re-run as root.' + fi +else + if ! available sudo; then + # Dummy sudo if not available + sudo() { + while [ "${1#-}" != "$1" ]; do shift; done + "$@" + } + fi fi -for BINDIR in /usr/local/bin /usr/bin /bin; do - echo $PATH | grep -q $BINDIR && break || continue -done -OLLAMA_INSTALL_DIR=$(dirname ${BINDIR}) - -if [ -d "$OLLAMA_INSTALL_DIR/lib/ollama" ] ; then - status "Cleaning up old version at $OLLAMA_INSTALL_DIR/lib/ollama" - $SUDO rm -rf "$OLLAMA_INSTALL_DIR/lib/ollama" +if ! needs=$(require curl awk grep sed tee xargs); then + error "$(printf 'The following tools are required but missing:\n%s\n' "$needs")" fi -status "Installing ollama to $OLLAMA_INSTALL_DIR" -$SUDO install -o0 -g0 -m755 -d $BINDIR -$SUDO install -o0 -g0 -m755 -d "$OLLAMA_INSTALL_DIR/lib/ollama" -status "Downloading Linux ${ARCH} bundle" + +for bin_dir in /usr/local/bin /usr/bin /bin; do + case :$PATH: in + *":$bin_dir:"* | *":$bin_dir/:"*) break ;; + esac + false +done || error 'Cannot determine installation directory' +ollama_install_dir=${bin_dir%/*} + +lib_ollama=$ollama_install_dir/lib/ollama + +if [ -d "$lib_ollama" ]; then + status "Cleaning up old version at $lib_ollama" + sudo rm -rf -- "$lib_ollama" +fi + +status "Installing ollama to $ollama_install_dir" +sudo install -o0 -g0 -m755 -d "$bin_dir" "$lib_ollama" +status "Downloading Linux ${arch} bundle" + +url_arch=$(url_encode "$arch") + curl --fail --show-error --location --progress-bar \ - "https://ollama.com/download/ollama-linux-${ARCH}.tgz${VER_PARAM}" | \ - $SUDO tar -xzf - -C "$OLLAMA_INSTALL_DIR" + --get --data-urlencode "$ver_param" \ + "https://ollama.com/download/ollama-linux-${url_arch}.tgz" | + sudo tar -xzf - -C "$ollama_install_dir" -if [ "$OLLAMA_INSTALL_DIR/bin/ollama" != "$BINDIR/ollama" ] ; then - status "Making ollama accessible in the PATH in $BINDIR" - $SUDO ln -sf "$OLLAMA_INSTALL_DIR/ollama" "$BINDIR/ollama" +if [ "$ollama_install_dir/bin/ollama" != "$bin_dir/ollama" ]; then + status "Making ollama accessible in the PATH in $bin_dir" + sudo ln -sf "$ollama_install_dir/ollama" "$bin_dir/ollama" fi # Check for NVIDIA JetPack systems with additional downloads -if [ -f /etc/nv_tegra_release ] ; then - if grep R36 /etc/nv_tegra_release > /dev/null ; then +if [ -f /etc/nv_tegra_release ]; then + if grep R36 /etc/nv_tegra_release >/dev/null; then status "Downloading JetPack 6 components" curl --fail --show-error --location --progress-bar \ - "https://ollama.com/download/ollama-linux-${ARCH}-jetpack6.tgz${VER_PARAM}" | \ - $SUDO tar -xzf - -C "$OLLAMA_INSTALL_DIR" - elif grep R35 /etc/nv_tegra_release > /dev/null ; then + --get --data-urlencode "$ver_param" \ + "https://ollama.com/download/ollama-linux-${url_arch}-jetpack6.tgz" | + sudo tar -xzf - -C "$ollama_install_dir" + elif grep R35 /etc/nv_tegra_release >/dev/null; then status "Downloading JetPack 5 components" curl --fail --show-error --location --progress-bar \ - "https://ollama.com/download/ollama-linux-${ARCH}-jetpack5.tgz${VER_PARAM}" | \ - $SUDO tar -xzf - -C "$OLLAMA_INSTALL_DIR" + --get --data-urlencode "$ver_param" \ + "https://ollama.com/download/ollama-linux-${url_arch}-jetpack5.tgz" | + sudo tar -xzf - -C "$ollama_install_dir" else warning "Unsupported JetPack version detected. GPU may not be supported" fi @@ -109,35 +123,37 @@ install_success() { status 'The Ollama API is now available at 127.0.0.1:11434.' status 'Install complete. Run "ollama" from the command line.' } -trap install_success EXIT +trap install_success EXIT INT TERM # Everything from this point onwards is optional. configure_systemd() { if ! id ollama >/dev/null 2>&1; then status "Creating ollama user..." - $SUDO useradd -r -s /bin/false -U -m -d /usr/share/ollama ollama + sudo useradd -r -s /bin/false -U -m -d /usr/share/ollama ollama fi if getent group render >/dev/null 2>&1; then status "Adding ollama user to render group..." - $SUDO usermod -a -G render ollama + sudo usermod -a -G render ollama fi if getent group video >/dev/null 2>&1; then status "Adding ollama user to video group..." - $SUDO usermod -a -G video ollama + sudo usermod -a -G video ollama fi - status "Adding current user to ollama group..." - $SUDO usermod -a -G ollama $(whoami) + if [ "$(id -u)" != 0 ]; then + status "Adding current user to ollama group..." + sudo usermod -a -G ollama "$(id -un)" + fi status "Creating ollama systemd service..." - cat </dev/null + sudo tee /etc/systemd/system/ollama.service >/dev/null </dev/null ; then - $SUDO $PACKAGE_MANAGER-config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m | sed -e 's/aarch64/sbsa/')/cuda-$1$2.repo - else - error $CUDA_REPO_ERR_MSG - fi - ;; - dnf) - if curl -I --silent --fail --location "https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m | sed -e 's/aarch64/sbsa/')/cuda-$1$2.repo" >/dev/null ; then - $SUDO $PACKAGE_MANAGER config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m | sed -e 's/aarch64/sbsa/')/cuda-$1$2.repo - else - error $CUDA_REPO_ERR_MSG - fi - ;; + + cuda_repo_url="${NVIDIA_REPOS_URL}/${u_os_name}${u_os_version}/${u_cuda_arch}/cuda-${u_os_name}${u_os_version.repo}" + + case "$package_manager" in yum) + sudo "$package_manager" -y install yum-utils + ;; + esac + case "$package_manager" in yum | dnf) + curl -I --silent --fail --location "$cuda_repo_url" >/dev/null || error "$cuda_repo_err_message" + sudo "$package_manager" config-manager --add-repo "$cuda_repo_url" + ;; esac - case $1 in - rhel) - status 'Installing EPEL repository...' - # EPEL is required for third-party dependencies such as dkms and libvdpau - $SUDO $PACKAGE_MANAGER -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-$2.noarch.rpm || true - ;; + case "$1" in + rhel) + status 'Installing EPEL repository...' + # EPEL is required for third-party dependencies such as dkms and libvdpau + epel_url="https://dl.fedoraproject.org/pub/epel/epel-release-latest-$u_os_version.noarch.rpm" + sudo "$package_manager" -y install "$epel_url" || true + ;; esac status 'Installing CUDA driver...' if [ "$1" = 'centos' ] || [ "$1$2" = 'rhel7' ]; then - $SUDO $PACKAGE_MANAGER -y install nvidia-driver-latest-dkms + sudo "$package_manager" -y install nvidia-driver-latest-dkms fi - $SUDO $PACKAGE_MANAGER -y install cuda-drivers + sudo "$package_manager" -y install cuda-drivers +} + +url_encode() { + awk 'BEGIN { + for (i=0; i<125; i++) m[sprintf("%c", i)] = i + for (i=1; i<=length(ARGV[1]); i++) { + c = substr(ARGV[1], i, 1) + if (c ~ /[[:alnum:]_.!~*\47()-]/) printf "%s", c + else printf "%%%02X", m[c] + } + }' "$1" } # ref: https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#ubuntu # ref: https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#debian install_cuda_driver_apt() { - status 'Installing NVIDIA repository...' - if curl -I --silent --fail --location "https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m | sed -e 's/aarch64/sbsa/')/cuda-keyring_1.1-1_all.deb" >/dev/null ; then - curl -fsSL -o $TEMP_DIR/cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m | sed -e 's/aarch64/sbsa/')/cuda-keyring_1.1-1_all.deb - else - error $CUDA_REPO_ERR_MSG - fi + u_os_name=$(url_encode "$1") + u_os_version=$(url_encode "$2") + u_cuda_arch=$(url_encode "$(uname -m | sed -e 's/aarch64/sbsa/')") + ( + status 'Installing NVIDIA repository...' - case $1 in + tmp_dir= + trap 'rm -rf -- "$tmp_dir"' EXIT INT TERM + tmp_dir=$(mktemp -d) || error 'Cannot create temp dir' + cuda_keyring_url="${NVIDIA_REPOS_URL}/${u_os_name}${u_os_version}/${u_cuda_arch}/cuda-keyring_1.1-1_all.deb" + + if ! curl -fsSL -I --fail --location "$cuda_keyring_url" >/dev/null; then + error "$cuda_repo_err_message" + fi + curl -fsSL -o "$tmp_dir/cuda-keyring.deb" "$cuda_keyring_url" + + case "$1" in debian) status 'Enabling contrib sources...' - $SUDO sed 's/main/contrib/' < /etc/apt/sources.list | $SUDO tee /etc/apt/sources.list.d/contrib.list > /dev/null + sudo sed 's/main/contrib/' /etc/apt/sources.list | + sudo tee /etc/apt/sources.list.d/contrib.list >/dev/null if [ -f "/etc/apt/sources.list.d/debian.sources" ]; then - $SUDO sed 's/main/contrib/' < /etc/apt/sources.list.d/debian.sources | $SUDO tee /etc/apt/sources.list.d/contrib.sources > /dev/null + sudo sed 's/main/contrib/' /etc/apt/sources.list.d/debian.sources | + sudo tee /etc/apt/sources.list.d/contrib.sources >/dev/null fi ;; - esac + esac - status 'Installing CUDA driver...' - $SUDO dpkg -i $TEMP_DIR/cuda-keyring.deb - $SUDO apt-get update + status 'Installing CUDA driver...' + sudo dpkg -i "$tmp_dir/cuda-keyring.deb" + sudo apt-get update - [ -n "$SUDO" ] && SUDO_E="$SUDO -E" || SUDO_E= - DEBIAN_FRONTEND=noninteractive $SUDO_E apt-get -y install cuda-drivers -q + DEBIAN_FRONTEND=noninteractive sudo -e apt-get -y install cuda-drivers -q + ) || exit } if [ ! -f "/etc/os-release" ]; then error "Unknown distribution. Skipping CUDA installation." fi -. /etc/os-release +. /etc/os-release || error 'Cannot determine OS release' -OS_NAME=$ID -OS_VERSION=$VERSION_ID +u_os_name=$ID +u_os_version=$VERSION_ID -PACKAGE_MANAGER= -for PACKAGE_MANAGER in dnf yum apt-get; do - if available $PACKAGE_MANAGER; then - break - fi +for package_manager in apt-get dnf yum; do + available "$package_manager" && break + package_manager= done -if [ -z "$PACKAGE_MANAGER" ]; then +if [ -z "$package_manager" ]; then error "Unknown package manager. Skipping CUDA installation." fi -if ! check_gpu nvidia-smi || [ -z "$(nvidia-smi | grep -o "CUDA Version: [0-9]*\.[0-9]*")" ]; then - case $OS_NAME in - centos|rhel) install_cuda_driver_yum 'rhel' $(echo $OS_VERSION | cut -d '.' -f 1) ;; - rocky) install_cuda_driver_yum 'rhel' $(echo $OS_VERSION | cut -c1) ;; - fedora) [ $OS_VERSION -lt '39' ] && install_cuda_driver_yum $OS_NAME $OS_VERSION || install_cuda_driver_yum $OS_NAME '39';; - amzn) install_cuda_driver_yum 'fedora' '37' ;; - debian) install_cuda_driver_apt $OS_NAME $OS_VERSION ;; - ubuntu) install_cuda_driver_apt $OS_NAME $(echo $OS_VERSION | sed 's/\.//') ;; - *) exit ;; +if ! check_gpu nvidia-smi || { nvidia-smi | grep -q 'CUDA Version: [0-9]*\.[0-9]*'; }; then + case "$u_os_name" in + centos | rhel) install_cuda_driver_yum 'rhel' "${u_os_version%%.*}" ;; + rocky) install_cuda_driver_yum 'rhel' "${u_os_version%"${u_os_version#?}"}" ;; + fedora) + if [ "${u_os_version%%.*}" -lt '39' ]; then + install_cuda_driver_yum "$u_os_name" "$u_os_version" + else + install_cuda_driver_yum "$u_os_name" '39' + fi + ;; + amzn) install_cuda_driver_yum 'fedora' '37' ;; + debian) install_cuda_driver_apt "$u_os_name" "$u_os_version" ;; + ubuntu) install_cuda_driver_apt "$u_os_name" "${u_os_version%%.*}${u_os_version#*.}" ;; + *) exit ;; esac fi -if ! lsmod | grep -q nvidia || ! lsmod | grep -q nvidia_uvm; then - KERNEL_RELEASE="$(uname -r)" - case $OS_NAME in - rocky) $SUDO $PACKAGE_MANAGER -y install kernel-devel kernel-headers ;; - centos|rhel|amzn) $SUDO $PACKAGE_MANAGER -y install kernel-devel-$KERNEL_RELEASE kernel-headers-$KERNEL_RELEASE ;; - fedora) $SUDO $PACKAGE_MANAGER -y install kernel-devel-$KERNEL_RELEASE ;; - debian|ubuntu) $SUDO apt-get -y install linux-headers-$KERNEL_RELEASE ;; - *) exit ;; +if ! { grep -q nvidia /proc/modules && grep -q nvidia_uvm /proc/modules; }; then + kernel_release="$(uname -r)" + case "$u_os_name" in + rocky) sudo "$package_manager" -y install kernel-devel kernel-headers ;; + centos | rhel | amzn) sudo "$package_manager" -y install "kernel-devel-$kernel_release" "kernel-headers-$kernel_release" ;; + fedora) sudo "$package_manager" -y install "kernel-devel-$kernel_release" ;; + debian | ubuntu) sudo apt-get -y install "linux-headers-$kernel_release" ;; + *) exit ;; esac - NVIDIA_CUDA_VERSION=$($SUDO dkms status | awk -F: '/added/ { print $1 }') - if [ -n "$NVIDIA_CUDA_VERSION" ]; then - $SUDO dkms install $NVIDIA_CUDA_VERSION + nvidia_cuda_version=$(sudo dkms status | awk -F: '/added/ { print $1 }') + if [ -n "$nvidia_cuda_version" ]; then + sudo dkms install "$nvidia_cuda_version" fi - if lsmod | grep -q nouveau; then + if grep -q nouveau /proc/modules; then status 'Reboot to complete NVIDIA CUDA driver install.' exit 0 fi - $SUDO modprobe nvidia - $SUDO modprobe nvidia_uvm + sudo modprobe nvidia nvidia_uvm fi # make sure the NVIDIA modules are loaded on boot with nvidia-persistenced if available nvidia-persistenced; then - $SUDO touch /etc/modules-load.d/nvidia.conf - MODULES="nvidia nvidia-uvm" - for MODULE in $MODULES; do - if ! grep -qxF "$MODULE" /etc/modules-load.d/nvidia.conf; then - echo "$MODULE" | $SUDO tee -a /etc/modules-load.d/nvidia.conf > /dev/null + sudo touch /etc/modules-load.d/nvidia.conf + for module in nvidia nvidia-uvm; do + if ! grep -qxF "$module" /etc/modules-load.d/nvidia.conf; then + printf %s\\n "$module" | sudo tee -a /etc/modules-load.d/nvidia.conf >/dev/null fi done fi