#!/bin/sh set -eu release_repository="${COMPARTMENT_RELEASES_REPOSITORY:-compartmentdev/compartment}" channel="latest" version="" bin_dir="" init_install="0" init_update="0" init_login="0" login_api_url="" login_email="" login_organization="" login_onboarding_session="" main_release_tag_asset="main-release-tag.txt" while [ "$#" -gt 0 ]; do case "$1" in --channel) channel="$2" shift 2 ;; --version) version="$2" shift 2 ;; --bin-dir) bin_dir="$2" shift 2 ;; --init-install) init_install="1" shift ;; --init-update) init_update="1" shift ;; --init-login) init_login="1" shift ;; --api-url) login_api_url="$2" shift 2 ;; --email) login_email="$2" shift 2 ;; --organization) login_organization="$2" shift 2 ;; --onboarding-session) login_onboarding_session="$2" shift 2 ;; *) printf 'Unknown installer argument: %s\n' "$1" >&2 exit 1 ;; esac done if [ -n "$version" ] && [ "$channel" != "latest" ]; then printf 'Choose either --version or --channel, not both.\n' >&2 exit 1 fi init_mode_count=0 if [ "$init_install" = "1" ]; then init_mode_count=$((init_mode_count + 1)) fi if [ "$init_update" = "1" ]; then init_mode_count=$((init_mode_count + 1)) fi if [ "$init_login" = "1" ]; then init_mode_count=$((init_mode_count + 1)) fi if [ "$init_mode_count" -gt 1 ]; then printf 'Choose at most one of --init-install, --init-update, or --init-login.\n' >&2 exit 1 fi if [ "$init_login" = "1" ]; then if [ -z "$login_api_url" ]; then printf 'Expected --api-url with --init-login.\n' >&2 exit 1 fi else if [ -n "$login_api_url" ] || [ -n "$login_email" ] || [ -n "$login_organization" ] || [ -n "$login_onboarding_session" ]; then printf 'Use --api-url, --email, --organization, and --onboarding-session only with --init-login.\n' >&2 exit 1 fi fi case "$channel" in latest|main) ;; *) printf 'Unsupported channel: %s\n' "$channel" >&2 exit 1 ;; esac resolve_main_release_tag() { main_release_tag_url="https://github.com/${release_repository}/releases/download/main/${main_release_tag_asset}" resolved_release_tag="$(curl -fsSL "$main_release_tag_url" | tr -d '\r\n')" if [ -z "$resolved_release_tag" ]; then printf 'Missing main release tag pointer in %s\n' "$main_release_tag_url" >&2 exit 1 fi case "$resolved_release_tag" in sha-*) printf 'Resolved main to %s\n' "$resolved_release_tag" >&2 printf '%s' "$resolved_release_tag" ;; *) printf 'Invalid main release tag pointer: %s\n' "$resolved_release_tag" >&2 exit 1 ;; esac } can_use_installer_terminal() { ( exec /dev/null 2>&1 } can_write_installer_terminal() { ( exec >/dev/tty ) >/dev/null 2>&1 } write_installer_terminal_prompt() { prompt_text="$1" if can_write_installer_terminal; then printf '%s' "$prompt_text" >/dev/tty return 0 fi printf '%s' "$prompt_text" >&2 } quote_shell_argument() { quote_value="$1" case "$quote_value" in *[!A-Za-z0-9_./:=@+-]*) printf "'%s'" "$(printf '%s' "$quote_value" | sed "s/'/'\\\\''/g")" ;; *) printf '%s' "$quote_value" ;; esac } format_init_login_command() { format_login_path="$1" format_login_api_url="$2" format_login_email="$3" format_login_organization="$4" format_login_onboarding_session="$5" format_login_command="$(printf '"%s" login --api-url %s --email %s' \ "$format_login_path" \ "$(quote_shell_argument "$format_login_api_url")" \ "$(quote_shell_argument "$format_login_email")")" if [ -n "$format_login_organization" ]; then format_login_command="${format_login_command} --organization $(quote_shell_argument "$format_login_organization")" fi if [ -n "$format_login_onboarding_session" ]; then format_login_command="${format_login_command} --onboarding-session $(quote_shell_argument "$format_login_onboarding_session")" fi printf '%s' "$format_login_command" } read_init_login_email() { read_login_email="$1" if [ -n "$read_login_email" ]; then printf '%s' "$read_login_email" return 0 fi if ! can_use_installer_terminal; then printf 'Requested `--init-login`, but no email was provided and no terminal is available. Re-run with `--email `.\n' >&2 exit 1 fi write_installer_terminal_prompt 'Email: ' IFS= read -r read_login_email &2 exit 1 fi printf '%s' "$read_login_email" } run_init_install() { init_install_path="$1" if ! can_use_installer_terminal; then printf 'Requested `--init-install`, but no terminal is available for sudo and setup prompts. Run `"%s" install` from an interactive shell.\n' "$init_install_path" >&2 exit 1 fi printf 'Running `"%s" install` for system self-hosted setup.\n' "$init_install_path" if can_write_installer_terminal; then "$init_install_path" install /dev/tty 2>/dev/tty return 0 fi "$init_install_path" install &2 exit 1 fi printf 'Running `%s` for system self-hosted update.\n' "$init_update_command" if can_write_installer_terminal; then "$init_update_path" system update /dev/tty 2>/dev/tty return 0 fi "$init_update_path" system update &2 exit 1 fi set -- login --api-url "$init_login_api_url" --email "$resolved_login_email" if [ -n "$init_login_organization" ]; then set -- "$@" --organization "$init_login_organization" fi if [ -n "$init_login_onboarding_session" ]; then set -- "$@" --onboarding-session "$init_login_onboarding_session" fi printf 'Running `%s` for local CLI login.\n' "$init_login_command" if can_write_installer_terminal; then "$init_login_path" "$@" /dev/tty 2>/dev/tty return 0 fi "$init_login_path" "$@" /dev/null 2>&1; then usable_bin_owner_match="$(find "$usable_bin_candidate_directory" -prune -user "$(id -u)" -print 2>/dev/null || true)" [ -n "$usable_bin_owner_match" ] return $? fi return 0 } select_user_bin_directory() { select_bin_old_ifs="$IFS" IFS=: for select_bin_path_entry in ${PATH:-}; do IFS="$select_bin_old_ifs" if is_user_bin_candidate "$select_bin_path_entry" && is_usable_user_bin_directory "$select_bin_path_entry"; then printf '%s' "$select_bin_path_entry" return 0 fi IFS=: done IFS="$select_bin_old_ifs" printf '%s' "${HOME}/.local/bin" } read_shell_name() { shell_path="${SHELL:-}" printf '%s' "${shell_path##*/}" } read_shell_profile_path() { shell_name="$1" if [ -n "${PROFILE:-}" ]; then printf '%s' "$PROFILE" return 0 fi case "$shell_name" in zsh) if [ "$os" = "darwin" ]; then printf '%s' "${ZDOTDIR:-$HOME}/.zprofile" else printf '%s' "${ZDOTDIR:-$HOME}/.zshrc" fi ;; bash) if [ "$os" = "darwin" ]; then printf '%s' "${HOME}/.bash_profile" else printf '%s' "${HOME}/.bashrc" fi ;; fish) printf '%s' "${HOME}/.config/fish/config.fish" ;; *) printf '' ;; esac } build_path_update_command() { path_command_shell_name="$1" path_command_directory="$2" case "$path_command_shell_name" in fish) printf 'fish_add_path "%s"' "$path_command_directory" ;; *) printf 'export PATH="%s:$PATH"' "$path_command_directory" ;; esac } print_path_instruction() { instruction_path_directory="$1" instruction_shell_name="$2" instruction_profile_path="$3" instruction_path_command="$(build_path_update_command "$instruction_shell_name" "$instruction_path_directory")" printf '%s is not on PATH.\n' "$instruction_path_directory" if [ -n "$instruction_profile_path" ]; then printf 'Add it to %s, or run for this shell: %s\n' "$instruction_profile_path" "$instruction_path_command" return 0 fi printf 'Add it to your shell profile, or run for this shell: %s\n' "$instruction_path_command" } should_update_shell_profile() { prompt_path_directory="$1" prompt_profile_path="$2" if [ "${COMPARTMENT_INSTALLER_ACCEPT_PATH_UPDATE:-}" = "1" ]; then return 0 fi if ! can_use_installer_terminal; then return 1 fi write_installer_terminal_prompt "${prompt_path_directory} is not on PATH. Add it to ${prompt_profile_path}? [Y/n] " IFS= read -r answer /dev/null 2>&1; then return 0 fi { printf '\n' printf '# Add Compartment CLI to PATH\n' printf '%s\n' "$append_path_command" } >> "$append_profile_path" } ensure_bin_directory_on_path() { ensure_bin_directory="$1" if is_directory_on_path "$ensure_bin_directory"; then return 0 fi ensure_shell_name="$(read_shell_name)" ensure_profile_path="$(read_shell_profile_path "$ensure_shell_name")" ensure_path_command="$(build_path_update_command "$ensure_shell_name" "$ensure_bin_directory")" if [ -z "$ensure_profile_path" ]; then print_path_instruction "$ensure_bin_directory" "$ensure_shell_name" '' return 0 fi if should_update_shell_profile "$ensure_bin_directory" "$ensure_profile_path"; then append_path_update_if_missing "$ensure_profile_path" "$ensure_path_command" printf 'Added %s to %s. Restart your shell or run: %s\n' "$ensure_bin_directory" "$ensure_profile_path" "$ensure_path_command" return 0 fi print_path_instruction "$ensure_bin_directory" "$ensure_shell_name" "$ensure_profile_path" } os="$(uname -s | tr '[:upper:]' '[:lower:]')" arch="$(uname -m)" case "$os" in darwin) target_os="darwin" ;; linux) target_os="linux" ;; *) printf 'Unsupported operating system: %s\n' "$os" >&2 exit 1 ;; esac case "$arch" in x86_64|amd64) target_arch="x64" ;; arm64|aarch64) target_arch="arm64" ;; *) printf 'Unsupported architecture: %s\n' "$arch" >&2 exit 1 ;; esac artifact_name="compartment-${target_os}-${target_arch}.tar.gz" if [ -n "$version" ]; then case "$version" in main) resolved_release_tag="$(resolve_main_release_tag)" release_path="releases/download/${resolved_release_tag}" ;; sha-*) release_path="releases/download/${version}" ;; *) release_path="releases/download/v${version}" ;; esac else if [ "$channel" = "main" ]; then resolved_release_tag="$(resolve_main_release_tag)" release_path="releases/download/${resolved_release_tag}" else release_path="releases/latest/download" fi fi base_url="https://github.com/${release_repository}/${release_path}" artifact_url="${base_url}/${artifact_name}" checksums_url="${base_url}/checksums.txt" temp_directory="$(mktemp -d)" trap 'rm -rf "$temp_directory"' EXIT INT TERM artifact_path="${temp_directory}/${artifact_name}" checksums_path="${temp_directory}/checksums.txt" curl -fsSL -o "$artifact_path" "$artifact_url" curl -fsSL -o "$checksums_path" "$checksums_url" expected_checksum_line="$(awk -v target="$artifact_name" '$2 == target { print $0 }' "$checksums_path")" if [ -z "$expected_checksum_line" ]; then printf 'Missing checksum entry for %s\n' "$artifact_name" >&2 exit 1 fi if command -v sha256sum >/dev/null 2>&1; then printf '%s\n' "$expected_checksum_line" | (cd "$temp_directory" && sha256sum -c -) elif command -v shasum >/dev/null 2>&1; then expected_checksum="$(printf '%s\n' "$expected_checksum_line" | awk '{ print $1 }')" actual_checksum="$(shasum -a 256 "$artifact_path" | awk '{ print $1 }')" if [ "$actual_checksum" != "$expected_checksum" ]; then printf 'Checksum mismatch for %s\n' "$artifact_name" >&2 exit 1 fi else printf 'Missing sha256 checksum tool.\n' >&2 exit 1 fi if [ -z "$bin_dir" ]; then bin_dir="$(select_user_bin_directory)" fi mkdir -p "$bin_dir" tar -xzf "$artifact_path" -C "$temp_directory" install_path="${bin_dir}/compartment" install -m 0755 "${temp_directory}/compartment" "$install_path" printf 'Installed compartment to %s\n' "$install_path" "$install_path" --version ensure_bin_directory_on_path "$bin_dir" if [ "$init_install" = "1" ]; then run_init_install "$install_path" exit 0 fi if [ "$init_update" = "1" ]; then run_init_update "$install_path" exit 0 fi if [ "$init_login" = "1" ]; then run_init_login "$install_path" "$login_api_url" "$login_email" "$login_organization" "$login_onboarding_session" exit 0 fi printf 'Installed CLI only. Run `"%s" install` when you are ready, run `sudo "%s" system update` for an existing runtime, or re-run this installer with `--init-install` or `--init-update`.\n' "$install_path" "$install_path"