#!/bin/sh set -e # Code generated by godownloader. DO NOT EDIT. # usage() { this=$1 cat < /dev/null } uname_os() { os=$(uname -s | tr '[:upper:]' '[:lower:]') echo "$os" } uname_arch() { arch=$(uname -m) case $arch in aarch64) arch="arm64" ;; x86_64) arch="x86_64" ;; x86-64) arch="x86_64" ;; x64) arch="x86_64" ;; amd64) arch="x86_64" ;; arm64) arch="arm64" ;; armv8*) arch="arm64" ;; i386) arch="i386" ;; i686) arch="i386" ;; x86) arch="i386" ;; 386) arch="i386" ;; esac echo ${arch} } uname_arch_check() { arch=$(uname_arch) case "$arch" in x86_64) return 0 ;; arm64) return 0 ;; i386) return 0 ;; esac echo "$0: ARCH $arch is not supported, supported ARCH: x86_64, arm64, i386" return 1 } uname_os_check() { os=$(uname_os) case "$os" in darwin) return 0 ;; linux) return 0 ;; windows) return 0 ;; esac echo "$0: OS $os is not supported, supported OS: darwin, linux, windows" return 1 } untar() { tarball=$1 destination=$2 case "${tarball}" in *.tar.gz|*.tgz) if [ -z "$destination" ]; then tar -xzf "${tarball}" else tar -xzf "${tarball}" -C "${destination}" fi ;; *.tar) if [ -z "$destination" ]; then tar -xf "${tarball}" else tar -xf "${tarball}" -C "${destination}" fi ;; *.zip) if [ -z "$destination" ]; then unzip "${tarball}" else unzip "${tarball}" -d "${destination}" fi ;; *) echo "Unknown archive format for ${tarball}" return 1 esac } mktmpdir() { test -z "$TMPDIR" && TMPDIR="$(mktemp -d)" mkdir -p "${TMPDIR}" echo "${TMPDIR}" } http_download() { local_file=$1 source_url=$2 header=$3 headerflag='' destflag='' if is_command curl; then cmd='curl --fail -sSL' destflag='-o' headerflag='-H' elif is_command wget; then cmd='wget -q' destflag='-O' headerflag='--header' else echo "http_download: unable to find wget or curl" return 1 fi if [ -z "$header" ]; then $cmd $destflag "$local_file" "$source_url" else $cmd $headerflag "$header" $destflag "$local_file" "$source_url" fi } github_api() { local_file=$1 source_url=$2 header="" http_download "$local_file" "$source_url" "$header" } github_last_release() { owner_repo=$1 giturl="https://api.github.com/repos/${owner_repo}/releases" html=$(github_api - "$giturl") # Extract all tag names, filter out preview versions, and get the first (latest) one version=$(echo "$html" | grep "\"tag_name\":" | cut -f4 -d'"' | grep -v -E "(preview|alpha|beta|rc|dev|pre|snapshot|nightly|canary|experimental|unstable)" | head -n 1) test -z "$version" && return 1 echo "$version" } hash_sha256() { TARGET=${1:-/dev/stdin}; if is_command gsha256sum; then hash=$(gsha256sum "$TARGET") || return 1 echo "$hash" | cut -d ' ' -f 1 elif is_command sha256sum; then hash=$(sha256sum "$TARGET") || return 1 echo "$hash" | cut -d ' ' -f 1 elif is_command shasum; then hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1 echo "$hash" | cut -d ' ' -f 1 elif is_command openssl; then hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1 echo "$hash" | cut -d ' ' -f a else echo "hash_sha256: unable to find command to compute sha-256 hash" return 1 fi } hash_sha256_verify() { TARGET=$1 checksums=$2 if [ -z "$checksums" ]; then echo "hash_sha256_verify: checksum file not specified in arg2" return 1 fi BASENAME=${TARGET##*/} want=$(grep "${BASENAME}" "${checksums}" 2> /dev/null | tr '\t' ' ' | cut -d ' ' -f 1) if [ -z "$want" ]; then echo "hash_sha256_verify: unable to find checksum for '${TARGET}' in '${checksums}'" return 1 fi got=$(hash_sha256 "$TARGET") if [ "$want" != "$got" ]; then echo "hash_sha256_verify: checksum for '$TARGET' did not verify ${want} vs $got" return 1 fi } cat /dev/null << EOF ------------------------------------------------------------------------ End of functions from https://github.com/client9/posixshell ------------------------------------------------------------------------ EOF OWNER=blaxel-ai REPO=toolkit BINARY=blaxel BINARY_SHORT_NAME=bl BINDIR=${BINDIR:-~/.local/bin} PREFIX="$OWNER/$REPO" ARCH=$(uname_arch) OS=$(uname_os) OS_TITLE=$(echo "$OS" | awk '{print toupper(substr($0,1,1)) substr($0,2)}') case "$VERSION" in latest) VERSION="" ;; -h|-?|*help*) usage "$0" exit 1 ;; esac if [ -z "${VERSION}" ]; then echo "$PREFIX: checking GitHub for latest version" VERSION=$(github_last_release "$OWNER/$REPO") fi NAME=${BINARY}_${OS_TITLE}_${ARCH}.tar.gz if [ "$OS" = "windows" ]; then NAME="${NAME}.zip" BINARY="${BINARY}.zip" fi TARBALL_URL=https://github.com/${OWNER}/${REPO}/releases/download/${VERSION}/${NAME} # --- Shared helper functions for installer setup --- # Detect if running in a CI environment is_ci() { [ -n "${CI:-}" ] || [ -n "${GITHUB_ACTIONS:-}" ] || [ -n "${GITLAB_CI:-}" ] || [ -n "${CIRCLECI:-}" ] || [ -n "${TRAVIS:-}" ] || [ -n "${JENKINS_URL:-}" ] || [ -n "${BUILDKITE:-}" ] } # Detect the user's shell name # Sets the global DETECTED_SHELL variable detect_shell() { DETECTED_SHELL="" # Detect shell from $SHELL environment variable first (most reliable) if [ -n "${SHELL:-}" ]; then DETECTED_SHELL=$(basename "$SHELL") else # Fallback: try to detect from parent process parent_pid=$(ps -o ppid= -p $$ 2>/dev/null | tr -d ' ') if [ -n "$parent_pid" ]; then DETECTED_SHELL=$(ps -o comm= -p "$parent_pid" 2>/dev/null | sed 's/^-//') fi # Final fallback: try to detect from process if [ -z "$DETECTED_SHELL" ]; then DETECTED_SHELL=$(ps -p $$ -o comm= 2>/dev/null | sed 's/^-//') fi fi # If still no shell detected, check if fish config exists if [ -z "$DETECTED_SHELL" ] || [ "$DETECTED_SHELL" = "sh" ]; then if [ -f "$HOME/.config/fish/config.fish" ] || command -v fish >/dev/null 2>&1; then DETECTED_SHELL="fish" fi fi } # Detect the RC file for the given shell # Sets the global DETECTED_RC_FILE (display path) and DETECTED_RC_FILE_PATH (absolute path) # Arguments: $1 = shell name, $2 = "strict" to return 1 for unsupported shells (optional) detect_rc_file() { local shell_name="$1" local mode="$2" DETECTED_RC_FILE="" DETECTED_RC_FILE_PATH="" case "$shell_name" in zsh) DETECTED_RC_FILE="~/.zshrc" DETECTED_RC_FILE_PATH="$HOME/.zshrc" ;; bash) # Check for .bash_profile first (macOS default), then .bashrc if [ -f "$HOME/.bash_profile" ]; then DETECTED_RC_FILE="~/.bash_profile" DETECTED_RC_FILE_PATH="$HOME/.bash_profile" else DETECTED_RC_FILE="~/.bashrc" DETECTED_RC_FILE_PATH="$HOME/.bashrc" fi ;; fish) DETECTED_RC_FILE="~/.config/fish/config.fish" DETECTED_RC_FILE_PATH="$HOME/.config/fish/config.fish" ;; tcsh|csh) if [ "$mode" = "strict" ]; then return 1; fi DETECTED_RC_FILE="~/.cshrc" DETECTED_RC_FILE_PATH="$HOME/.cshrc" ;; ksh|mksh) if [ "$mode" = "strict" ]; then return 1; fi DETECTED_RC_FILE="~/.kshrc" DETECTED_RC_FILE_PATH="$HOME/.kshrc" ;; *) if [ "$mode" = "strict" ]; then return 1; fi DETECTED_RC_FILE="~/.profile" DETECTED_RC_FILE_PATH="$HOME/.profile" ;; esac return 0 } # Prompt the user with a question, handling both interactive and non-interactive (piped) modes # Arguments: $1 = prompt message # Sets the global PROMPT_RESPONSE variable # Returns 1 if no TTY is available (caller should handle the fallback) prompt_user() { local message="$1" PROMPT_RESPONSE="" if [ -t 0 ]; then # Running interactively - ask user via stdin printf "%s" "$message" read -r PROMPT_RESPONSE elif [ -e /dev/tty ]; then # Running non-interactively (piped from curl) - ask user via /dev/tty printf "%s" "$message" > /dev/tty read -r PROMPT_RESPONSE < /dev/tty else # No TTY available return 1 fi return 0 } # Check if the RC file already contains an active PATH entry for the install dir rc_file_has_active_path_entry() { local rc_file_path="$1" local shell_name="$2" local bin_path="$3" [ -f "$rc_file_path" ] || return 1 awk -v shell_name="$shell_name" -v bin_path="$bin_path" ' /^[[:space:]]*#/ { next } shell_name == "fish" && /^[[:space:]]*set[[:space:]]+-gx[[:space:]]+PATH[[:space:]]+/ && index($0, bin_path) > 0 { found = 1 } shell_name != "fish" && /^[[:space:]]*(export[[:space:]]+)?PATH=/ && index($0, bin_path) > 0 { found = 1 } END { exit found ? 0 : 1 } ' "$rc_file_path" } # --- Installer setup functions --- # Function to detect shell and provide PATH instructions setup_path_interactive() { local bin_path="$1" # Skip PATH setup for system directories that are already in PATH case "$bin_path" in /usr/local/bin|/usr/bin|/bin) echo "" echo "${BINARY} and ${BINARY_SHORT_NAME} were installed successfully to $bin_path" echo "" echo "Since $bin_path is already in your system PATH, no additional configuration is needed." echo "" echo "To get started, run: ${BINARY_SHORT_NAME} --help" return ;; esac detect_shell local shell_name="$DETECTED_SHELL" detect_rc_file "$shell_name" local rc_file="$DETECTED_RC_FILE" local rc_file_path="$DETECTED_RC_FILE_PATH" # For unknown shells, label it generically case "$shell_name" in zsh|bash|fish|tcsh|csh|ksh|mksh) ;; *) shell_name="shell" ;; esac echo "" echo "${BINARY} and ${BINARY_SHORT_NAME} were installed successfully to $bin_path" echo "" # Check if PATH is already configured if rc_file_has_active_path_entry "$rc_file_path" "$shell_name" "$bin_path"; then echo "${BINARY} is already configured in your PATH via $rc_file" echo "" echo "To get started, run: ${BINARY_SHORT_NAME} --help" return fi echo "To get started, you need ${BINARY} in your PATH environment variable." echo "" # BL_INSTALL_PATH=true/false bypasses CI check and prompt local response="n" if [ "${BL_INSTALL_PATH:-}" = "true" ]; then response="y" elif [ "${BL_INSTALL_PATH:-}" = "false" ]; then response="n" elif is_ci; then echo "Detected CI environment - skipping PATH modification." return elif prompt_user "Do you want to automatically add ${BINARY} to your PATH by modifying $rc_file? [y/N] "; then case "$PROMPT_RESPONSE" in [yY]|[yY][eE][sS]) response="y" ;; *) response="n" ;; esac fi case "$response" in n) # User declined or non-interactive mode echo "To add ${BINARY} and ${BINARY_SHORT_NAME} to your PATH, run:" echo "" if [ "$shell_name" = "fish" ]; then echo " echo '' >> $rc_file" echo " echo '# Added by ${BINARY} installer' >> $rc_file" echo " echo 'set -gx PATH $bin_path \$PATH' >> $rc_file" else echo " echo '' >> $rc_file" echo " echo '# Added by ${BINARY} installer' >> $rc_file" echo " echo 'export PATH=\"$bin_path:\$PATH\"' >> $rc_file" fi echo "" echo "Then restart your $shell_name or run: source $rc_file" ;; y) # User accepted automatic setup echo "Adding ${BINARY} to PATH in $rc_file" # Create the directory if it doesn't exist (for fish config) if [ "$shell_name" = "fish" ]; then mkdir -p "$(dirname "$rc_file_path")" fi # Add PATH export to the RC file using printf for better portability if [ "$shell_name" = "fish" ]; then printf "\n# Added by %s installer\nset -gx PATH %s \$PATH\n" "$BINARY" "$bin_path" >> "$rc_file_path" else printf "\n# Added by %s installer\nexport PATH=\"%s:\$PATH\"\n" "$BINARY" "$bin_path" >> "$rc_file_path" fi echo "✓ Added ${BINARY} to PATH in $rc_file" echo "" echo "To use ${BINARY} in your current shell, run:" echo " source $rc_file" echo "" echo "Or start a new terminal session." ;; esac echo "" echo "To get started, run: ${BINARY_SHORT_NAME} --help" } # Function to setup shell completions # Installs completion scripts to the shell's native completions directory # so they are auto-loaded without modifying RC files. setup_completion() { local bin_path="$1" # BL_INSTALL_COMPLETION=true/false bypasses CI check and prompt if [ "${BL_INSTALL_COMPLETION:-}" = "false" ]; then return fi if [ "${BL_INSTALL_COMPLETION:-}" != "true" ]; then if is_ci; then return fi fi detect_shell local shell_name="$DETECTED_SHELL" local completion_dir="" local completion_file="" case "$shell_name" in zsh) # Use the standard zsh completions dir; create it if needed # Many systems auto-load from /usr/local/share/zsh/site-functions # but that requires root, so prefer a user-level dir completion_dir="${ZSH_COMPLETION_DIR:-${HOME}/.zsh/completions}" completion_file="${completion_dir}/_${BINARY_SHORT_NAME}" ;; bash) # bash-completion 2.x auto-loads from this user directory completion_dir="${BASH_COMPLETION_USER_DIR:-${HOME}/.local/share/bash-completion/completions}" completion_file="${completion_dir}/${BINARY_SHORT_NAME}" ;; fish) # Fish auto-loads from this directory completion_dir="${HOME}/.config/fish/completions" completion_file="${completion_dir}/${BINARY_SHORT_NAME}.fish" ;; *) # Unsupported shell for completions - skip silently return ;; esac # Check if completion is already installed if [ -f "$completion_file" ]; then return fi if [ "${BL_INSTALL_COMPLETION:-}" != "true" ]; then echo "" if ! prompt_user "Do you want to install command completions? [Y/n] "; then return fi case "$PROMPT_RESPONSE" in [nN]|[nN][oO]) return ;; esac fi mkdir -p "$completion_dir" # Generate completion script directly into the completions directory if "${bin_path}/${BINARY_SHORT_NAME}" completion "$shell_name" > "$completion_file" 2>/dev/null; then # For bash: prepend a shim that defines _get_comp_words_by_ref if # the bash-completion package is not installed (e.g. macOS default bash). if [ "$shell_name" = "bash" ] && ! grep -q "Shim: provide _get_comp_words_by_ref" "$completion_file" 2>/dev/null; then local tmp_file="${completion_file}.tmp" cat > "$tmp_file" <<'BASH_SHIM' # Shim: provide _get_comp_words_by_ref if bash-completion is not installed. # This allows completions to work on macOS default bash (3.2) without # requiring 'brew install bash-completion'. if ! type _get_comp_words_by_ref >/dev/null 2>&1; then _get_comp_words_by_ref() { local exclude cur_ words_ cword_ if [ "$1" = "-n" ]; then exclude=$2 shift 2 fi while [ $# -gt 0 ]; do case "$1" in cur) cur="${COMP_WORDS[COMP_CWORD]}" ;; prev) prev="${COMP_WORDS[COMP_CWORD-1]}" ;; words) eval words='("${COMP_WORDS[@]}")' ;; cword) cword=$COMP_CWORD ;; esac shift done } fi BASH_SHIM cat "$completion_file" >> "$tmp_file" mv "$tmp_file" "$completion_file" fi echo "✓ Command completions installed to $completion_file" if [ "$shell_name" = "zsh" ]; then # Zsh needs the completions dir in fpath; check if it's already there detect_rc_file "$shell_name" if [ -n "$DETECTED_RC_FILE_PATH" ] && ! grep -q "${completion_dir}" "$DETECTED_RC_FILE_PATH" 2>/dev/null; then printf "\n# Added by %s installer - completions directory\nfpath=(%s \$fpath)\nautoload -Uz compinit && compinit\n" "$BINARY" "$completion_dir" >> "$DETECTED_RC_FILE_PATH" echo " Added $completion_dir to fpath in $DETECTED_RC_FILE" fi fi echo " Restart your shell to enable completions." else # Fallback: binary might not be in PATH yet, clean up empty file rm -f "$completion_file" echo "⚠ Could not generate completions (${BINARY_SHORT_NAME} not yet in PATH)." echo " Run this after restarting your shell:" case "$shell_name" in zsh) echo " ${BINARY_SHORT_NAME} completion zsh > $completion_file" ;; bash) echo " ${BINARY_SHORT_NAME} completion bash > $completion_file" ;; fish) echo " ${BINARY_SHORT_NAME} completion fish > $completion_file" ;; esac fi } # Function to setup tracking preference setup_tracking() { local config_dir="$HOME/.blaxel" local config_file="$config_dir/config.yaml" # BL_INSTALL_TRACKING=true/false bypasses CI check and prompt if [ "${BL_INSTALL_TRACKING:-}" = "false" ]; then return fi if [ "${BL_INSTALL_TRACKING:-}" != "true" ]; then if is_ci; then return fi fi # Check if tracking is already configured if [ -f "$config_file" ] && grep -q "^tracking:" "$config_file"; then return fi local tracking_value="false" if [ "${BL_INSTALL_TRACKING:-}" = "true" ]; then tracking_value="true" else echo "" if ! prompt_user "Help us improve Blaxel faster by sending anonymous error reports? [y/N] "; then return fi case "$PROMPT_RESPONSE" in [yY]|[yY][eE][sS]) tracking_value="true" ;; esac fi # Create config directory if it doesn't exist mkdir -p "$config_dir" # If running as root (e.g. via sudo), fix ownership for the real user if [ "$(id -u)" = "0" ] && [ -n "${SUDO_USER:-}" ]; then chown "$SUDO_USER" "$config_dir" fi # Write or append tracking to config file if [ -f "$config_file" ]; then # Append tracking to existing config printf "\ntracking: %s\n" "$tracking_value" >> "$config_file" else # Create new config file with tracking printf "tracking: %s\n" "$tracking_value" > "$config_file" fi # If running as root (e.g. via sudo), fix ownership for the real user if [ "$(id -u)" = "0" ] && [ -n "${SUDO_USER:-}" ]; then chown "$SUDO_USER" "$config_file" fi if [ "$tracking_value" = "true" ]; then echo "✓ Tracking enabled. Thank you for helping improve Blaxel!" else echo "✓ Tracking disabled." fi } # wrap all destructive operations into a function # to prevent curl|bash network truncation and disaster execute() { SCRIPT_TMPDIR=$(mktmpdir) echo "$PREFIX: downloading from ${TARBALL_URL}" http_download "${SCRIPT_TMPDIR}/${NAME}" "$TARBALL_URL" untar "${SCRIPT_TMPDIR}/${NAME}" "${SCRIPT_TMPDIR}" install -d "${BINDIR}" install "${SCRIPT_TMPDIR}/${BINARY}" "${BINDIR}/${BINARY}" install "${SCRIPT_TMPDIR}/${BINARY}" "${BINDIR}/${BINARY_SHORT_NAME}" # Convert relative path to absolute path for PATH instructions if [ "${BINDIR#/}" = "${BINDIR}" ]; then # Relative path - convert to absolute ABSOLUTE_BINDIR="$(cd "${BINDIR}" && pwd)" else # Already absolute path ABSOLUTE_BINDIR="${BINDIR}" fi setup_path_interactive "$ABSOLUTE_BINDIR" setup_completion "$ABSOLUTE_BINDIR" setup_tracking } uname_os_check uname_arch_check execute