rzup 0.5.1

The RISC Zero version management library and CLI
Documentation
#!/usr/bin/env bash

# Copyright 2025 RISC Zero, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -euo pipefail

readonly VERSION="1.0.0"
readonly RZUP_URL="${RZUP_URL:-https://risc0-artifacts.s3.us-west-2.amazonaws.com/rzup}"
readonly ENV_PATH="${ENV_PATH:-prod}"
readonly RZUP_BIN_DIR="${HOME}/.risc0/bin"

TEMP_DIR=""
PROFILE_PATH=""
QUIET=0
DEBUG=0

readonly RED=$'\033[0;31m'
readonly GREEN=$'\033[0;32m'
readonly BLUE=$'\033[0;34m'
readonly YELLOW=$'\033[1;33m'
readonly BOLD=$'\033[1m'
readonly RESET=$'\033[0m'

cleanup() {
    if [[ -n "${TEMP_DIR}" && -d "${TEMP_DIR}" ]]; then
        rm -rf "${TEMP_DIR}"
    fi
}
trap cleanup EXIT
trap 'error "Installation interrupted. Cleaning up..." 1' INT TERM

info() {
    if ((QUIET == 0)); then
        printf "${BLUE}${RESET} %s\n" "$1" >&2
    fi
}

success() {
    if ((QUIET == 0)); then
        printf "${GREEN}${RESET} %s\n" "$1" >&2
    fi
}

error() {
    printf "${RED}${RESET} %s\n" "$1" >&2
    exit "${2:-1}"
}

debug() {
    if ((DEBUG == 1)); then
        printf "${YELLOW}debug${RESET} %s\n" "$1" >&2
    fi
}

show_help() {
    cat <<EOF
${BOLD}rzup installer v${VERSION}${RESET}

Usage: install [options]

Options:
  -q, --quiet     Suppress output
  -d, --debug     Enable debug output
  -h, --help      Show this help message
  -v, --version   Show version information

Environment variables:
  RZUP_URL    Override the default download URL
  ENV_PATH    Set environment path (default: prod)
EOF
}

parse_args() {
    while [[ $# -gt 0 ]]; do
        case $1 in
            -h|--help)
                show_help
                exit 0
                ;;
            -v|--version)
                echo "rzup installer v${VERSION}"
                exit 0
                ;;
            -q|--quiet)
                QUIET=1
                shift
                ;;
            -d|--debug)
                DEBUG=1
                shift
                ;;
            *)
                error "Unknown option: $1"
                ;;
        esac
    done
}

check_requirements() {
    local missing=()

    # check for rust installation
    if ! command -v rustc >/dev/null 2>&1; then
        missing+=("rustc")
    fi

    # use curl or wget
    if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
        missing+=("curl/wget")
    fi

    if ((${#missing[@]} > 0)); then
        error "Missing required tools: ${missing[*]}"
    fi
}

get_architecture() {
    local os
    local cpu

    os=$(uname -s | tr '[:upper:]' '[:lower:]')
    cpu=$(uname -m)

    if [[ "$os" == "linux" && "$cpu" =~ ^(x86_64|amd64)$ ]]; then
        echo "Linux-X64"
    elif [[ "$os" == "darwin" && "$cpu" == "arm64" ]]; then
        echo "macOS-ARM64"
    else
        error "Unsupported architecture: $os/$cpu"
    fi
}

download_file() {
    local url="$1"
    local output="$2"
    local temp_output="${output}.tmp"

    # use curl if available, otherwise wget
    if command -v curl >/dev/null 2>&1; then
        if ((QUIET == 1)); then
            curl -fsSL "$url" -o "$temp_output" || error "Download failed"
        else
            curl -fL --progress-bar "$url" -o "$temp_output" || error "Download failed"
        fi
    else
        if ((QUIET == 1)); then
            wget -q "$url" -O "$temp_output" || error "Download failed"
        else
            wget --show-progress "$url" -O "$temp_output" || error "Download failed"
        fi
    fi

    if [[ ! -s "$temp_output" ]]; then
        error "Downloaded file is empty"
    fi

    mv "$temp_output" "$output"
}

setup_shell_profile() {
    case "${SHELL:-}" in
        */zsh)  PROFILE_PATH="${ZDOTDIR:-$HOME}/.zshrc" ;;
        */bash) PROFILE_PATH="$HOME/.bashrc" ;;
        */fish) PROFILE_PATH="$HOME/.config/fish/config.fish" ;;
        *)      PROFILE_PATH="$HOME/.profile" ;;
    esac

    if [[ ! -f "$PROFILE_PATH" ]]; then
        touch "$PROFILE_PATH"
    fi

    if ! grep -q "$RZUP_BIN_DIR" "$PROFILE_PATH" 2>/dev/null; then
        {
            echo ""
            echo "export PATH=\"\$PATH:$RZUP_BIN_DIR\""
        } >> "$PROFILE_PATH"
        success "Updated shell profile"
    fi
}

install_rzup() {
    info "Starting rzup installation..."

    local arch
    arch=$(get_architecture)
    debug "Detected architecture: $arch"

    local download_url="${RZUP_URL}/${ENV_PATH}/${arch}/rzup"
    debug "Using download URL: $download_url"

    mkdir -p "$RZUP_BIN_DIR" || error "Failed to create installation directory"
    debug "Created installation directory: $RZUP_BIN_DIR"

    TEMP_DIR=$(mktemp -d) || error "Failed to create temporary directory"
    local temp_file="${TEMP_DIR}/rzup"
    debug "Temporary binary location: $temp_file"

    info "Downloading rzup..."
    download_file "$download_url" "$temp_file"

    chmod +x "$temp_file" || error "Failed to make rzup executable"

    debug "Moving rzup binary from $temp_file to $RZUP_BIN_DIR/rzup"
    mv "$temp_file" "$RZUP_BIN_DIR/rzup" || error "Failed to install rzup"

    info "Configuring shell..."
    setup_shell_profile

    success "Installation complete! rzup is installed in $RZUP_BIN_DIR"

    if ((QUIET == 0)); then
        cat <<EOF

${GREEN}To get started:${RESET}
1. Restart your shell or run:
   ${BOLD}source "$PROFILE_PATH"${RESET}
2. Verify the installation:
   ${BOLD}rzup --help${RESET}

EOF
    fi
}

main() {
    parse_args "$@"
    check_requirements
    install_rzup
}

main "$@"