install.sh•13.7 kB
#!/usr/bin/env bash
#
# Smart Tree Installer Script
#
# This script installs the 'st' binary on Linux and macOS.
#
# Usage:
# curl -sSL https://raw.githubusercontent.com/user/repo/main/scripts/install.sh | bash
#
# You can customize the installation by setting environment variables:
# - INSTALL_DIR: The directory to install the binary to (default: /usr/local/bin)
# - VERSION: The version to install (default: latest)
set -euo pipefail
# --- Configuration ---
GITHUB_REPO="8b-is/smart-tree"
BINARY_NAME="st"
INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}"
# --- Helper Functions ---
print_info() {
if [[ -t 1 ]]; then
echo -e "\033[0;34m[INFO]\033[0m $1"
else
echo "[INFO] $1"
fi
}
print_success() {
if [[ -t 1 ]]; then
echo -e "\033[0;32m[SUCCESS]\033[0m $1"
else
echo "[SUCCESS] $1"
fi
}
print_error() {
if [[ -t 1 ]]; then
echo -e "\033[0;31m[ERROR]\033[0m $1" >&2
else
echo "[ERROR] $1" >&2
fi
exit 1
}
print_warning() {
if [[ -t 1 ]]; then
echo -e "\033[0;33m[WARNING]\033[0m $1"
else
echo "[WARNING] $1"
fi
}
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Function to check if a release has assets
check_release_assets() {
local version="$1"
local assets_count
assets_count=$(curl -s "https://api.github.com/repos/$GITHUB_REPO/releases/tags/$version" | jq -r '.assets | length')
if [[ "$assets_count" -gt 0 ]]; then
return 0
else
return 1
fi
}
# Function to get releases with assets
get_releases_with_assets() {
curl -s "https://api.github.com/repos/$GITHUB_REPO/releases" | \
jq -r '.[] | select(.assets | length > 0) | .tag_name' | \
head -10
}
# Function to select version interactively
select_version() {
print_warning "The latest version ($1) doesn't have any release binaries yet."
print_info "Fetching other available versions with binaries..."
local versions=()
while IFS= read -r version; do
versions+=("$version")
done < <(get_releases_with_assets)
if [[ ${#versions[@]} -eq 0 ]]; then
print_error "No releases with binaries found!"
fi
print_info "Available versions with binaries:"
for i in "${!versions[@]}"; do
echo " $((i+1)). ${versions[$i]}"
done
local selection
while true; do
read -p "Select a version (1-${#versions[@]}) or 'q' to quit: " selection
if [[ "$selection" == "q" ]]; then
print_info "Installation cancelled."
exit 0
fi
if [[ "$selection" =~ ^[0-9]+$ ]] && (( selection >= 1 && selection <= ${#versions[@]} )); then
VERSION="${versions[$((selection-1))]}"
print_info "Selected version: $VERSION"
break
else
print_error "Invalid selection. Please try again."
fi
done
}
# --- Main Installation Logic ---
# Check for required tools
if ! command_exists curl; then
print_error "curl is required but not installed. Please install curl first."
fi
if ! command_exists jq; then
print_warning "jq is not installed. Some features may not work properly."
print_info "Consider installing jq for better release detection."
fi
# 1. Detect OS and Architecture
os_type="$(uname -s)"
architecture="$(uname -m)"
case "$os_type" in
Linux)
os="unknown-linux-gnu"
;;
Darwin)
os="apple-darwin"
;;
*)
print_error "Unsupported OS: $os_type"
;;
esac
case "$architecture" in
x86_64)
arch="x86_64"
;;
arm64 | aarch64)
arch="aarch64"
;;
*)
print_error "Unsupported architecture: $architecture"
;;
esac
# On Apple Silicon, prefer homebrew's default directory if it exists and the
# user has not specified a custom INSTALL_DIR.
if [[ "$os" == "apple-darwin" && "$arch" == "aarch64" && -d "/opt/homebrew/bin" ]]; then
if [[ "$INSTALL_DIR" == "/usr/local/bin" ]]; then
INSTALL_DIR="/opt/homebrew/bin"
fi
fi
# 2. Determine Version to Install
if [[ -z "${VERSION:-}" ]]; then
print_info "Fetching the latest version number..."
# Use GitHub API to get the latest release tag
latest_version_tag=$(curl --silent "https://api.github.com/repos/$GITHUB_REPO/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
if [[ -z "$latest_version_tag" ]]; then
print_error "Could not fetch the latest version. Please check the repository path and your network connection."
fi
# Check if latest version has assets
if command_exists jq && ! check_release_assets "$latest_version_tag"; then
select_version "$latest_version_tag"
else
VERSION="$latest_version_tag"
print_info "Latest version is $VERSION"
fi
else
print_info "Installing specified version: $VERSION"
# Check if specified version has assets
if command_exists jq && ! check_release_assets "$VERSION"; then
print_warning "Version $VERSION doesn't have any release binaries."
read -p "Would you like to select another version? (y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
select_version "$VERSION"
else
print_error "Cannot install version without binaries."
fi
fi
fi
# 3. Construct Download URL
# Note: Artifacts include version in filename
archive_name="${BINARY_NAME}-${VERSION}-${arch}-${os}.tar.gz"
download_url="https://github.com/$GITHUB_REPO/releases/download/$VERSION/$archive_name"
# 4. Download and Extract
tmp_dir=$(mktemp -d)
trap 'rm -rf "$tmp_dir"' EXIT
print_info "Downloading from $download_url"
if ! curl --progress-bar --fail -L "$download_url" -o "$tmp_dir/$archive_name"; then
print_error "Download failed. Please check the URL and your network."
fi
print_info "Extracting the binary..."
tar -xzf "$tmp_dir/$archive_name" -C "$tmp_dir"
# 5. Install the binary
print_info "Installing to $INSTALL_DIR..."
binary_path="$tmp_dir/$BINARY_NAME"
if [[ ! -f "$binary_path" ]]; then
# Sometimes the binary is in a subdirectory, e.g. target/release/
found_binary=$(find "$tmp_dir" -type f -name $BINARY_NAME | head -n 1)
if [[ -z "$found_binary" ]]; then
print_error "Could not find the '$BINARY_NAME' binary in the downloaded archive."
fi
binary_path=$found_binary
fi
if [[ -w "$INSTALL_DIR" ]]; then
mv "$binary_path" "$INSTALL_DIR/$BINARY_NAME"
else
print_info "Need sudo access to install to $INSTALL_DIR"
sudo mv "$binary_path" "$INSTALL_DIR/$BINARY_NAME"
fi
# 6. Verify Installation
if ! command_exists "$BINARY_NAME"; then
print_error "Installation failed. The '$BINARY_NAME' command is not available in your PATH."
fi
version_output=$($BINARY_NAME --version)
print_success "Successfully installed $BINARY_NAME to $INSTALL_DIR/$BINARY_NAME"
print_info "Version: $version_output"
print_info "You can now use the '$BINARY_NAME' command."
# --- Shell Completion Installation ---
install_shell_completions() {
local shell_type
local completion_installed=false
# Detect user's shell
if [[ -n "${SHELL:-}" ]]; then
shell_type=$(basename "$SHELL")
else
shell_type=$(basename "$(getent passwd "$USER" | cut -d: -f7)")
fi
print_info "Detected shell: $shell_type"
case "$shell_type" in
bash)
install_bash_completion
;;
zsh)
install_zsh_completion
;;
fish)
install_fish_completion
;;
*)
print_info "Auto-completion setup not available for $shell_type"
print_info "You can generate completions manually with: st --completions <shell>"
return
;;
esac
}
install_bash_completion() {
local completion_dir
local completion_file
# Find bash completion directory
if [[ -d "/etc/bash_completion.d" ]] && [[ -w "/etc/bash_completion.d" ]]; then
completion_dir="/etc/bash_completion.d"
elif [[ -d "$HOME/.local/share/bash-completion/completions" ]]; then
completion_dir="$HOME/.local/share/bash-completion/completions"
elif [[ -d "$HOME/.bash_completion.d" ]]; then
completion_dir="$HOME/.bash_completion.d"
else
# Create user completion directory
completion_dir="$HOME/.bash_completion.d"
mkdir -p "$completion_dir"
fi
completion_file="$completion_dir/_st"
print_info "Installing bash completions to $completion_file"
if $BINARY_NAME --completions bash > "$completion_file" 2>/dev/null; then
print_success "Bash completions installed!"
# Check if user's bashrc sources completions
if [[ -f "$HOME/.bashrc" ]] && ! grep -q "bash_completion.d" "$HOME/.bashrc"; then
print_info "Would you like to add completion sourcing to your .bashrc?"
read -p "(y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "" >> "$HOME/.bashrc"
echo "# Smart Tree bash completions" >> "$HOME/.bashrc"
echo "if [[ -d \"$completion_dir\" ]]; then" >> "$HOME/.bashrc"
echo " for f in \"$completion_dir\"/*; do" >> "$HOME/.bashrc"
echo " [[ -r \"\$f\" ]] && source \"\$f\"" >> "$HOME/.bashrc"
echo " done" >> "$HOME/.bashrc"
echo "fi" >> "$HOME/.bashrc"
print_success "Added completion sourcing to .bashrc"
print_info "Run 'source ~/.bashrc' to enable completions in current session"
fi
fi
else
print_warning "Failed to generate bash completions"
fi
}
install_zsh_completion() {
local completion_dir
local completion_file
local use_enhanced=false
# Check if enhanced completion exists
if command_exists curl && curl -s "https://raw.githubusercontent.com/$GITHUB_REPO/$VERSION/completions/_st_enhanced" >/dev/null 2>&1; then
print_info "Enhanced zsh completions with tips are available!"
read -p "Install enhanced completions with tips and examples? (y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
use_enhanced=true
fi
fi
# Find zsh completion directory
if [[ -d "/usr/local/share/zsh/site-functions" ]] && [[ -w "/usr/local/share/zsh/site-functions" ]]; then
completion_dir="/usr/local/share/zsh/site-functions"
elif [[ -d "$HOME/.zsh/completions" ]]; then
completion_dir="$HOME/.zsh/completions"
else
# Create user completion directory
completion_dir="$HOME/.zsh/completions"
mkdir -p "$completion_dir"
fi
completion_file="$completion_dir/_st"
if [[ "$use_enhanced" == "true" ]]; then
print_info "Installing enhanced zsh completions to $completion_file"
if curl -s "https://raw.githubusercontent.com/$GITHUB_REPO/$VERSION/completions/_st_enhanced" > "$completion_file"; then
print_success "Enhanced zsh completions installed!"
print_info "Tips: Use 'st_tips' command to see all tips and tricks"
else
print_warning "Failed to download enhanced completions, falling back to basic"
use_enhanced=false
fi
fi
if [[ "$use_enhanced" == "false" ]]; then
print_info "Installing zsh completions to $completion_file"
if $BINARY_NAME --completions zsh > "$completion_file" 2>/dev/null; then
print_success "Zsh completions installed!"
else
print_warning "Failed to generate zsh completions"
return
fi
fi
# Check if user's zshrc includes the completion directory in fpath
if [[ -f "$HOME/.zshrc" ]] && ! grep -q "$completion_dir" "$HOME/.zshrc"; then
print_info "Would you like to add the completion directory to your .zshrc?"
read -p "(y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "" >> "$HOME/.zshrc"
echo "# Smart Tree zsh completions" >> "$HOME/.zshrc"
echo "fpath=(\"$completion_dir\" \$fpath)" >> "$HOME/.zshrc"
echo "autoload -Uz compinit && compinit" >> "$HOME/.zshrc"
print_success "Added completion setup to .zshrc"
print_info "Run 'source ~/.zshrc' to enable completions in current session"
fi
fi
}
install_fish_completion() {
local completion_dir="$HOME/.config/fish/completions"
local completion_file="$completion_dir/st.fish"
mkdir -p "$completion_dir"
print_info "Installing fish completions to $completion_file"
if $BINARY_NAME --completions fish > "$completion_file" 2>/dev/null; then
print_success "Fish completions installed!"
print_info "Completions will be available in new fish sessions"
else
print_warning "Failed to generate fish completions"
fi
}
# Offer to install completions
# Only prompt if running in a TTY; otherwise skip interactive prompt.
if [[ -t 0 && -t 1 ]]; then
print_info ""
read -r -n 1 -p "Would you like to install shell completions for $BINARY_NAME? (y/n): " REPLY
printf "\n"
else
# Non-interactive session: do not prompt
REPLY="n"
fi
if [[ $REPLY =~ ^[Yy]$ ]]; then
install_shell_completions
else
print_info "You can install completions later with: $BINARY_NAME --completions <shell>"
fi
# Elvis has left the building! 🎸
print_success "Thank you, thank you very much! Smart Tree is ready to rock! 🌳🎸"