#!/usr/bin/env bash # ------------------------------------------------------------ # Copyright 2023 The Radius Authors. # # 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. # ------------------------------------------------------------ # ============================================================================ # Radius CLI Installer # # Usage: # wget -qO- https://raw.githubusercontent.com/radius-project/radius/main/deploy/install.sh | /bin/bash # wget -qO- ... | /bin/bash -s -- --version 0.40.0 # wget -qO- ... | /bin/bash -s -- --install-dir ~/.local/bin # wget -qO- ... | /bin/bash -s -- --version 0.40.0 --install-dir /opt/bin # wget -qO- ... | /bin/bash -s -- --include-rc # wget -qO- ... | sudo /bin/bash -s -- --install-dir /usr/local/bin # # Environment variables (override flags): # INSTALL_DIR - Installation directory (default: auto-detected) # INCLUDE_RC - Include release candidates ("true"/"false") # ============================================================================ set -euo pipefail # Include release candidates when determining latest version : "${INCLUDE_RC:="false"}" # Http request CLI RADIUS_HTTP_REQUEST_CLI="curl" # GitHub Organization and repo name to download release readonly GITHUB_ORG="radius-project" readonly GITHUB_REPO="radius" # Radius CLI filename readonly RADIUS_CLI_FILENAME="rad" # Temp directory for downloads (set in downloadFile, cleaned up on exit) RADIUS_TMP_ROOT="" # OS and ARCH are set in getSystemInfo OS="" ARCH="" usage() { cat << EOF Radius CLI Installer Usage: install.sh [OPTIONS] [VERSION] Options: -d, --install-dir Installation directory (default: auto-detected) -v, --version Version to install (e.g., 0.40.0, edge) -rc, --include-rc Include release candidates in latest version -h, --help Show this help message Environment Variables: INSTALL_DIR Override installation directory INCLUDE_RC Set to "true" to include release candidates Install Directory Detection: - If run as root or with sudo available: /usr/local/bin - Otherwise: \$HOME/.local/bin Examples: # Install latest stable version ./install.sh # Install a specific version ./install.sh --version 0.40.0 # Install to a custom directory (no root required) ./install.sh --install-dir ~/.local/bin # Install the edge (development) version ./install.sh --version edge EOF exit 0 } getSystemInfo() { ARCH=$(uname -m) case "${ARCH}" in armv7*) ARCH="arm" ;; aarch64) ARCH="arm64" ;; x86_64) ARCH="amd64" ;; *) ;; # Other architectures are validated in verifySupported esac OS=$(uname | tr '[:upper:]' '[:lower:]') } # Determine the default install directory based on permissions. # Follows the pattern used by fnm/uv: use /usr/local/bin when root, # otherwise fall back to $HOME/.local/bin (always writable). getDefaultInstallDir() { if [[ -n "${INSTALL_DIR:-}" ]]; then # User explicitly set via environment variable or flag echo "${INSTALL_DIR}" return fi if [[ ${EUID:-$(id -u)} -eq 0 ]]; then echo "/usr/local/bin" return fi # Non-root: use a user-writable location echo "${HOME}/.local/bin" } # Determine whether sudo is needed and available for the chosen install dir. needsSudo() { local install_dir="$1" # Root never needs sudo if [[ ${EUID:-$(id -u)} -eq 0 ]]; then return 1 fi # If the directory exists and is writable, no sudo needed if [[ -d "${install_dir}" && -w "${install_dir}" ]]; then return 1 fi # If the parent directory is writable (dir doesn't exist yet), no sudo local parent_dir parent_dir=$(dirname "${install_dir}") if [[ -d "${parent_dir}" && -w "${parent_dir}" ]]; then return 1 fi # Need elevated privileges return 0 } runAsRoot() { if needsSudo "${INSTALL_DIR}"; then if command -v sudo &> /dev/null; then sudo "$@" else echo "Error: installation to ${INSTALL_DIR} requires root privileges." echo "Either run as root, install sudo, or use --install-dir to choose a user-writable location." echo " Example: $0 --install-dir \"\${HOME}/.local/bin\"" exit 1 fi else "$@" fi } verifySupported() { local supported=(darwin-arm64 darwin-amd64 linux-amd64 linux-arm linux-arm64) local current_osarch="${OS}-${ARCH}" for osarch in "${supported[@]}"; do if [[ "${osarch}" == "${current_osarch}" ]]; then echo "Your system is ${OS}_${ARCH}" return fi done echo "No prebuilt binary for ${current_osarch}" exit 1 } checkHttpRequestCLI() { if command -v curl &> /dev/null; then RADIUS_HTTP_REQUEST_CLI=curl elif command -v wget &> /dev/null; then RADIUS_HTTP_REQUEST_CLI=wget else echo "Either curl or wget is required" exit 1 fi } checkExistingRadius() { local cli_file="${INSTALL_DIR}/${RADIUS_CLI_FILENAME}" if [[ -f "${cli_file}" ]]; then local version version=$("${cli_file}" version --cli 2> /dev/null || echo "unknown") printf '\nRadius CLI is detected. Current version: %s\n\n' "${version}" printf 'Reinstalling Radius CLI - %s...\n\n' "${cli_file}" else printf 'Installing Radius CLI...\n\n' fi } # Warn if existing rad binaries are found in PATH at different locations. warnExistingRadiusElsewhere() { # Resolve the target install directory to an absolute path without creating it local resolved_install if [[ -d "${INSTALL_DIR}" ]]; then if ! resolved_install=$(cd "${INSTALL_DIR}" && pwd -P); then resolved_install="${INSTALL_DIR}" fi else resolved_install="${INSTALL_DIR}" fi # Walk every PATH directory looking for rad binaries elsewhere local stale_paths=() local IFS=':' for dir in ${PATH}; do local path_dir="${dir}" if [[ "${path_dir}" == "~" ]]; then path_dir="${HOME}" elif [[ "${path_dir}" == "~/"* ]]; then path_dir="${HOME}/${path_dir:2}" fi local candidate="${path_dir}/${RADIUS_CLI_FILENAME}" if [[ -x "${candidate}" ]]; then local resolved_dir resolved_dir=$(cd "${path_dir}" 2> /dev/null && pwd -P) || continue if [[ "${resolved_dir}" != "${resolved_install}" ]]; then stale_paths+=("${candidate}") fi fi done if (( ${#stale_paths[@]} == 0 )); then return fi echo "============================================================================" echo "WARNING: Existing Radius CLI installation(s) found in different location(s):" for p in "${stale_paths[@]}"; do echo " ${p}" done echo "" echo "The new installation will be placed in:" echo " ${INSTALL_DIR}/${RADIUS_CLI_FILENAME}" echo "" echo "Remove the old binary(ies) before continuing to avoid using the wrong version:" for p in "${stale_paths[@]}"; do echo " rm -- \"${p}\"" done echo "============================================================================" } getLatestRelease() { local radReleaseUrl="https://api.github.com/repos/${GITHUB_ORG}/${GITHUB_REPO}/releases" local latest_release="" if [[ "${INCLUDE_RC}" == "true" ]]; then if [[ "${RADIUS_HTTP_REQUEST_CLI}" == "curl" ]]; then latest_release=$(curl -s "${radReleaseUrl}" | grep \"tag_name\" | awk 'NR==1{print $2}' | sed -n 's/\"\(.*\)\",/\1/p') else latest_release=$(wget -q --header="Accept: application/json" -O - "${radReleaseUrl}" | grep \"tag_name\" | awk 'NR==1{print $2}' | sed -n 's/\"\(.*\)\",/\1/p') fi else if [[ "${RADIUS_HTTP_REQUEST_CLI}" == "curl" ]]; then latest_release=$(curl -s "${radReleaseUrl}" | grep \"tag_name\" | grep -v rc | awk 'NR==1{print $2}' | sed -n 's/\"\(.*\)\",/\1/p') else latest_release=$(wget -q --header="Accept: application/json" -O - "${radReleaseUrl}" | grep \"tag_name\" | grep -v rc | awk 'NR==1{print $2}' | sed -n 's/\"\(.*\)\",/\1/p') fi fi if [[ -z "${latest_release}" ]]; then echo "Error: could not determine latest release" exit 1 fi ret_val="${latest_release}" } downloadFile() { local release_tag="$1" local radius_cli_artifact="${RADIUS_CLI_FILENAME}_${OS}_${ARCH}" RADIUS_TMP_ROOT=$(mktemp -d "${TMPDIR:-/tmp}/radius-install-XXXXXX") local artifact_tmp_file="${RADIUS_TMP_ROOT}/${radius_cli_artifact}" if [[ "${release_tag}" == "edge" ]]; then if ! command -v oras &> /dev/null; then echo "Error: oras CLI is not installed or not found in PATH." echo "Please visit https://edge.docs.radapp.io/installation for edge CLI installation instructions." exit 1 fi local download_url="ghcr.io/radius-project/rad/${OS}-${ARCH}:latest" echo "Downloading edge CLI from ${download_url}..." if ! oras pull "${download_url}" -o "${RADIUS_TMP_ROOT}"; then echo "Failed to download edge CLI." echo "If this was an authentication issue, please run 'docker logout ghcr.io' to clear any expired credentials." echo "Visit https://edge.docs.radapp.io/installation for edge CLI installation instructions." exit 1 fi mv "${RADIUS_TMP_ROOT}/rad" "${artifact_tmp_file}" else local download_base="https://github.com/${GITHUB_ORG}/${GITHUB_REPO}/releases/download" local download_url="${download_base}/${release_tag}/${radius_cli_artifact}" echo "Downloading ${download_url}..." if [[ "${RADIUS_HTTP_REQUEST_CLI}" == "curl" ]]; then curl -SsL "${download_url}" -o "${artifact_tmp_file}" else wget -q -O "${artifact_tmp_file}" "${download_url}" fi fi if [[ ! -f "${artifact_tmp_file}" ]]; then echo "Failed to download ${download_url:-the artifact}..." exit 1 fi } installFile() { local radius_cli_artifact="${RADIUS_CLI_FILENAME}_${OS}_${ARCH}" local tmp_root_radius_cli="${RADIUS_TMP_ROOT}/${radius_cli_artifact}" local cli_file="${INSTALL_DIR}/${RADIUS_CLI_FILENAME}" if [[ ! -f "${tmp_root_radius_cli}" ]]; then echo "Failed to unpack Radius CLI executable." exit 1 fi if [[ -f "${cli_file}" ]]; then runAsRoot rm "${cli_file}" fi chmod a+x "${tmp_root_radius_cli}" runAsRoot mkdir -p "${INSTALL_DIR}" runAsRoot cp "${tmp_root_radius_cli}" "${cli_file}" if [[ -f "${cli_file}" ]]; then echo "${RADIUS_CLI_FILENAME} installed into ${INSTALL_DIR} successfully" echo "Installing bicep (\"rad bicep download\")..." if "${cli_file}" bicep download; then echo "bicep installed successfully" else echo "Failed to install bicep" exit 1 fi else echo "Failed to install ${RADIUS_CLI_FILENAME}" exit 1 fi } failTrap() { local result=$? if [[ "${result}" != "0" ]]; then echo "Failed to install Radius CLI" echo "For support, go to https://github.com/radius-project/radius" fi cleanup exit "${result}" } cleanup() { if [[ -d "${RADIUS_TMP_ROOT:-}" ]]; then rm -rf "${RADIUS_TMP_ROOT}" fi } # Print post-install guidance, including PATH hints when needed. installCompleted() { echo "" # Check if the install dir is in PATH if ! echo "${PATH}" | tr ':' '\n' | grep -Fqx "${INSTALL_DIR}"; then echo "============================================================================" echo "NOTE: ${INSTALL_DIR} is not in your \$PATH." echo "" echo "Add it by running one of the following:" echo "" local current_shell current_shell="$(basename "${SHELL:-bash}")" case "${current_shell}" in zsh) echo " echo 'export PATH=\"${INSTALL_DIR}:\$PATH\"' >> ~/.zshrc" echo " source ~/.zshrc" ;; fish) echo " fish_add_path ${INSTALL_DIR}" ;; *) echo " echo 'export PATH=\"${INSTALL_DIR}:\$PATH\"' >> ~/.bashrc" echo " source ~/.bashrc" ;; esac echo "============================================================================" fi echo "To get started with Radius, please visit https://docs.radapp.io/getting-started/" } # ----------------------------------------------------------------------------- # main # ----------------------------------------------------------------------------- trap "failTrap" EXIT getSystemInfo checkHttpRequestCLI # Parse command-line arguments VERSION_ARG="" while [[ $# -gt 0 ]]; do case $1 in -v | --version) VERSION_ARG="$2" shift 2 ;; -d | --install-dir) INSTALL_DIR="$2" shift 2 ;; -rc | --include-rc) INCLUDE_RC="true" shift ;; -h | --help) usage ;; -*) echo "Unknown option: $1" echo "Run '$0 --help' for usage." exit 1 ;; *) # Support legacy positional version argument for backward compatibility VERSION_ARG="$1" shift ;; esac done # Support RADIUS_INSTALL_DIR as a backward-compatible alias for INSTALL_DIR if [[ -n "${RADIUS_INSTALL_DIR:-}" && -z "${INSTALL_DIR:-}" ]]; then INSTALL_DIR="${RADIUS_INSTALL_DIR}" fi # Set install directory (after arg parsing so --install-dir takes effect) INSTALL_DIR=$(getDefaultInstallDir) if [[ -z "${VERSION_ARG}" ]]; then echo "Getting the latest Radius CLI..." if [[ "${INCLUDE_RC}" == "true" ]]; then echo "Including release candidates in version selection..." fi getLatestRelease elif [[ "${VERSION_ARG}" == "edge" ]]; then ret_val="edge" else # Strip leading "v" if present, then normalize to v-prefixed format ret_val="v${VERSION_ARG#v}" fi verifySupported checkExistingRadius warnExistingRadiusElsewhere echo "Installing ${ret_val} Radius CLI..." echo "Install directory: ${INSTALL_DIR}" downloadFile "${ret_val}" installFile cleanup installCompleted