#!/bin/sh # GENERATED FILE - DO NOT EDIT. # Source: installer/generate.ts and installer/releases.json. set -u set -f LC_ALL=C TZ=UTC0 export LC_ALL TZ unset TAR_OPTIONS GZIP GZIP_OPT BZIP2 BZIP XZ_OPT XZ_DEFAULTS IFS=' ' CLI_RELEASE_VERSION='1.0.13' INSTALL_LIMIT_BYTES=104857600 INSTALL_LIMIT_BLOCKS=204800 stdout_green='' stderr_yellow='' stderr_red='' stdout_reset='' stderr_reset='' if [ -z "${NO_COLOR+x}" ] && [ "${TERM-}" != dumb ]; then if [ -t 1 ]; then stdout_green=$(printf '\033[32m') stdout_reset=$(printf '\033[0m') fi if [ -t 2 ]; then stderr_yellow=$(printf '\033[33m') stderr_red=$(printf '\033[31m') stderr_reset=$(printf '\033[0m') fi fi info() { printf '%s\n' "$*" } success() { printf '%sOK %s%s\n' "$stdout_green" "$*" "$stdout_reset" } warn() { printf '%s! %s%s\n' "$stderr_yellow" "$*" "$stderr_reset" >&2 } error() { printf '%sERROR %s%s\n' "$stderr_red" "$*" "$stderr_reset" >&2 } fail() { error "$*" exit 1 } usage() { # The help text deliberately prints the HOME expression for the caller. # shellcheck disable=SC2016 printf '%s\n' \ 'Usage: install.sh [--help]' \ '' \ 'Install or update the stable ReRune CLI at $HOME/.local/bin/rerune.' \ 'The installer is non-interactive and does not use sudo.' } case $# in 0) : ;; 1) if [ "$1" = '--help' ]; then usage exit 0 fi error 'Unknown argument. Only --help is supported.' usage >&2 exit 2 ;; *) error 'Too many arguments. Only --help is supported.' usage >&2 exit 2 ;; esac contains_path_control_character() { if printf '%s' "$1" | grep -q '[[:cntrl:]]'; then return 0; fi path_control_c1_pattern=$(printf '\302[\200-\237]') printf '%s' "$1" | grep -q "$path_control_c1_pattern" } is_safe_absolute_path() { case $1 in /*) : ;; *) return 1 ;; esac case $1 in *//*) return 1 ;; esac if contains_path_control_character "$1"; then return 1; fi return 0 } normalize_absolute_path() { normalized_path=$1 while [ "$normalized_path" != / ] && [ "${normalized_path%/}" != "$normalized_path" ]; do normalized_path=${normalized_path%/} done printf '%s\n' "$normalized_path" } is_decimal() { case $1 in ''|*[!0-9]*) return 1 ;; *) return 0 ;; esac } is_positive_decimal() { is_decimal "$1" || return 1 case $1 in 0|0*) return 1 ;; *) return 0 ;; esac } is_sha256() { [ "${#1}" -eq 64 ] || return 1 case $1 in *[!0-9a-f]*) return 1 ;; *) return 0 ;; esac } is_semver_component() { is_decimal "$1" || return 1 case $1 in 0|[1-9]|[1-9][0-9]*) return 0 ;; *) return 1 ;; esac } is_strict_semver() { semver_check=$1 semver_major=${semver_check%%.*} semver_rest=${semver_check#*.} [ "$semver_rest" != "$semver_check" ] || return 1 semver_minor=${semver_rest%%.*} semver_patch=${semver_rest#*.} [ "$semver_patch" != "$semver_rest" ] || return 1 case $semver_patch in *.*) return 1 ;; esac is_semver_component "$semver_major" && is_semver_component "$semver_minor" && is_semver_component "$semver_patch" } decimal_compare() { decimal_left=$1 decimal_right=$2 if [ "${#decimal_left}" -lt "${#decimal_right}" ]; then printf '%s\n' '-1' return 0 fi if [ "${#decimal_left}" -gt "${#decimal_right}" ]; then printf '%s\n' '1' return 0 fi while [ -n "$decimal_left" ]; do decimal_left_digit=${decimal_left%"${decimal_left#?}"} decimal_right_digit=${decimal_right%"${decimal_right#?}"} if [ "$decimal_left_digit" -lt "$decimal_right_digit" ]; then printf '%s\n' '-1' return 0 fi if [ "$decimal_left_digit" -gt "$decimal_right_digit" ]; then printf '%s\n' '1' return 0 fi decimal_left=${decimal_left#?} decimal_right=${decimal_right#?} done printf '%s\n' '0' } semver_compare() { compare_left=$1 compare_right=$2 compare_left_major=${compare_left%%.*} compare_left_rest=${compare_left#*.} compare_left_minor=${compare_left_rest%%.*} compare_left_patch=${compare_left_rest#*.} compare_right_major=${compare_right%%.*} compare_right_rest=${compare_right#*.} compare_right_minor=${compare_right_rest%%.*} compare_right_patch=${compare_right_rest#*.} compare_result=$(decimal_compare "$compare_left_major" "$compare_right_major") [ "$compare_result" -eq 0 ] || { printf '%s\n' "$compare_result" return 0 } compare_result=$(decimal_compare "$compare_left_minor" "$compare_right_minor") [ "$compare_result" -eq 0 ] || { printf '%s\n' "$compare_result" return 0 } decimal_compare "$compare_left_patch" "$compare_right_patch" } require_command() { command -v "$1" >/dev/null 2>&1 || fail "$1 is required." } user_path=${PATH-} PATH=/usr/bin:/bin:/usr/sbin:/sbin export PATH require_command grep active_rerune='' normalized_user_path='' path_entry_index=0 path_remainder=$user_path: while [ -n "$path_remainder" ]; do path_entry=${path_remainder%%:*} path_remainder=${path_remainder#*:} path_entry_index=$((path_entry_index + 1)) if ! is_safe_absolute_path "$path_entry"; then if contains_path_control_character "$path_entry"; then path_entry_reason='contains control characters' else case $path_entry in '') path_entry_reason='is empty' ;; /*//*) path_entry_reason='contains repeated slash characters' ;; /*) path_entry_reason='is malformed' ;; *) path_entry_reason='is relative' ;; esac fi path_entry_label="PATH entry $path_entry_index" case $path_entry in ''|*[!A-Za-z0-9_./~@%+=,-]*) : ;; *) path_entry_label="$path_entry_label ($path_entry)" ;; esac case $path_entry in '') path_candidate=./rerune ;; *) path_candidate=$path_entry/rerune ;; esac if [ -z "$active_rerune" ] && [ -x "$path_candidate" ]; then fail "$path_entry_label resolves an executable rerune through a relative or malformed path. Remove that PATH entry before installing." fi warn "Ignoring $path_entry_label while checking for an existing rerune because it $path_entry_reason." else path_entry=$(normalize_absolute_path "$path_entry") case :$normalized_user_path: in *:"$path_entry":*) : ;; *) normalized_user_path=${normalized_user_path:+$normalized_user_path:}$path_entry ;; esac path_candidate=$path_entry/rerune [ "$path_entry" != / ] || path_candidate=/rerune if [ -z "$active_rerune" ] && [ -x "$path_candidate" ]; then active_rerune=$path_candidate fi fi done require_command id current_uid=$(id -u 2>/dev/null) || fail 'Could not determine the current user ID.' is_decimal "$current_uid" || fail 'The current user ID is invalid.' [ "$current_uid" -ne 0 ] || fail 'Refusing to install ReRune CLI as root.' [ -n "${HOME-}" ] || fail 'HOME is required.' is_safe_absolute_path "$HOME" || fail 'HOME must be an absolute path containing only safe ASCII path characters.' HOME=$(normalize_absolute_path "$HOME") export HOME install_dir=$HOME/.local/bin destination=$install_dir/rerune lock_dir=$install_dir/.rerune-install.lock if [ -n "$active_rerune" ]; then is_safe_absolute_path "$active_rerune" || fail 'An active rerune command has an unsafe or relative path. Refusing to continue.' if [ "$active_rerune" != "$destination" ]; then homebrew_managed=no brew_command='' case $active_rerune in /opt/homebrew/bin/rerune) brew_command=/opt/homebrew/bin/brew ;; /usr/local/bin/rerune) brew_command=/usr/local/bin/brew ;; /home/linuxbrew/.linuxbrew/bin/rerune) brew_command=/home/linuxbrew/.linuxbrew/bin/brew ;; esac if [ -n "$brew_command" ] && [ -x "$brew_command" ]; then brew_root=$("$brew_command" --prefix 2>/dev/null || :) if is_safe_absolute_path "$brew_root" && [ "$active_rerune" = "$brew_root/bin/rerune" ] && "$brew_command" list --versions rerune >/dev/null 2>&1; then homebrew_managed=yes fi fi if [ "$homebrew_managed" = yes ]; then fail "ReRune is managed by Homebrew at $active_rerune. Use: brew upgrade BasalBit/tap/rerune" fi pacman_package='' if [ -x /usr/bin/pacman ]; then pacman_package=$(/usr/bin/pacman -Qqo "$active_rerune" 2>/dev/null || :) case $pacman_package in ''|*[!A-Za-z0-9@._+-]*) pacman_package='' ;; esac fi if [ -n "$pacman_package" ]; then if [ "$pacman_package" = rerune-bin ]; then fail "ReRune is managed by pacman package rerune-bin at $active_rerune. Use: yay -Syu rerune-bin" fi fail "ReRune is managed by pacman package $pacman_package at $active_rerune. Update it with your Arch package manager." fi fail "An active rerune command exists outside $destination at $active_rerune. Remove that conflict or adjust PATH before installing." fi fi require_command uname install_os=$(uname -s 2>/dev/null || :) install_arch=$(uname -m 2>/dev/null || :) case $install_os:$install_arch in Darwin:arm64|Darwin:aarch64) install_target=darwin-arm64 ;; Darwin:x86_64|Darwin:amd64) require_command sysctl rosetta_state=$(sysctl -in sysctl.proc_translated 2>/dev/null || :) case $rosetta_state in 1) install_target=darwin-arm64 ;; ''|0) install_target=darwin-amd64 ;; *) fail 'Could not determine whether macOS is running under Rosetta.' ;; esac ;; Linux:x86_64|Linux:amd64) install_target=linux-amd64 ;; Linux:aarch64|Linux:arm64) install_target=linux-arm64 ;; *) fail "ReRune CLI does not support $install_os/$install_arch." ;; esac install_family=${install_target%-*} release_url='' release_archive_sha256='' release_binary_sha256='' case $install_target in darwin-amd64) release_url='https://raw.githubusercontent.com/BasalBit/rerune-releases/main/v1.0.13/rerune_1.0.13_darwin_amd64.tar.gz' release_archive_sha256='2892ce55592a0381124cc47645d347cbedc2f872f829b1264e4c6efba135a9ed' release_binary_sha256='a4def51a81a984db3c25aa60398587998c5d5311b202f32a654c8d2eb6024dc9' ;; darwin-arm64) release_url='https://raw.githubusercontent.com/BasalBit/rerune-releases/main/v1.0.13/rerune_1.0.13_darwin_arm64.tar.gz' release_archive_sha256='d406e3d9f44cc2e0bdbe3d7fef061ee9942d9827a5c7b09b81ee0cf25b677d11' release_binary_sha256='72497cb3d49de0cf014ffc5d3985d1268c67eb50d6fbadcf3dea050d65b2f5f8' ;; linux-amd64) release_url='https://raw.githubusercontent.com/BasalBit/rerune-releases/main/v1.0.13/rerune_1.0.13_linux_amd64.tar.gz' release_archive_sha256='5bb959ec819cea447f4f138d38173d8b7d04c69a1fce8cd65af6e92b4b4b84d0' release_binary_sha256='15f8ee02b3eef675daae884a42c51a46010d79005a4c82fe1c0469c3fc185d9f' ;; linux-arm64) release_url='https://raw.githubusercontent.com/BasalBit/rerune-releases/main/v1.0.13/rerune_1.0.13_linux_arm64.tar.gz' release_archive_sha256='bad66a59adee718d81594bf66b8379095965da31d5a3371e3b06b6962ae9b1f3' release_binary_sha256='c8fcb3255f2e4dda9a27eb343ef66d06d8dc5f776472319eec5fae404b20c8a6' ;; esac case $release_url in https://*) : ;; *) fail "No HTTPS stable ReRune CLI artifact is available for $install_target." ;; esac case $release_url in *[!A-Za-z0-9._~:/?\&=%+-]*) fail 'The embedded release URL is invalid.' ;; esac is_sha256 "$release_archive_sha256" || fail 'The embedded archive SHA-256 is invalid.' is_sha256 "$release_binary_sha256" || fail 'The embedded binary SHA-256 is invalid.' is_strict_semver "$CLI_RELEASE_VERSION" || fail 'The embedded ReRune CLI release version is invalid.' info 'ReRune CLI installer' info " Platform: $install_target" info " Target: ReRune CLI $CLI_RELEASE_VERSION" info " Path: $destination" info '' for required_command in awk cat date kill ls mkdir mv ps rm rmdir wc; do require_command "$required_command" done if command -v sha256sum >/dev/null 2>&1; then hash_tool=sha256sum elif command -v shasum >/dev/null 2>&1; then hash_tool=shasum else fail 'sha256sum or shasum is required.' fi metadata_record() { # The quoted path is safe; only fixed metadata fields are parsed. # shellcheck disable=SC2012 ls -ldn "$1" 2>/dev/null | awk 'NR == 1 { print $1 ":" $3 }' } safe_mode_for_path() { inspected_path=$1 inspected_mode=$2 case $inspected_mode in ??????????) printf '%s\n' "$inspected_mode" ;; ??????????@|??????????.) printf '%s\n' "${inspected_mode%?}" ;; ??????????+) case $install_os in Darwin) access_control_output=$(ls -lde "$inspected_path" 2>/dev/null) || return 1 printf '%s\n' "$access_control_output" | awk 'NR == 1 { next } /^[[:space:]]*[0-9]+: .* deny / { next } { exit 1 }' || return 1 printf '%s\n' "${inspected_mode%?}" ;; Linux) command -v getfacl >/dev/null 2>&1 || return 1 access_control_output=$(getfacl -cp "$inspected_path" 2>/dev/null) || return 1 printf '%s\n' "$access_control_output" | awk -F: '$0 == "" { next } $1 == "user" && $2 == "" { next } $1 == "group" && $2 == "" { next } $1 == "other" && $2 == "" { next } { exit 1 }' || return 1 printf '%s\n' "${inspected_mode%?}" ;; *) return 1 ;; esac ;; *) return 1 ;; esac } require_no_symlink_components() { component_remainder=${1#/} component_path='' while [ -n "$component_remainder" ]; do path_component=${component_remainder%%/*} case $path_component in ''|.|..) fail "$1 contains an unsafe path component." ;; esac component_path=$component_path/$path_component [ ! -L "$component_path" ] || fail "Refusing to use symlinked path component $component_path." [ -d "$component_path" ] || fail "$component_path is not a directory." component_metadata=$(metadata_record "$component_path") component_raw_mode=${component_metadata%%:*} component_owner=${component_metadata#*:} component_mode=$(safe_mode_for_path "$component_path" "$component_raw_mode") || fail "$component_path has unsupported or unsafe access controls." case $component_mode in d?????????) : ;; *) fail "$component_path has invalid directory metadata." ;; esac if [ "$component_owner" != 0 ] && [ "$component_owner" != "$current_uid" ]; then fail "$component_path is owned by an untrusted user." fi case $component_mode in ???[sS]??????|??????[sS]???) fail "$component_path has unsafe special permissions." ;; esac case $component_mode in ?????w????|????????w?) case $component_owner:$component_mode in 0:?????????t) : ;; *) fail "$component_path is replaceable through unsafe ancestor permissions." ;; esac ;; esac if [ "$component_remainder" = "$path_component" ]; then component_remainder='' else component_remainder=${component_remainder#*/} fi done } require_safe_owned_directory() { [ -d "$1" ] && [ ! -L "$1" ] || fail "$1 must be a real directory, not a symlink." directory_metadata=$(metadata_record "$1") directory_raw_mode=${directory_metadata%%:*} directory_owner=${directory_metadata#*:} [ "$directory_metadata" != "$directory_owner" ] || fail "Could not inspect $1." directory_mode=$(safe_mode_for_path "$1" "$directory_raw_mode") || fail "$1 has unsupported or unsafe access controls." [ "$directory_owner" = "$current_uid" ] || fail "$1 is not owned by the current user." case $directory_mode in drwx??????) : ;; *) fail "$1 does not grant its owner safe directory access." ;; esac case $directory_mode in ?????w????|????????w?|???[sS]??????|??????[sS]???|?????????[tT]) fail "$1 has unsafe permissions." ;; esac } require_safe_owned_file() { [ -f "$1" ] && [ ! -L "$1" ] || return 1 file_metadata=$(metadata_record "$1") file_raw_mode=${file_metadata%%:*} file_owner=${file_metadata#*:} [ "$file_metadata" != "$file_owner" ] || return 1 file_mode=$(safe_mode_for_path "$1" "$file_raw_mode") || return 1 [ "$file_owner" = "$current_uid" ] || return 1 case $file_mode in -?????????) : ;; *) return 1 ;; esac case $file_mode in ?????w????|????????w?|???[sS]??????|??????[sS]???|?????????[tT]) return 1 ;; esac return 0 } require_exact_mode() { exact_metadata=$(metadata_record "$1") exact_raw_mode=${exact_metadata%%:*} exact_owner=${exact_metadata#*:} exact_mode=$(safe_mode_for_path "$1" "$exact_raw_mode") || return 1 [ "$exact_owner" = "$current_uid" ] && [ "$exact_mode" = "$2" ] } require_no_symlink_components "$HOME" require_safe_owned_directory "$HOME" old_umask=$(umask) umask 077 for directory_path in "$HOME/.local" "$install_dir"; do [ ! -L "$directory_path" ] || fail "Refusing to install through symlink $directory_path." if [ ! -d "$directory_path" ]; then [ ! -e "$directory_path" ] || fail "$directory_path exists and is not a directory." mkdir "$directory_path" || fail "Could not create $directory_path." fi require_safe_owned_directory "$directory_path" done umask "$old_umask" [ -w "$install_dir" ] && [ -x "$install_dir" ] || fail "$install_dir is not writable and searchable." hash_file() { if [ "$hash_tool" = sha256sum ]; then hash_output=$(sha256sum "$1" 2>/dev/null) || return 1 else hash_output=$(shasum -a 256 "$1" 2>/dev/null) || return 1 fi hash_value=${hash_output%% *} is_sha256 "$hash_value" || return 1 printf '%s\n' "$hash_value" } scratch_dir='' staged_binary='' verify_fallback='' backup_binary='' backup_sha256='' preserve_backup=no transaction_state='none' lock_owned=no lock_pid='' lock_time='' lock_start='' stale_lock_claim='' cleanup_running=no read_lock_value() { lock_value_path=$1 require_safe_owned_file "$lock_value_path" || return 1 lock_value=$(cat "$lock_value_path" 2>/dev/null) || return 1 lock_value_size=$(wc -c <"$lock_value_path" | awk '{ print $1 }') || return 1 is_decimal "$lock_value_size" || return 1 [ "$lock_value_size" -eq $((${#lock_value} + 1)) ] || return 1 printf '%s\n' "$lock_value" } inspect_lock_entries() { checked_lock_dir=$1 lock_pid_entries=0 lock_time_entries=0 lock_start_entries=0 lock_entries_ok=yes set +f for lock_entry in "$checked_lock_dir"/* "$checked_lock_dir"/.[!.]* "$checked_lock_dir"/..?*; do [ -e "$lock_entry" ] || [ -L "$lock_entry" ] || continue case $lock_entry in "$checked_lock_dir/pid") lock_pid_entries=$((lock_pid_entries + 1)) ;; "$checked_lock_dir/time") lock_time_entries=$((lock_time_entries + 1)) ;; "$checked_lock_dir/start") lock_start_entries=$((lock_start_entries + 1)) ;; *) lock_entries_ok=no ;; esac done set -f [ "$lock_entries_ok" = yes ] && [ "$lock_pid_entries" -le 1 ] && [ "$lock_time_entries" -le 1 ] && [ "$lock_start_entries" -le 1 ] } validate_lock_entries() { inspect_lock_entries "$1" && [ "$lock_pid_entries" -eq 1 ] && [ "$lock_time_entries" -eq 1 ] && [ "$lock_start_entries" -eq 1 ] } remove_partial_lock() { partial_lock_dir=$1 [ -d "$partial_lock_dir" ] && [ ! -L "$partial_lock_dir" ] && require_exact_mode "$partial_lock_dir" 'drwx------' && inspect_lock_entries "$partial_lock_dir" || return 1 for partial_entry in "$partial_lock_dir/pid" "$partial_lock_dir/time" "$partial_lock_dir/start"; do if [ -e "$partial_entry" ] || [ -L "$partial_entry" ]; then require_exact_mode "$partial_entry" '-rw-------' || return 1 rm -f "$partial_entry" || return 1 fi done rmdir "$partial_lock_dir" } process_start_identity() { process_identity=$(ps -p "$1" -o lstart= 2>/dev/null | awk '{$1=$1; print}') || return 1 [ -n "$process_identity" ] || return 1 if printf '%s' "$process_identity" | grep -q '[[:cntrl:]]'; then return 1; fi printf '%s\n' "$process_identity" } initialize_lock_metadata() { lock_pid=$$ lock_time=$(date +%s 2>/dev/null || :) lock_start=$(process_start_identity "$lock_pid" 2>/dev/null || :) is_positive_decimal "$lock_pid" && is_positive_decimal "$lock_time" && [ -n "$lock_start" ] || return 1 printf '%s\n' "$lock_pid" >"$lock_dir/pid" || return 1 printf '%s\n' "$lock_time" >"$lock_dir/time" || return 1 printf '%s\n' "$lock_start" >"$lock_dir/start" || return 1 require_exact_mode "$lock_dir" 'drwx------' && require_exact_mode "$lock_dir/pid" '-rw-------' && require_exact_mode "$lock_dir/time" '-rw-------' && require_exact_mode "$lock_dir/start" '-rw-------' && validate_lock_entries "$lock_dir" } assert_owned_lock() { [ "$lock_owned" = yes ] && [ -d "$lock_dir" ] && [ ! -L "$lock_dir" ] && require_exact_mode "$lock_dir" 'drwx------' && require_exact_mode "$lock_dir/pid" '-rw-------' && require_exact_mode "$lock_dir/time" '-rw-------' && require_exact_mode "$lock_dir/start" '-rw-------' && validate_lock_entries "$lock_dir" || return 1 asserted_pid=$(read_lock_value "$lock_dir/pid" 2>/dev/null || :) asserted_time=$(read_lock_value "$lock_dir/time" 2>/dev/null || :) asserted_start=$(read_lock_value "$lock_dir/start" 2>/dev/null || :) [ "$asserted_pid" = "$lock_pid" ] && [ "$asserted_time" = "$lock_time" ] && [ "$asserted_start" = "$lock_start" ] } remove_owned_lock() { case $lock_owned in no) return 0 ;; initializing) remove_partial_lock "$lock_dir" 2>/dev/null || warn "Could not remove partial installer lock $lock_dir." ;; yes) if assert_owned_lock; then cleanup_pid=$(read_lock_value "$lock_dir/pid" 2>/dev/null || :) cleanup_time=$(read_lock_value "$lock_dir/time" 2>/dev/null || :) cleanup_start=$(read_lock_value "$lock_dir/start" 2>/dev/null || :) if [ "$cleanup_pid" = "$lock_pid" ] && [ "$cleanup_time" = "$lock_time" ] && [ "$cleanup_start" = "$lock_start" ]; then rm -f "$lock_dir/pid" "$lock_dir/time" "$lock_dir/start" 2>/dev/null || : rmdir "$lock_dir" 2>/dev/null || warn "Could not remove installer lock $lock_dir." else warn "Installer lock $lock_dir changed during installation; it was not removed." fi else warn "Installer lock $lock_dir changed during installation; it was not removed." fi ;; esac lock_owned=no } rollback_transaction() { rollback_ok=no rollback_kind=$transaction_state case $rollback_kind in update) if require_safe_owned_file "$backup_binary" && require_exact_mode "$backup_binary" '-rwxr-xr-x'; then rollback_hash=$(hash_file "$backup_binary" 2>/dev/null || :) if [ "$rollback_hash" = "$backup_sha256" ]; then if [ ! -e "$destination" ] && [ ! -L "$destination" ]; then if ln "$backup_binary" "$destination" 2>/dev/null; then if rm -f "$backup_binary" 2>/dev/null; then backup_binary='' else warn "Restored the previous CLI but could not remove backup $backup_binary."; fi fi elif require_safe_owned_file "$destination"; then rollback_destination_sha256=$(hash_file "$destination" 2>/dev/null || :) if [ "$rollback_destination_sha256" = "$backup_sha256" ]; then if rm -f "$backup_binary" 2>/dev/null; then backup_binary='' else warn "The previous CLI is installed, but backup $backup_binary could not be removed."; fi elif [ "$rollback_destination_sha256" = "$release_binary_sha256" ] && mv -f "$backup_binary" "$destination"; then backup_binary='' fi fi restored_hash=$(hash_file "$destination" 2>/dev/null || :) if [ "$restored_hash" = "$backup_sha256" ] && require_exact_mode "$destination" '-rwxr-xr-x'; then rollback_ok=yes fi fi fi ;; fresh) if [ ! -e "$destination" ] && [ ! -L "$destination" ]; then rollback_ok=yes elif require_safe_owned_file "$destination"; then rollback_hash=$(hash_file "$destination" 2>/dev/null || :) if [ "$rollback_hash" = "$release_binary_sha256" ] && rm -f "$destination"; then rollback_ok=yes fi fi ;; none) rollback_ok=yes ;; esac if [ "$rollback_ok" != yes ] && [ "$rollback_kind" = update ] && [ -n "$backup_binary" ]; then preserve_backup=yes fi transaction_state=none [ "$rollback_ok" = yes ] } cleanup() { trap '' 1 2 3 15 [ "$cleanup_running" = no ] || return 0 cleanup_running=yes if [ "$transaction_state" != none ]; then rollback_transaction || warn 'Could not roll back the interrupted ReRune CLI installation.' fi if [ -n "$staged_binary" ]; then if rm -f "$staged_binary" 2>/dev/null; then staged_binary='' else warn "Could not remove staged installer file $staged_binary."; fi fi if [ -n "$verify_fallback" ]; then if rm -f "$verify_fallback" 2>/dev/null; then verify_fallback='' else warn "Could not remove verification file $verify_fallback."; fi fi if [ -n "$backup_binary" ]; then if [ "$preserve_backup" = yes ]; then warn "A verified backup was preserved at $backup_binary." else if rm -f "$backup_binary" 2>/dev/null; then backup_binary='' else warn "Could not remove backup file $backup_binary."; fi fi fi if [ -n "$stale_lock_claim" ]; then stale_claim_complete=no if validate_lock_entries "$stale_lock_claim" 2>/dev/null && require_exact_mode "$stale_lock_claim/pid" '-rw-------' && require_exact_mode "$stale_lock_claim/time" '-rw-------' && require_exact_mode "$stale_lock_claim/start" '-rw-------'; then cleanup_stale_pid=$(read_lock_value "$stale_lock_claim/pid" 2>/dev/null || :) cleanup_stale_time=$(read_lock_value "$stale_lock_claim/time" 2>/dev/null || :) cleanup_stale_start=$(read_lock_value "$stale_lock_claim/start" 2>/dev/null || :) if [ "$cleanup_stale_pid" = "${stale_pid-}" ] && [ "$cleanup_stale_time" = "${stale_time-}" ] && [ "$cleanup_stale_start" = "${stale_start-}" ]; then stale_claim_complete=yes fi fi if [ "$stale_claim_complete" = yes ] && [ ! -e "$lock_dir" ] && [ ! -L "$lock_dir" ]; then mv "$stale_lock_claim" "$lock_dir" 2>/dev/null || warn "Could not restore stale lock $stale_lock_claim." elif ! remove_partial_lock "$stale_lock_claim" 2>/dev/null; then warn "Stale lock claim $stale_lock_claim was preserved." fi stale_lock_claim='' fi if [ -n "$scratch_dir" ]; then rm -f \ "$scratch_dir/archive.tar.gz" \ "$scratch_dir/archive.list" \ "$scratch_dir/archive.verbose" \ "$scratch_dir/candidate" \ "$scratch_dir/version.actual" \ "$scratch_dir/version.expected" 2>/dev/null || warn "Could not remove every private installer file under $scratch_dir." if rmdir "$scratch_dir" 2>/dev/null; then scratch_dir='' else warn "Could not remove private temporary directory $scratch_dir."; fi fi remove_owned_lock cleanup_running=no } trap cleanup 0 trap 'exit 129' 1 trap 'exit 130' 2 trap 'exit 131' 3 trap 'exit 143' 15 acquire_lock() { deferred_signal_status=0 trap 'deferred_signal_status=129' 1 trap 'deferred_signal_status=130' 2 trap 'deferred_signal_status=131' 3 trap 'deferred_signal_status=143' 15 old_umask=$(umask) umask 077 if mkdir "$lock_dir" 2>/dev/null; then lock_owned=initializing if ! initialize_lock_metadata; then remove_partial_lock "$lock_dir" 2>/dev/null || : lock_owned=no umask "$old_umask" fail 'Could not create safe installer lock metadata.' fi lock_owned=yes umask "$old_umask" trap 'exit 129' 1 trap 'exit 130' 2 trap 'exit 131' 3 trap 'exit 143' 15 if [ "$deferred_signal_status" -ne 0 ]; then exit "$deferred_signal_status" fi return 0 fi umask "$old_umask" [ -d "$lock_dir" ] && [ ! -L "$lock_dir" ] || fail "$lock_dir exists but is not a safe installer lock." require_exact_mode "$lock_dir" 'drwx------' || fail "$lock_dir is not an installer-owned private lock." require_exact_mode "$lock_dir/pid" '-rw-------' || fail "$lock_dir has unsafe PID metadata." require_exact_mode "$lock_dir/time" '-rw-------' || fail "$lock_dir has unsafe time metadata." require_exact_mode "$lock_dir/start" '-rw-------' || fail "$lock_dir has unsafe process metadata." validate_lock_entries "$lock_dir" || fail "$lock_dir contains unexpected lock entries." stale_pid=$(read_lock_value "$lock_dir/pid" 2>/dev/null || :) stale_time=$(read_lock_value "$lock_dir/time" 2>/dev/null || :) stale_start=$(read_lock_value "$lock_dir/start" 2>/dev/null || :) if ! is_positive_decimal "$stale_pid" || ! is_positive_decimal "$stale_time" || [ -z "$stale_start" ]; then fail "$lock_dir contains invalid lock metadata." fi if kill -0 "$stale_pid" 2>/dev/null; then running_start=$(process_start_identity "$stale_pid" 2>/dev/null || :) [ -n "$running_start" ] || fail "Could not validate the process using PID $stale_pid from the installer lock." if [ "$running_start" = "$stale_start" ]; then fail "Another ReRune CLI installer is running with PID $stale_pid." fi fi now=$(date +%s 2>/dev/null || :) is_positive_decimal "$now" || fail 'Could not validate stale installer lock time.' if [ "$(decimal_compare "$stale_time" "$now")" -gt 0 ]; then fail "$lock_dir has a future timestamp; refusing to remove it." fi if [ "${#now}" -gt 18 ] || [ "$now" -le 300 ]; then fail 'Could not safely calculate the stale installer lock age.' fi stale_cutoff=$((now - 300)) if [ "$(decimal_compare "$stale_time" "$stale_cutoff")" -gt 0 ]; then fail "$lock_dir belongs to a recently exited installer; retry after five minutes." fi stale_lock_claim=$install_dir/.rerune-install.stale.$$.$now [ ! -e "$stale_lock_claim" ] && [ ! -L "$stale_lock_claim" ] || fail 'Could not reserve a stale lock claim path.' mv "$lock_dir" "$stale_lock_claim" || fail 'The stale installer lock changed before it could be claimed.' if ! require_exact_mode "$stale_lock_claim" 'drwx------' || ! require_exact_mode "$stale_lock_claim/pid" '-rw-------' || ! require_exact_mode "$stale_lock_claim/time" '-rw-------' || ! require_exact_mode "$stale_lock_claim/start" '-rw-------' || ! validate_lock_entries "$stale_lock_claim"; then if [ ! -e "$lock_dir" ] && [ ! -L "$lock_dir" ]; then mv "$stale_lock_claim" "$lock_dir" 2>/dev/null || : fi stale_lock_claim='' fail 'The stale installer lock changed while it was being claimed.' fi claimed_pid=$(read_lock_value "$stale_lock_claim/pid" 2>/dev/null || :) claimed_time=$(read_lock_value "$stale_lock_claim/time" 2>/dev/null || :) claimed_start=$(read_lock_value "$stale_lock_claim/start" 2>/dev/null || :) if [ "$claimed_pid" != "$stale_pid" ] || [ "$claimed_time" != "$stale_time" ] || [ "$claimed_start" != "$stale_start" ]; then if [ ! -e "$lock_dir" ] && [ ! -L "$lock_dir" ]; then mv "$stale_lock_claim" "$lock_dir" 2>/dev/null || : fi stale_lock_claim='' fail 'The stale installer lock was replaced during validation.' fi rm -f "$stale_lock_claim/pid" "$stale_lock_claim/time" "$stale_lock_claim/start" || fail 'Could not remove claimed stale lock metadata.' rmdir "$stale_lock_claim" || fail 'The claimed stale installer lock changed during removal.' stale_lock_claim='' old_umask=$(umask) umask 077 mkdir "$lock_dir" || fail 'Could not acquire the installer lock after stale lock removal.' lock_owned=initializing if ! initialize_lock_metadata; then remove_partial_lock "$lock_dir" 2>/dev/null || : lock_owned=no umask "$old_umask" fail 'Could not create safe installer lock metadata.' fi lock_owned=yes umask "$old_umask" trap 'exit 129' 1 trap 'exit 130' 2 trap 'exit 131' 3 trap 'exit 143' 15 if [ "$deferred_signal_status" -ne 0 ]; then exit "$deferred_signal_status" fi } print_path_instruction() { # The command is guidance for the caller's parent shell, not this process. # shellcheck disable=SC2016 printf '%s\n' 'export PATH="$HOME/.local/bin:$PATH"' >&2 } reviewed_binary_version() { reviewed_binary=$1 reviewed_output=$(cd / && "$reviewed_binary" version /dev/null) reviewed_status=$? if [ "$reviewed_status" -ne 0 ]; then reviewed_output=$(cd / && "$reviewed_binary" --version /dev/null) || return $? fi printf '%s\n' "$reviewed_output" } acquire_lock existing_state=absent existing_sha256='' known_version='' known_target='' if [ -L "$destination" ]; then fail "Refusing to replace symlink $destination." elif [ -e "$destination" ]; then require_safe_owned_file "$destination" || fail "$destination is not a safe regular file owned by the current user." require_exact_mode "$destination" '-rwxr-xr-x' || fail "$destination must be owned by the current user with mode 755." existing_sha256=$(hash_file "$destination") || fail "Could not hash $destination." case $install_family:$existing_sha256 in darwin:cdfaa452f782f330505821b75ff6e82a03f2a21254e85fa410683b10f893aa9b) known_version='1.0.0'; known_target='darwin-amd64' ;; darwin:aa7245e919e277830e91210a367bc5b45407f9a4edb7078020154b064d24ca5d) known_version='1.0.0'; known_target='darwin-arm64' ;; linux:15dede8bca8f399f48c9e3fe4b127aaa1459f23215b95f56a93ff5d622753e97) known_version='1.0.0'; known_target='linux-amd64' ;; linux:137b1f24976262ce97232a54096bfb489ea7a329a50b3e742454a81fb8a8a3e0) known_version='1.0.0'; known_target='linux-arm64' ;; darwin:391b31a88bfa083030553c116efb78b6bf9b2718c7f3ed4935eef55be8a5e0a3) known_version='1.0.1'; known_target='darwin-amd64' ;; darwin:a9092d0aa1323af297299b8b3cab621217f80d8ad701a8dd63858521ec6d3e0c) known_version='1.0.1'; known_target='darwin-arm64' ;; linux:b451c04849169c05dc779b1923ac46b7d55c34063e54bae596dc48d30cb7e8b9) known_version='1.0.1'; known_target='linux-amd64' ;; linux:a99832eeec64f9092f3a97cad4166630da37f8006d27eff323a952bfb89eb4c5) known_version='1.0.1'; known_target='linux-arm64' ;; darwin:222a9cf3dd1d00c24afa75d4e40c3c98a83989f85ffcaca82ceba0a85d27783c) known_version='1.0.3'; known_target='darwin-amd64' ;; darwin:02ed2c1126b7a1207b32520c2c89f15775f0605a90edda9c0d592d68bf927a03) known_version='1.0.3'; known_target='darwin-arm64' ;; linux:3da5df6da51cbd1712e15c297e99173a7526a2b828c7eafad4a819c085a30f89) known_version='1.0.3'; known_target='linux-amd64' ;; linux:7905291f19313309c972c0b555e7b2b64b54b6f24004471a42eee2a2b2b1f775) known_version='1.0.3'; known_target='linux-arm64' ;; darwin:748bdf35905726cdc0a3731996f48e4196e3b10671a68244540ae064b1bd0a76) known_version='1.0.4'; known_target='darwin-amd64' ;; darwin:44e4f22c7e3f245aab999f1b41121b858fd60c596096c472a5217333ec62534f) known_version='1.0.4'; known_target='darwin-arm64' ;; linux:1ef7c8dd1c7d440d0aa434f6e5728b95df2e44cd376fdd8ae537254a3db5c230) known_version='1.0.4'; known_target='linux-amd64' ;; linux:8f477a9cf7be812f017c8ea224333f89b29b344c5c9fbb6c8f1f1395ade4c1ed) known_version='1.0.4'; known_target='linux-arm64' ;; darwin:65912e8d704a5054e2840d3b20350e9bb0bd38a5b17681c475a738d2a33e71fb) known_version='1.0.5'; known_target='darwin-amd64' ;; darwin:3270883004f5c9570e8d40a6a3777cc05caeda756c13581ed015a02be133322b) known_version='1.0.5'; known_target='darwin-arm64' ;; linux:991eaa9450558d00b88fe5f8b7e64959abff668b9f77c82fb71569dfe66042e8) known_version='1.0.5'; known_target='linux-amd64' ;; linux:f7c1a68849c10af86809c3e22ebcfa97fdf5c371fa9f9e84dfca7e58f195d391) known_version='1.0.5'; known_target='linux-arm64' ;; darwin:e4a2cec14d449013010b6e4c14860b8704135e8a75c0c548ec104488dc519c0a) known_version='1.0.6'; known_target='darwin-amd64' ;; darwin:fc9d7c31caf60dae9cfff6e6f7d2daa26dcc3b40826f4d4aa8b41c44c828be7f) known_version='1.0.6'; known_target='darwin-arm64' ;; linux:8576eec98eb7869f3f5b5e8ce0341823593b3984f38ca62cb8c946fc7f710a69) known_version='1.0.6'; known_target='linux-amd64' ;; linux:8545f85ecb4b79b6412a30de06e2ef8ece78f017a257c3f8729aca98bb314f85) known_version='1.0.6'; known_target='linux-arm64' ;; darwin:087a9ab654221c5c76bd39b7cf8f243183ec433872310733fe3767a1e990a808) known_version='1.0.7'; known_target='darwin-amd64' ;; darwin:5d1c9a8acc83cdfe992e86538de0c147a76f70c53780adac06dacf70bf055abc) known_version='1.0.7'; known_target='darwin-arm64' ;; linux:6635a6f052ce147a54ff4849e72492d0aecfa23472d2e764dff7e5392de40d07) known_version='1.0.7'; known_target='linux-amd64' ;; linux:9fe2a5aeb7c369a6a3aa5727d4213a218c5be21ed13899492d8379279e38ebc1) known_version='1.0.7'; known_target='linux-arm64' ;; darwin:229a207646ce50219c1877744fe63783fff96783b7d4cf88c84323ca84d1d2fb) known_version='1.0.8'; known_target='darwin-amd64' ;; darwin:8500cc758a0b7586c75bb7deb1ad61ca9a2d8a40529472d7618806c88f8f98e6) known_version='1.0.8'; known_target='darwin-arm64' ;; linux:9992e639d8cdf289b4447710919590839448a037cd876c206c295221dcc1943d) known_version='1.0.8'; known_target='linux-amd64' ;; linux:6df93fecfc6843c2138177820021a5910fca430d70ccabc8f22cc73686964725) known_version='1.0.8'; known_target='linux-arm64' ;; darwin:bb06d10372e66da82d593c1524446d486036d664b82294aab03db2675f046898) known_version='1.0.9'; known_target='darwin-amd64' ;; darwin:952447357b28c147675728742f7b8a67d0e6fbdff6c1ee9a32014cb49a46edb9) known_version='1.0.9'; known_target='darwin-arm64' ;; linux:293cb48c6e629035d0a93391a390da9de2d13627dbfe070bf05b161fe1b396a4) known_version='1.0.9'; known_target='linux-amd64' ;; linux:b3039dc116c7047e643cb417201446d59b07db8f3b477570be10c5bdb38a6e2d) known_version='1.0.9'; known_target='linux-arm64' ;; darwin:aa53cfc91ca57bd20edd4110954b9595f1fc2c732478be882bcaef991a7f7f8a) known_version='1.0.10'; known_target='darwin-amd64' ;; darwin:ffd5ef82f600444377560c23279ad6ac152948bdf282e158349e974f98ee7e41) known_version='1.0.10'; known_target='darwin-arm64' ;; linux:240d3401152ae8bc5e079fd9318a7c469405dd647259eb709331db28fe50f910) known_version='1.0.10'; known_target='linux-amd64' ;; linux:1b296e504008e93d4f48bb57b0c1a11cbd9252969a0114c2c765f3380b402cbb) known_version='1.0.10'; known_target='linux-arm64' ;; darwin:f058dc472edea40f1cfecd7dab9c0064facc2b396fac1690bc930c37248d9ea0) known_version='1.0.11'; known_target='darwin-amd64' ;; darwin:d16b6f42ce1f6489d3554b68883ea9ae5b6294151ae392baeac2ba89cfb811e4) known_version='1.0.11'; known_target='darwin-arm64' ;; linux:5a898a8c404e67d12ea7bd4a4fa8e5477816a426e70ffe454814d74200cef2e4) known_version='1.0.11'; known_target='linux-amd64' ;; linux:200920987bcd807f1f7986660b591e7d5dcbbf62d86104b580f91ba8639463b0) known_version='1.0.11'; known_target='linux-arm64' ;; darwin:14f31523181ed4f11568ba05aef20ee45114bf21245bd27d9117ee429bf3a8ce) known_version='1.0.12'; known_target='darwin-amd64' ;; darwin:c3a7e8381c5eb16b0df4916c5a2686775e6f3f535f41265469ed56ddb79cc990) known_version='1.0.12'; known_target='darwin-arm64' ;; linux:3034a7280ca878635d56cadd33848a24497525928ba0b8ef00a24a329ac03c84) known_version='1.0.12'; known_target='linux-amd64' ;; linux:bc0074c883ebc72ddde7d16df91e2efe9310e3c40a3bf222f34caa3b515c5471) known_version='1.0.12'; known_target='linux-arm64' ;; darwin:a4def51a81a984db3c25aa60398587998c5d5311b202f32a654c8d2eb6024dc9) known_version='1.0.13'; known_target='darwin-amd64' ;; darwin:72497cb3d49de0cf014ffc5d3985d1268c67eb50d6fbadcf3dea050d65b2f5f8) known_version='1.0.13'; known_target='darwin-arm64' ;; linux:15f8ee02b3eef675daae884a42c51a46010d79005a4c82fe1c0469c3fc185d9f) known_version='1.0.13'; known_target='linux-amd64' ;; linux:c8fcb3255f2e4dda9a27eb343ef66d06d8dc5f776472319eec5fae404b20c8a6) known_version='1.0.13'; known_target='linux-arm64' ;; esac [ -n "$known_version" ] && [ -n "$known_target" ] || fail "$destination has an unknown SHA-256. Refusing to execute or replace it." is_strict_semver "$known_version" || fail 'The embedded known-binary version is invalid.' case $known_target in "$install_family"-amd64|"$install_family"-arm64) : ;; *) fail 'The embedded known-binary target is invalid.' ;; esac existing_state=known fi if [ "$existing_state" = known ]; then if [ "$known_target" = "$install_target" ]; then existing_reported_version=$(reviewed_binary_version "$destination") || fail "$destination matched a reviewed hash but did not report its version safely." [ "$existing_reported_version" = "$known_version" ] || fail "$destination matched a reviewed hash but did not report exact version $known_version." fi version_order=$(semver_compare "$known_version" "$CLI_RELEASE_VERSION") if [ "$version_order" -eq 0 ] && [ "$known_target" = "$install_target" ]; then success "ReRune CLI $known_version is already installed at $destination." case :$normalized_user_path: in *:"$install_dir":*) : ;; *) warn "$install_dir is not on PATH." print_path_instruction ;; esac exit 0 fi if [ "$version_order" -gt 0 ]; then success "ReRune CLI $known_version is newer than stable $CLI_RELEASE_VERSION; leaving it unchanged." case :$normalized_user_path: in *:"$install_dir":*) : ;; *) warn "$install_dir is not on PATH." print_path_instruction ;; esac exit 0 fi fi for required_command in chmod cmp cp curl ln mktemp tar; do require_command "$required_command" done case $install_os in Darwin) temporary_root=/private/tmp ;; Linux) temporary_root=/tmp ;; *) fail 'Could not select a private temporary directory root.' ;; esac require_no_symlink_components "$temporary_root" old_umask=$(umask) umask 077 scratch_dir=$(mktemp -d "$temporary_root/rerune-install.XXXXXXXX" 2>/dev/null) || fail "Could not create a private temporary directory under $temporary_root." umask "$old_umask" [ "$scratch_dir" != "$temporary_root/rerune-install.XXXXXXXX" ] || fail 'mktemp did not create a unique temporary directory.' case $scratch_dir in "$temporary_root"/rerune-install.*) : ;; *) fail 'mktemp returned an unexpected temporary path.' ;; esac [ -d "$scratch_dir" ] && [ ! -L "$scratch_dir" ] || fail 'The temporary directory is unsafe.' require_exact_mode "$scratch_dir" 'drwx------' || fail 'The temporary directory is not private and installer-owned.' archive_path=$scratch_dir/archive.tar.gz archive_list=$scratch_dir/archive.list archive_verbose=$scratch_dir/archive.verbose candidate_binary=$scratch_dir/candidate version_actual=$scratch_dir/version.actual version_expected=$scratch_dir/version.expected assert_owned_lock || fail 'The installer lock changed before download.' info "Downloading ReRune CLI $CLI_RELEASE_VERSION for $install_target..." ( ulimit -f "$INSTALL_LIMIT_BLOCKS" 2>/dev/null || exit 125 curl \ --disable \ --fail \ --location \ --silent \ --show-error \ --proto '=https' \ --proto-redir '=https' \ --tlsv1.2 \ --retry 3 \ --retry-delay 1 \ --retry-connrefused \ --connect-timeout 10 \ --max-time 120 \ --max-filesize "$INSTALL_LIMIT_BYTES" \ --output "$archive_path" \ "$release_url" ) || fail 'The ReRune CLI download failed.' archive_size=$(wc -c <"$archive_path" | awk '{ print $1 }') || fail 'Could not determine the downloaded archive size.' is_decimal "$archive_size" || fail 'The downloaded archive size is invalid.' [ "$archive_size" -gt 0 ] || fail 'The downloaded archive is empty.' [ "$archive_size" -le "$INSTALL_LIMIT_BYTES" ] || fail 'The downloaded archive exceeds 100 MiB.' archive_sha256=$(hash_file "$archive_path") || fail 'Could not hash the downloaded archive.' [ "$archive_sha256" = "$release_archive_sha256" ] || fail 'The ReRune CLI archive SHA-256 does not match the embedded release hash.' success 'Download verified.' ( ulimit -f "$INSTALL_LIMIT_BLOCKS" 2>/dev/null || exit 125 tar -tzf "$archive_path" ) >"$archive_list" || fail 'Could not safely list the ReRune CLI archive.' ( ulimit -f "$INSTALL_LIMIT_BLOCKS" 2>/dev/null || exit 125 tar -tvzf "$archive_path" ) >"$archive_verbose" || fail 'Could not inspect ReRune CLI archive entry types.' [ -s "$archive_list" ] && [ -s "$archive_verbose" ] || fail 'The ReRune CLI archive has no entries.' awk 'seen[$0]++ { exit 1 }' "$archive_list" || fail 'The ReRune CLI archive contains duplicate paths.' archive_entry_count=0 archive_binary_count=0 archive_member='' while IFS= read -r member_path || [ -n "$member_path" ]; do archive_entry_count=$((archive_entry_count + 1)) case $member_path in ''|/*|-*|*//*|*\\*|*:*|*[!A-Za-z0-9_./-]*) fail 'The ReRune CLI archive contains an unsafe path.' ;; esac normalized_member=${member_path%/} [ -n "$normalized_member" ] || fail 'The ReRune CLI archive contains an unsafe root entry.' member_remainder=$normalized_member while [ -n "$member_remainder" ]; do member_component=${member_remainder%%/*} case $member_component in ''|.|..) fail 'The ReRune CLI archive contains a traversal path.' ;; esac if [ "$member_remainder" = "$member_component" ]; then member_remainder='' else member_remainder=${member_remainder#*/} fi done case $member_path in */) : ;; rerune|*/rerune) archive_member=$normalized_member archive_binary_count=$((archive_binary_count + 1)) ;; esac done <"$archive_list" [ "$archive_binary_count" -eq 1 ] || fail 'The ReRune CLI archive must contain exactly one expected rerune binary.' archive_verbose_count=0 while IFS= read -r verbose_line || [ -n "$verbose_line" ]; do archive_verbose_count=$((archive_verbose_count + 1)) entry_type=${verbose_line%"${verbose_line#?}"} case $entry_type in -|d) : ;; *) fail 'The ReRune CLI archive contains a link or special entry.' ;; esac done <"$archive_verbose" [ "$archive_verbose_count" -eq "$archive_entry_count" ] || fail 'The ReRune CLI archive listings are inconsistent.' ( ulimit -f "$INSTALL_LIMIT_BLOCKS" 2>/dev/null || exit 125 tar -xOzf "$archive_path" "$archive_member" ) >"$candidate_binary" || fail 'Could not safely extract the expected rerune binary.' candidate_size=$(wc -c <"$candidate_binary" | awk '{ print $1 }') || fail 'Could not determine the candidate binary size.' is_decimal "$candidate_size" || fail 'The candidate binary size is invalid.' [ "$candidate_size" -gt 0 ] || fail 'The candidate ReRune CLI binary is empty.' [ "$candidate_size" -le "$INSTALL_LIMIT_BYTES" ] || fail 'The candidate ReRune CLI binary exceeds 100 MiB.' candidate_sha256=$(hash_file "$candidate_binary") || fail 'Could not hash the candidate ReRune CLI binary.' [ "$candidate_sha256" = "$release_binary_sha256" ] || fail 'The candidate ReRune CLI binary SHA-256 does not match the embedded release hash.' chmod 700 "$candidate_binary" || fail 'Could not make the verified candidate executable.' printf '%s\n' "$CLI_RELEASE_VERSION" >"$version_expected" || fail 'Could not prepare candidate version verification.' verify_version_exact() { version_binary=$1 : >"$version_actual" || return 1 ( ulimit -f "$INSTALL_LIMIT_BLOCKS" 2>/dev/null || exit 125 cd / || exit 125 "$version_binary" version "$version_actual" 2>/dev/null version_status=$? if [ "$version_status" -eq 0 ]; then cmp -s "$version_expected" "$version_actual" return $? fi [ "$version_status" -ne 126 ] || return 126 : >"$version_actual" || return 1 ( ulimit -f "$INSTALL_LIMIT_BLOCKS" 2>/dev/null || exit 125 cd / || exit 125 "$version_binary" --version "$version_actual" 2>/dev/null version_status=$? [ "$version_status" -eq 0 ] || return "$version_status" cmp -s "$version_expected" "$version_actual" } verify_version_exact "$candidate_binary" candidate_version_status=$? if [ "$candidate_version_status" -eq 126 ]; then old_umask=$(umask) umask 077 verify_fallback=$(mktemp "$install_dir/.rerune-verify.XXXXXXXX" 2>/dev/null) || fail 'Could not create a noexec verification fallback in the install directory.' umask "$old_umask" cp "$candidate_binary" "$verify_fallback" || fail 'Could not stage the noexec verification fallback.' chmod 700 "$verify_fallback" || fail 'Could not make the noexec verification fallback executable.' fallback_sha256=$(hash_file "$verify_fallback") || fail 'Could not hash the noexec verification fallback.' [ "$fallback_sha256" = "$release_binary_sha256" ] || fail 'The noexec verification fallback SHA-256 changed during staging.' verify_version_exact "$verify_fallback" || fail "The verified ReRune CLI candidate did not report exactly $CLI_RELEASE_VERSION." rm -f "$verify_fallback" || fail 'Could not remove the noexec verification fallback.' verify_fallback='' elif [ "$candidate_version_status" -ne 0 ]; then fail "The verified ReRune CLI candidate did not report exactly $CLI_RELEASE_VERSION." fi old_umask=$(umask) umask 077 staged_binary=$(mktemp "$install_dir/.rerune-stage.XXXXXXXX" 2>/dev/null) || fail 'Could not create a same-filesystem staging file.' umask "$old_umask" cp "$candidate_binary" "$staged_binary" || fail 'Could not copy the verified ReRune CLI candidate to the staging file.' chmod 755 "$staged_binary" || fail 'Could not set mode 755 on the staged ReRune CLI binary.' staged_sha256=$(hash_file "$staged_binary") || fail 'Could not hash the staged ReRune CLI binary.' [ "$staged_sha256" = "$release_binary_sha256" ] || fail 'The staged ReRune CLI binary SHA-256 changed during staging.' require_exact_mode "$staged_binary" '-rwxr-xr-x' || fail 'The staged ReRune CLI binary does not have safe mode 755 metadata.' assert_owned_lock || fail 'The installer lock changed before replacement.' if [ "$existing_state" = known ]; then require_safe_owned_file "$destination" || fail "$destination changed before replacement." replacement_source_sha256=$(hash_file "$destination") || fail "Could not recheck $destination before replacement." [ "$replacement_source_sha256" = "$existing_sha256" ] || fail "$destination changed before replacement." old_umask=$(umask) umask 077 backup_binary=$(mktemp "$install_dir/.rerune-backup.XXXXXXXX" 2>/dev/null) || fail 'Could not reserve a same-filesystem backup path.' rm -f "$backup_binary" || fail 'Could not prepare the same-filesystem backup path.' ln "$destination" "$backup_binary" || fail 'Could not atomically back up the installed ReRune CLI.' umask "$old_umask" backup_sha256=$(hash_file "$backup_binary") || fail 'Could not hash the ReRune CLI backup.' [ "$backup_sha256" = "$existing_sha256" ] || fail 'The ReRune CLI backup SHA-256 changed during backup.' require_exact_mode "$backup_binary" '-rwxr-xr-x' || fail 'The ReRune CLI backup has unsafe metadata.' transaction_state=update mv -f "$staged_binary" "$destination" || { rollback_transaction || : fail 'Could not atomically replace the installed ReRune CLI.' } staged_binary='' else transaction_state=fresh if ! ln "$staged_binary" "$destination" 2>/dev/null; then transaction_state=none fail "$destination appeared during installation; refusing to replace it." fi rm -f "$staged_binary" || fail 'Could not finish the atomic ReRune CLI installation.' staged_binary='' fi post_install_ok=yes assert_owned_lock || post_install_ok=no if [ -L "$destination" ] || ! require_safe_owned_file "$destination" || ! require_exact_mode "$destination" '-rwxr-xr-x'; then post_install_ok=no else installed_sha256=$(hash_file "$destination" 2>/dev/null || :) [ "$installed_sha256" = "$release_binary_sha256" ] || post_install_ok=no fi if [ "$post_install_ok" != yes ]; then if rollback_transaction; then fail 'Post-install verification failed; the previous installation state was restored.' fi fail "Post-install verification failed and automatic rollback could not safely restore $destination." fi transaction_state=none if [ -n "$backup_binary" ]; then rm -f "$backup_binary" || fail 'The update succeeded, but the installer could not remove its backup file.' backup_binary='' fi cleanup trap - 0 1 2 3 15 if [ "$existing_state" = known ] && [ "$known_version" = "$CLI_RELEASE_VERSION" ]; then success "Updated ReRune CLI $CLI_RELEASE_VERSION to the native $install_target build at $destination." elif [ "$existing_state" = known ]; then success "Updated ReRune CLI from $known_version to $CLI_RELEASE_VERSION at $destination." else success "Installed ReRune CLI $CLI_RELEASE_VERSION at $destination." fi case :$normalized_user_path: in *:"$install_dir":*) : ;; *) warn "$install_dir is not on PATH." print_path_instruction ;; esac exit 0