#!/bin/sh set -eu fail() { printf 'mercator: %s\n' "$1" >&2 exit 1 } warn() { printf 'mercator: %s\n' "$1" >&2 } read_toggle() { toggle_name=$1 toggle_value=$2 toggle_fallback=$3 case "$toggle_value" in 1 | true | TRUE | True | yes | YES | Yes | on | ON | On) printf 'true\n' ;; 0 | false | FALSE | False | no | NO | No | off | OFF | Off | '') printf 'false\n' ;; *) warn "$toggle_name must be true or false; using $toggle_fallback." printf '%s\n' "$toggle_fallback" ;; esac } display_path() { if [ -n "${HOME:-}" ]; then home_prefix="$HOME/" case "$1" in "$HOME"/*) printf '%s/%s\n' '~' "${1#"$home_prefix"}" return ;; esac fi printf '%s\n' "$1" } run_mercator() { case "${1:-}" in '' | -*) MERCATOR_INSTALL_CHANNEL=standalone "$cli" setup "$@" ;; *) MERCATOR_INSTALL_CHANNEL=standalone "$cli" "$@" ;; esac } file_has_line() { line_file=$1 line_wanted=$2 [ -f "$line_file" ] || return 1 while IFS= read -r line_current || [ -n "$line_current" ]; do [ "$line_current" = "$line_wanted" ] && return 0 done <"$line_file" return 1 } remember_path_file() { path_file=$1 case ":$path_touched_files:" in *":$path_file:"*) return 1 ;; esac path_touched_files="$path_touched_files:$path_file" } append_path_line() { path_file=$1 path_line=$2 remember_path_file "$path_file" || return 0 if file_has_line "$path_file" "$path_line"; then path_present_files="${path_present_files:+$path_present_files, }$(display_path "$path_file")" return 0 fi path_parent=${path_file%/*} [ "$path_parent" != "$path_file" ] || path_parent=. mkdir -p "$path_parent" 2>/dev/null || return 1 { printf '\n%s\n' "$path_line" >>"$path_file"; } 2>/dev/null || return 1 path_updated_files="${path_updated_files:+$path_updated_files, }$(display_path "$path_file")" } print_manual_path() { printf '\n%s is not on PATH. Add it for future shells:\n' "$(display_path "$install_dir")" case "${SHELL##*/}" in fish) printf ' set -gx PATH "%s" %sPATH\n' "$install_dir" "$dollar" ;; *) printf ' export PATH="%s:%sPATH"\n' "$install_dir" "$dollar" ;; esac } configure_path() { case ":${PATH:-}:" in *":$install_dir:"*) return 0 ;; esac case "$no_modify_path" in true) print_manual_path return 0 ;; false) ;; esac [ -n "${HOME:-}" ] || { print_manual_path return 0 } case "$install_dir" in *[!A-Za-z0-9_./-]*) warn 'The install path contains characters that cannot be added to a shell profile safely.' print_manual_path return 0 ;; esac path_touched_files='' path_updated_files='' path_present_files='' shell_name=${SHELL##*/} sh_line=$(printf 'case ":%sPATH:" in *":%s:"*) ;; *) export PATH="%s:%sPATH" ;; esac # added by mercator installer' "$dollar" "$install_dir" "$install_dir" "$dollar") primary_profile='' case "$shell_name" in zsh) case "${ZDOTDIR:-}" in /*) primary_profile="$ZDOTDIR/.zshrc" ;; *) primary_profile="$HOME/.zshrc" ;; esac append_path_line "$primary_profile" "$sh_line" || true ;; bash) primary_profile="$HOME/.bashrc" append_path_line "$primary_profile" "$sh_line" || true if [ -f "$HOME/.bash_profile" ]; then append_path_line "$HOME/.bash_profile" "$sh_line" || true elif [ -f "$HOME/.bash_login" ]; then append_path_line "$HOME/.bash_login" "$sh_line" || true else append_path_line "$HOME/.profile" "$sh_line" || true fi ;; fish) fish_dir=${XDG_CONFIG_HOME:-$HOME/.config}/fish/conf.d primary_profile="$fish_dir/mercator.fish" fish_line=$(printf 'contains -- "%s" %sPATH; or set -gx PATH "%s" %sPATH # added by mercator installer' "$install_dir" "$dollar" "$install_dir" "$dollar") append_path_line "$primary_profile" "$fish_line" || true ;; *) primary_profile="$HOME/.profile" append_path_line "$primary_profile" "$sh_line" || true ;; esac printf '\n' if [ -n "$path_updated_files" ]; then printf 'Added %s to PATH in %s.\n' "$(display_path "$install_dir")" "$path_updated_files" printf 'Restart your shell, or load it now with:\n . %s\n' "$(display_path "$primary_profile")" elif [ -n "$path_present_files" ]; then printf 'A PATH entry for %s is already present in %s.\n' \ "$(display_path "$install_dir")" "$path_present_files" else print_manual_path fi } temporary_dir='' probe_dir='' probe_pid='' probe_watchdog='' lock_dir='' lock_owned=false stop_cli_probe() { if [ -n "$probe_watchdog" ]; then kill "$probe_watchdog" 2>/dev/null || true wait "$probe_watchdog" 2>/dev/null || true probe_watchdog='' fi if [ -n "$probe_pid" ]; then kill -KILL "$probe_pid" 2>/dev/null || true wait "$probe_pid" 2>/dev/null || true probe_pid='' fi } # Use a portable watchdog: macOS does not provide the timeout utility. run_cli_probe() { if [ -z "$probe_dir" ]; then probe_dir=$(mktemp -d "${TMPDIR:-/tmp}/mercator-probe.XXXXXX") || return 1 fi "$cli" "$1" >"$probe_dir/output" 2>/dev/null & probe_pid=$! ( sleep 10 & timer_pid=$! trap 'kill "$timer_pid" 2>/dev/null || true; wait "$timer_pid" 2>/dev/null || true; exit 0' HUP INT TERM wait "$timer_pid" || exit 0 kill -KILL "$probe_pid" 2>/dev/null || true ) & probe_watchdog=$! probe_status=0 wait "$probe_pid" 2>/dev/null || probe_status=$? probe_pid='' stop_cli_probe [ "$probe_status" = 0 ] || return 1 cli_probe_output=$(cat "$probe_dir/output") } valid_cli_version() { # Reject additional lines or diagnostic text before matching the whole version. case "$1" in '' | *[!0-9.]*) return 1 ;; esac printf '%s\n' "$1" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' } cleanup() { stop_cli_probe [ -z "$probe_dir" ] || rm -rf "$probe_dir" [ -z "$temporary_dir" ] || rm -rf "$temporary_dir" if [ "$lock_owned" = true ] && [ -r "$lock_dir/pid" ]; then lock_owner='' IFS= read -r lock_owner <"$lock_dir/pid" || true [ "$lock_owner" != "$$" ] || rm -rf "$lock_dir" fi } trap cleanup EXIT trap 'exit 129' HUP trap 'exit 130' INT trap 'exit 143' TERM for required_command in curl mktemp sleep; do command -v "$required_command" >/dev/null 2>&1 || fail "$required_command is required." done dollar='$' no_modify_path=$(read_toggle MERCATOR_NO_MODIFY_PATH "${MERCATOR_NO_MODIFY_PATH:-}" true) skip_setup=$(read_toggle MERCATOR_SKIP_SETUP "${MERCATOR_SKIP_SETUP:-}" true) install_dir=${MERCATOR_BIN_DIR:-${MERCATOR_INSTALL_DIR:-${INSTALL_DIR:-}}} if [ -z "$install_dir" ]; then [ -n "${HOME:-}" ] || fail 'HOME is unset; set MERCATOR_BIN_DIR explicitly.' install_dir="$HOME/.local/bin" fi while [ "$install_dir" != / ] && [ "${install_dir%/}" != "$install_dir" ]; do install_dir=${install_dir%/} done [ "$install_dir" != / ] || fail 'Refusing to install into /.' case "/$install_dir/" in */../*) fail 'Refusing an install path containing parent traversal.' ;; esac installer_url=${MERCATOR_INSTALLER_URL:-'https://mercator.sh/downloads/latest/install.sh'} cli="$install_dir/mercator" had_cli=false installer_completed=false [ ! -x "$cli" ] || had_cli=true if ! mkdir -p "$install_dir" 2>/dev/null; then [ "$had_cli" = true ] || fail "Could not create $install_dir." warn "Could not update $install_dir; continuing with the existing Mercator executable." else canonical_install_dir=$(CDPATH='' cd -P "$install_dir" 2>/dev/null && pwd) || fail "Could not resolve $install_dir." [ "$canonical_install_dir" != / ] || fail 'Refusing to install into /.' case "$install_dir" in /*) ;; *) install_dir=$canonical_install_dir ;; esac cli="$install_dir/mercator" [ ! -x "$cli" ] || had_cli=true lock_dir="$install_dir/.mercator-install-lock" if [ -d "$lock_dir" ]; then lock_pid='' [ ! -r "$lock_dir/pid" ] || IFS= read -r lock_pid <"$lock_dir/pid" || true case "$lock_pid" in '' | *[!0-9]*) rm -rf "$lock_dir" ;; *) kill -0 "$lock_pid" 2>/dev/null || rm -rf "$lock_dir" ;; esac fi if mkdir "$lock_dir" 2>/dev/null; then printf '%s\n' "$$" >"$lock_dir/pid" lock_owned=true else [ "$had_cli" = true ] || fail 'Another Mercator installation is already running.' warn 'Another Mercator installation is running; continuing with the existing executable.' fi fi if [ "$lock_owned" = true ]; then if temporary_dir=$(mktemp -d "${TMPDIR:-/tmp}/mercator-install.XXXXXX"); then installer="$temporary_dir/install.sh" if curl \ --fail \ --silent \ --show-error \ --location \ --max-redirs 3 \ --proto '=https' \ --proto-redir '=https' \ --tlsv1.2 \ --connect-timeout 10 \ --max-time 120 \ --output "$installer" \ "$installer_url"; then if sh -n "$installer"; then if MERCATOR_INSTALL_DIR="$install_dir" INSTALL_DIR="$install_dir" sh "$installer"; then installer_completed=true else warn 'The update failed before replacing the existing executable.' fi else warn 'The downloaded installer is not valid shell code; refusing to execute it.' fi else warn 'Could not download the Mercator installer.' fi else warn 'Could not create a temporary directory for the Mercator installer.' fi fi if [ ! -x "$cli" ]; then fail 'Mercator was not installed. Retry later or set MERCATOR_INSTALLER_URL to a trusted release installer.' fi if [ "$had_cli" = true ] && [ "$lock_owned" = true ] && [ "$installer_completed" = false ]; then warn 'Continuing with the existing Mercator executable.' fi # Validate reused binaries too: executable permissions alone do not prove the CLI works. run_cli_probe --version || fail "Mercator at $cli failed --version or exceeded the 10-second startup limit. Reinstall with: curl -fsSL https://mercator.sh/downloads/latest/install.sh | sh" valid_cli_version "$cli_probe_output" || fail "Mercator at $cli returned invalid or empty version output. Reinstall with: curl -fsSL https://mercator.sh/downloads/latest/install.sh | sh" run_cli_probe --help || fail "Mercator at $cli failed --help or exceeded the 10-second startup limit. Reinstall the CLI before setup." case "$cli_probe_output" in *mercator*setup*doctor*) ;; *) fail "Mercator at $cli returned incomplete or empty help. Reinstall the CLI before setup." ;; esac if [ "$had_cli" = true ]; then printf 'Mercator is available at %s.\n' "$(display_path "$cli")" fi cleanup trap - EXIT HUP INT TERM configure_path case "$skip_setup" in true) printf 'Setup skipped. Run %s setup when ready.\n' "$cli" exit 0 ;; false) ;; esac if [ -c /dev/tty ] && (: /dev/null; then if ! run_mercator "$@"