yams/install.sh
2025-08-13 13:03:55 -04:00

382 lines
No EOL
11 KiB
Bash
Executable file

#!/bin/bash
# YAMS Installation Script - Downloads Pre-Built Binaries
# This script downloads pre-built binaries from GitHub Releases (built with Conan)
# Usage: curl -fsSL https://raw.githubusercontent.com/trvon/yams/main/install.sh | bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# GitHub repository configuration
GITHUB_USER="${YAMS_GITHUB_USER:-trvon}"
GITHUB_REPO="${YAMS_GITHUB_REPO:-yams}"
GITHUB_API_URL="https://api.github.com/repos/${GITHUB_USER}/${GITHUB_REPO}"
# Installation configuration
INSTALL_DIR="${YAMS_INSTALL_DIR:-$HOME/.local/bin}"
BIN_NAME="yams"
VERSION="${YAMS_VERSION:-latest}"
# Logging functions
info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
}
fatal() {
error "$1"
exit 1
}
# Detect system information
detect_system() {
local os arch suffix
# Detect OS
case "$(uname -s)" in
Darwin) os="macos" ;;
Linux) os="linux" ;;
*) fatal "Unsupported operating system: $(uname -s)" ;;
esac
# Detect architecture
case "$(uname -m)" in
x86_64|amd64) arch="x86_64" ;;
arm64|aarch64) arch="arm64" ;;
*) fatal "Unsupported architecture: $(uname -m)" ;;
esac
# Determine file suffix and asset pattern
if [[ "$os" == "macos" ]]; then
suffix="zip"
else
suffix="tar.gz"
fi
ASSET_PATTERN="${BIN_NAME}-*-${os}-${arch}.${suffix}"
SYSTEM_OS="$os"
SYSTEM_ARCH="$arch"
ARCHIVE_SUFFIX="$suffix"
info "Detected system: $os-$arch"
}
# Check for required dependencies
check_dependencies() {
local deps=("curl" "tar")
if [[ "$ARCHIVE_SUFFIX" == "zip" ]]; then
deps+=("unzip")
fi
for dep in "${deps[@]}"; do
if ! command -v "$dep" >/dev/null 2>&1; then
fatal "Required dependency '$dep' not found. Please install it and try again."
fi
done
}
# Get the latest release or specified version
get_release_info() {
local api_url
if [[ "$VERSION" == "latest" ]]; then
api_url="${GITHUB_API_URL}/releases/latest"
info "Fetching latest release information..."
else
api_url="${GITHUB_API_URL}/releases/tags/v${VERSION}"
info "Fetching release information for version $VERSION..."
fi
RELEASE_INFO=$(curl -fsSL "$api_url") || fatal "Failed to fetch release information from GitHub API"
RELEASE_VERSION=$(echo "$RELEASE_INFO" | grep '"tag_name":' | sed -E 's/.*"tag_name": "v?([^"]+)".*/\1/')
if [[ -z "$RELEASE_VERSION" ]]; then
fatal "Could not determine release version"
fi
info "Target version: $RELEASE_VERSION"
}
# Find and download the appropriate asset
download_asset() {
local download_url asset_name temp_dir checksum_url checksum_file
# Extract asset information from release JSON
download_url=$(echo "$RELEASE_INFO" | grep '"browser_download_url":' | grep "$ASSET_PATTERN" | head -1 | sed -E 's/.*"browser_download_url": "([^"]+)".*/\1/')
if [[ -z "$download_url" ]]; then
fatal "Could not find asset matching pattern: $ASSET_PATTERN"
fi
asset_name=$(basename "$download_url")
temp_dir=$(mktemp -d)
info "Downloading $asset_name..."
# Download with retry logic
local max_retries=3
local retry_count=0
while [[ $retry_count -lt $max_retries ]]; do
if curl -fsSL -o "$temp_dir/$asset_name" "$download_url"; then
break
fi
retry_count=$((retry_count + 1))
if [[ $retry_count -eq $max_retries ]]; then
rm -rf "$temp_dir"
fatal "Failed to download asset from $download_url after $max_retries attempts"
fi
warn "Download failed, retrying ($retry_count/$max_retries)..."
sleep 2
done
# Try to download and verify checksums if available
checksum_url="${download_url}.sha256"
checksum_file="$temp_dir/${asset_name}.sha256"
if curl -fsSL -o "$checksum_file" "$checksum_url" 2>/dev/null; then
info "Verifying checksum..."
if command -v sha256sum >/dev/null 2>&1; then
if (cd "$temp_dir" && sha256sum -c "${asset_name}.sha256" >/dev/null 2>&1); then
success "Checksum verification passed"
else
warn "Checksum verification failed, but continuing..."
fi
elif command -v shasum >/dev/null 2>&1; then
local expected_checksum actual_checksum
expected_checksum=$(awk '{print $1}' "$checksum_file")
actual_checksum=$(shasum -a 256 "$temp_dir/$asset_name" | awk '{print $1}')
if [[ "$expected_checksum" == "$actual_checksum" ]]; then
success "Checksum verification passed"
else
warn "Checksum verification failed, but continuing..."
fi
else
info "No checksum utility found, skipping verification"
fi
else
info "No checksum file found, skipping verification"
fi
DOWNLOADED_FILE="$temp_dir/$asset_name"
TEMP_DIR="$temp_dir"
success "Downloaded $asset_name"
}
# Extract the downloaded archive
extract_archive() {
local extract_dir="$TEMP_DIR/extract"
mkdir -p "$extract_dir"
info "Extracting archive..."
case "$ARCHIVE_SUFFIX" in
"tar.gz")
tar -xzf "$DOWNLOADED_FILE" -C "$extract_dir" || fatal "Failed to extract tar.gz archive"
;;
"zip")
unzip -q "$DOWNLOADED_FILE" -d "$extract_dir" || fatal "Failed to extract zip archive"
;;
*)
fatal "Unsupported archive format: $ARCHIVE_SUFFIX"
;;
esac
# Find the binary (it might be in a subdirectory)
BINARY_PATH=$(find "$extract_dir" -name "$BIN_NAME" -type f | head -1)
if [[ -z "$BINARY_PATH" ]] || [[ ! -f "$BINARY_PATH" ]]; then
fatal "Could not find $BIN_NAME binary in extracted archive"
fi
success "Extracted binary: $BINARY_PATH"
}
# Install the binary
install_binary() {
info "Installing $BIN_NAME to $INSTALL_DIR..."
# Create install directory if it doesn't exist
mkdir -p "$INSTALL_DIR"
# Copy binary and make executable
cp "$BINARY_PATH" "$INSTALL_DIR/$BIN_NAME"
chmod +x "$INSTALL_DIR/$BIN_NAME"
success "Installed $BIN_NAME to $INSTALL_DIR/$BIN_NAME"
}
# Setup shell completion (if available)
setup_completions() {
# Check if the binary supports shell completions
if "$INSTALL_DIR/$BIN_NAME" --help 2>/dev/null | grep -q "completion\|completions"; then
info "Setting up shell completions..."
# Try to setup completions for common shells
for shell in bash zsh fish; do
if command -v "$shell" >/dev/null 2>&1; then
case "$shell" in
bash)
comp_dir="$HOME/.local/share/bash-completion/completions"
mkdir -p "$comp_dir"
if "$INSTALL_DIR/$BIN_NAME" completion bash > "$comp_dir/$BIN_NAME" 2>/dev/null; then
info "Installed bash completions"
fi
;;
zsh)
comp_dir="$HOME/.local/share/zsh/site-functions"
mkdir -p "$comp_dir"
if "$INSTALL_DIR/$BIN_NAME" completion zsh > "$comp_dir/_$BIN_NAME" 2>/dev/null; then
info "Installed zsh completions"
fi
;;
fish)
comp_dir="$HOME/.config/fish/completions"
mkdir -p "$comp_dir"
if "$INSTALL_DIR/$BIN_NAME" completion fish > "$comp_dir/$BIN_NAME.fish" 2>/dev/null; then
info "Installed fish completions"
fi
;;
esac
fi
done
fi
}
# Verify installation
verify_installation() {
info "Verifying installation..."
if [[ ! -x "$INSTALL_DIR/$BIN_NAME" ]]; then
fatal "Installation verification failed: $INSTALL_DIR/$BIN_NAME is not executable"
fi
# Test that the binary runs
if ! "$INSTALL_DIR/$BIN_NAME" --version >/dev/null 2>&1; then
warn "Binary was installed but --version command failed. Installation may be incomplete."
return
fi
local installed_version
installed_version=$("$INSTALL_DIR/$BIN_NAME" --version 2>/dev/null | head -1 || echo "unknown")
success "Installation verified. Installed version: $installed_version"
}
# Add to PATH instructions
show_path_instructions() {
if ! echo "$PATH" | grep -q "$INSTALL_DIR"; then
warn "Installation directory $INSTALL_DIR is not in your PATH"
info "Add the following to your shell profile (~/.bashrc, ~/.zshrc, etc.):"
echo ""
echo " export PATH=\"$INSTALL_DIR:\$PATH\""
echo ""
info "Then restart your shell or run: source ~/.bashrc (or ~/.zshrc)"
fi
}
# Cleanup temporary files
cleanup() {
if [[ -n "${TEMP_DIR:-}" ]] && [[ -d "$TEMP_DIR" ]]; then
rm -rf "$TEMP_DIR"
fi
}
# Main installation function
main() {
info "YAMS Installation Script"
info "========================"
info "Downloads pre-built binaries from GitHub Releases"
info ""
# Setup cleanup trap
trap cleanup EXIT
# Detect system and check dependencies
detect_system
check_dependencies
# Get release information and download
get_release_info
download_asset
extract_archive
# Install binary
install_binary
setup_completions
verify_installation
# Show final instructions
info ""
success "🎉 YAMS $RELEASE_VERSION has been successfully installed!"
info ""
info "Usage: $BIN_NAME --help"
show_path_instructions
info ""
info "Thank you for installing YAMS!"
}
# Handle command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--version)
VERSION="$2"
shift 2
;;
--install-dir)
INSTALL_DIR="$2"
shift 2
;;
--help)
cat << EOF
YAMS Installation Script
Usage: $0 [OPTIONS]
Options:
--version VERSION Install specific version (default: latest)
--install-dir DIR Installation directory (default: ~/.local/bin)
--help Show this help message
Environment Variables:
YAMS_VERSION Version to install (default: latest)
YAMS_INSTALL_DIR Installation directory (default: ~/.local/bin)
YAMS_GITHUB_USER GitHub username (default: trvon)
YAMS_GITHUB_REPO GitHub repository (default: yams)
Examples:
$0 # Install latest version
$0 --version 1.0.0 # Install specific version
$0 --install-dir /usr/local/bin # Install to system directory
EOF
exit 0
;;
*)
fatal "Unknown option: $1. Use --help for usage information."
;;
esac
done
# Run main installation
main