#!/bin/bash if [ "$DEBUG" == "1" ]; then set -x fi TMP_DIR=$(mktemp -d -t jpillora-installer-XXXXXXXXXX) function cleanup { rm -rf $TMP_DIR > /dev/null } function fail { cleanup msg=$1 echo "============" echo "Error: $msg" 1>&2 exit 1 } function install { #settings USER="help-me-test" PROG="cli" ASPROG="helpmetest" FILTER="" MOVE="true" RELEASE="latest" # v2.1.2 INSECURE="false" OUT_DIR="${OUT_DIR:-/usr/local/bin}" GH="https://github.com" #bash check [ ! "$BASH_VERSION" ] && fail "Please use bash instead" [ ! -d $OUT_DIR ] && fail "output directory missing: $OUT_DIR" #dependency check, assume we are a standard POISX machine which find > /dev/null || fail "find not installed" which xargs > /dev/null || fail "xargs not installed" which sort > /dev/null || fail "sort not installed" which tail > /dev/null || fail "tail not installed" which cut > /dev/null || fail "cut not installed" which du > /dev/null || fail "du not installed" #choose an HTTP client GET="" if which curl > /dev/null; then GET="curl" if [[ $INSECURE = "true" ]]; then GET="$GET --insecure"; fi GET="$GET --fail -# -L" elif which wget > /dev/null; then GET="wget" if [[ $INSECURE = "true" ]]; then GET="$GET --no-check-certificate"; fi GET="$GET -qO-" else fail "neither wget/curl are installed" fi #debug HTTP if [ "$DEBUG" == "1" ]; then GET="$GET -v" fi #optional auth to install from private repos #NOTE: this also needs to be set on your instance of installer AUTH="${GITHUB_TOKEN}" if [ ! -z "$AUTH" ]; then GET="$GET -H 'Authorization: $AUTH'" fi #find OS #TODO BSDs and other posixs OS="" if [ -n "$OS" ]; then echo "Override OS: $OS" else OS=$(uname -s | tr '[:upper:]' '[:lower:]') case "$OS" in darwin) OS="darwin";; linux) OS="linux";; *) fail "unknown os: $(uname -s)";; esac fi #find ARCH ARCH="" if [ -n "$ARCH" ]; then echo "Override architecture: $ARCH" # Special handling for macOS - detect Apple Silicon even when running under Rosetta 2 elif [[ $OS = "darwin" ]] && sysctl hw.optional.arm64 2>/dev/null | grep -q ': 1'; then ARCH="arm64" elif uname -m | grep -E 'loong(arch)?64' > /dev/null; then ARCH="loong64" elif uname -m | grep -E '(aarch64|arm64)' > /dev/null; then ARCH="arm64" elif uname -m | grep 64 > /dev/null; then ARCH="amd64" elif uname -m | grep arm > /dev/null; then ARCH="arm" #TODO armv6/v7 elif uname -m | grep 386 > /dev/null; then ARCH="386" else fail "unknown arch: $(uname -m)" fi #choose from asset list URL="" FTYPE="" case "${OS}_${ARCH}" in "darwin_amd64") URL="https://github.com/help-me-test/cli/releases/download/v2.1.2/helpmetest-cli_Darwin_x86_64.tar.gz" FTYPE=".tar.gz" ;; "darwin_arm64") URL="https://github.com/help-me-test/cli/releases/download/v2.1.2/helpmetest-cli_Darwin_arm64.tar.gz" FTYPE=".tar.gz" ;; "linux_386") URL="https://github.com/help-me-test/cli/releases/download/v2.1.2/helpmetest-cli_Linux_i386.tar.gz" FTYPE=".tar.gz" ;; "linux_amd64") URL="https://github.com/help-me-test/cli/releases/download/v2.1.2/helpmetest-cli_Linux_x86_64.tar.gz" FTYPE=".tar.gz" ;; "linux_arm64") URL="https://github.com/help-me-test/cli/releases/download/v2.1.2/helpmetest-cli_Linux_arm64.tar.gz" FTYPE=".tar.gz" ;; *) fail "No asset for platform ${OS}-${ARCH}";; esac #got URL! download it... echo -n "Installing" echo -n " $USER/$PROG" if [ ! -z "$RELEASE" ]; then echo -n " $RELEASE" fi if [ ! -z "$ASPROG" ]; then echo -n " as $ASPROG" fi echo -n " (${OS}/${ARCH})" echo "....." #enter tempdir mkdir -p $TMP_DIR cd $TMP_DIR if [[ $FTYPE = ".gz" ]]; then which gzip > /dev/null || fail "gzip is not installed" bash -c "$GET $URL" | gzip -d - > $PROG || fail "download failed" elif [[ $FTYPE = ".bz2" ]]; then which bzip2 > /dev/null || fail "bzip2 is not installed" bash -c "$GET $URL" | bzip2 -d - > $PROG || fail "download failed" elif [[ $FTYPE = ".tar.bz" ]] || [[ $FTYPE = ".tar.bz2" ]]; then which tar > /dev/null || fail "tar is not installed" which bzip2 > /dev/null || fail "bzip2 is not installed" bash -c "$GET $URL" | tar jxf - || fail "download failed" elif [[ $FTYPE = ".tar.gz" ]] || [[ $FTYPE = ".tgz" ]]; then which tar > /dev/null || fail "tar is not installed" which gzip > /dev/null || fail "gzip is not installed" bash -c "$GET $URL" | tar zxf - || fail "download failed" elif [[ $FTYPE = ".tar.xz" ]] || [[ $FTYPE = ".txz" ]]; then which tar > /dev/null || fail "tar is not installed" which xz > /dev/null || fail "xz is not installed" bash -c "$GET $URL" | tar Jxf - || fail "download failed" elif [[ $FTYPE = ".zip" ]]; then which unzip > /dev/null || fail "unzip is not installed" bash -c "$GET $URL" > tmp.zip || fail "download failed" unzip -o -qq tmp.zip || fail "unzip failed" rm tmp.zip || fail "cleanup failed" elif [[ $FTYPE = ".bin" ]]; then bash -c "$GET $URL" > "cli_${OS}_${ARCH}" || fail "download failed" else fail "unknown file type: $FTYPE" fi #search for binary TMP_BIN="" # if ASPROG is set, try to find file matching that name first if [ ! -z "$ASPROG" ]; then TMP_BIN=$(find . -type f -name "$ASPROG" | head -n 1) fi # if FILTER is set, find largest file matching the filter pattern if [ -z "$TMP_BIN" ] || [ ! -f "$TMP_BIN" ]; then if [ ! -z "$FILTER" ]; then TMP_BIN=$(find . -type f -name "*${FILTER}*" | xargs du | sort -n | tail -n 1 | cut -f 2) else TMP_BIN=$(find . -type f | xargs du | sort -n | tail -n 1 | cut -f 2) fi fi if [ ! -f "$TMP_BIN" ]; then fail "could not find binary" fi #ensure its larger than 1MB #TODO linux=elf/darwin=macho file detection? if [[ $(du -m $TMP_BIN | cut -f1) -lt 1 ]]; then fail "no binary found ($TMP_BIN is not larger than 1MB)" fi #move into PATH or cwd chmod +x $TMP_BIN || fail "chmod +x failed" DEST="$OUT_DIR/$PROG" if [ ! -z "$ASPROG" ]; then DEST="$OUT_DIR/$ASPROG" fi #try move without sudo OUT=$(mv $TMP_BIN $DEST 2>&1) STATUS=$? if [ $STATUS -ne 0 ]; then if [[ $OUT =~ "Permission denied" ]]; then # Make a backup of the binary to try multiple locations cp $TMP_BIN $TMP_BIN.backup # Try to install to any existing PATH directory INSTALLED=false IFS=':' read -ra PATH_DIRS <<< "$PATH" for PATH_DIR in "${PATH_DIRS[@]}"; do # Skip empty, relative paths, and the original failed directory if [ -z "$PATH_DIR" ] || [[ "$PATH_DIR" != /* ]] || [ "$PATH_DIR" = "$OUT_DIR" ]; then continue fi # Try this PATH directory DEST="$PATH_DIR/$PROG" if [ ! -z "$ASPROG" ]; then DEST="$PATH_DIR/$ASPROG" fi OUT=$(mv $TMP_BIN $DEST 2>&1) STATUS=$? if [ $STATUS -eq 0 ]; then INSTALLED=true rm -f $TMP_BIN.backup break fi # Restore binary for next attempt cp $TMP_BIN.backup $TMP_BIN done # Clean up backup if we haven't already rm -f $TMP_BIN.backup # If no PATH directory worked, fallback to ~/.local/bin if [ "$INSTALLED" = false ]; then LOCAL_BIN="$HOME/.local/bin" mkdir -p "$LOCAL_BIN" 2>/dev/null DEST="$LOCAL_BIN/$PROG" if [ ! -z "$ASPROG" ]; then DEST="$LOCAL_BIN/$ASPROG" fi OUT=$(mv $TMP_BIN $DEST 2>&1) STATUS=$? if [ $STATUS -ne 0 ]; then fail "Failed to install to $LOCAL_BIN: $OUT" fi # Add to PATH if not already there ADDED_TO_PATH=false if ! echo "$PATH" | grep -q "$LOCAL_BIN"; then # Add to shell config files for rcfile in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.config/fish/config.fish"; do if [ -f "$rcfile" ] || [ "$rcfile" = "$HOME/.bashrc" ] || [ "$rcfile" = "$HOME/.zshrc" ]; then # For fish shell, use fish syntax if [[ $rcfile == *"fish"* ]]; then if [ -f "$rcfile" ]; then if ! grep -q "fish_add_path.*\.local/bin" "$rcfile"; then echo "" >> "$rcfile" echo "# Added by installer" >> "$rcfile" echo "fish_add_path $LOCAL_BIN" >> "$rcfile" ADDED_TO_PATH=true fi fi else # For bash/zsh mkdir -p "$(dirname "$rcfile")" if ! grep -q "\.local/bin" "$rcfile"; then echo "" >> "$rcfile" echo "# Added by installer" >> "$rcfile" echo "export PATH=\"\$HOME/.local/bin:\$PATH\"" >> "$rcfile" ADDED_TO_PATH=true fi fi fi done fi fi echo "Installed at $DEST" if [ "$ADDED_TO_PATH" = true ]; then echo "" echo "Added ~/.local/bin to PATH in shell config files." echo "Run: source ~/.bashrc (or ~/.zshrc) to update current shell" echo "Or restart your terminal." elif ! echo "$PATH" | grep -q "$LOCAL_BIN"; then echo "" echo "⚠️ ~/.local/bin is not in your PATH" echo "Add this to your ~/.bashrc or ~/.zshrc:" echo " export PATH=\"\$HOME/.local/bin:\$PATH\"" fi else fail "mv failed ($OUT)" fi else echo "Installed at $DEST" fi #done cleanup } install