#!/bin/sh # Ballistics CLI Installer # https://ballistics.zip # # Copyright (c) 2025 Ballistics Engine Project # Licensed under the MIT License (see https://ballistics.zip/LICENSE) # # This script installs the ballistics CLI tool to your local system. # It detects your operating system and architecture, downloads the appropriate # binary, and installs it to ~/.local/bin or a custom location. set -e # Configuration BALLISTICS_VERSION="${BALLISTICS_VERSION:-latest}" BALLISTICS_INSTALL_DIR="${BALLISTICS_INSTALL_DIR:-$HOME/.local/bin}" BALLISTICS_BASE_URL="https://storage.googleapis.com/ballistics-releases" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' # No Color # Helper functions info() { printf "${BLUE}info${NC}: %s\n" "$1" } success() { printf "${GREEN}success${NC}: %s\n" "$1" } warning() { printf "${YELLOW}warning${NC}: %s\n" "$1" >&2 } error() { printf "${RED}error${NC}: %s\n" "$1" >&2 exit 1 } # Detect the operating system detect_os() { local os case "$(uname -s)" in Linux*) os="linux" ;; Darwin*) os="macos" ;; FreeBSD*) os="freebsd" ;; NetBSD*) os="netbsd" ;; OpenBSD*) os="openbsd" ;; CYGWIN*|MINGW*|MSYS*) os="windows" ;; *) os="unknown" warning "Unknown operating system: $(uname -s)" ;; esac echo "$os" } # Detect the CPU architecture detect_arch() { local arch case "$(uname -m)" in x86_64|amd64) arch="x86_64" ;; i386|i686|i586) arch="x86" ;; aarch64|arm64) arch="aarch64" ;; armv7*|armv6*) arch="armv7" ;; *) arch="unknown" warning "Unknown architecture: $(uname -m)" ;; esac echo "$arch" } # Check if a command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Download a file download_file() { local url="$1" local output="$2" if command_exists curl; then curl -fsSL "$url" -o "$output" elif command_exists wget; then wget -q "$url" -O "$output" else error "Neither curl nor wget found. Please install one of them." fi } # Get the latest version from the server get_latest_version() { local version_url="${BALLISTICS_BASE_URL}/latest-version.txt" local version if command_exists curl; then version=$(curl -fsSL "$version_url" 2>/dev/null || echo "") elif command_exists wget; then version=$(wget -qO- "$version_url" 2>/dev/null || echo "") fi if [ -z "$version" ]; then # Fallback to a default version if we can't fetch the latest version="0.3.2" warning "Could not fetch latest version, using default: $version" fi echo "$version" } # Build the download URL for the binary build_download_url() { local os="$1" local arch="$2" local version="$3" # Construct platform string local platform="${os}-${arch}" # Handle special cases and platform mappings case "$platform" in "windows-x86_64") platform="windows-x86_64" ;; "macos-aarch64") platform="macos-aarch64" # Apple Silicon ;; "macos-x86_64") platform="macos-x86_64" # Intel Mac ;; "freebsd-x86_64") platform="freebsd-x86_64" ;; "freebsd-x86") platform="freebsd-x86" ;; "freebsd-aarch64") platform="freebsd-aarch64" ;; "netbsd-x86_64") platform="netbsd-x86_64" ;; "netbsd-x86") platform="netbsd-x86" ;; "netbsd-aarch64") platform="netbsd-aarch64" ;; "openbsd-x86_64") platform="openbsd-x86_64" ;; "openbsd-x86") platform="openbsd-x86" ;; "openbsd-aarch64") platform="openbsd-aarch64" ;; esac # Build URL local binary_name="ballistics-${version}-${platform}" if [ "$os" = "windows" ]; then binary_name="${binary_name}.exe" fi echo "${BALLISTICS_BASE_URL}/${version}/${binary_name}" } # Verify the downloaded binary (optional, if we provide checksums) verify_binary() { local binary_path="$1" local checksum_url="$2" if ! command_exists sha256sum && ! command_exists shasum; then warning "Cannot verify binary checksum (sha256sum/shasum not found)" return 0 fi # Download checksum file local checksum_file="/tmp/ballistics-checksum.txt" if ! download_file "$checksum_url" "$checksum_file" 2>/dev/null; then warning "Could not download checksum file, skipping verification" return 0 fi # Verify checksum local expected_checksum=$(cat "$checksum_file") local actual_checksum if command_exists sha256sum; then actual_checksum=$(sha256sum "$binary_path" | cut -d' ' -f1) else actual_checksum=$(shasum -a 256 "$binary_path" | cut -d' ' -f1) fi if [ "$expected_checksum" != "$actual_checksum" ]; then error "Checksum verification failed!" fi rm -f "$checksum_file" return 0 } # Show fallback instructions for unsupported platforms show_fallback_instructions() { local os="$1" local arch="$2" echo "" warning "Pre-compiled binaries are not available for $os-$arch" echo "" echo "You can still install ballistics by building from source:" echo "" echo " 1. Install Rust (if not already installed):" echo " ${CYAN}curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh${NC}" echo "" echo " 2. Clone and build ballistics:" echo " ${CYAN}git clone https://github.com/ajokela/ballistics-engine.git${NC}" echo " ${CYAN}cd ballistics-engine${NC}" echo " ${CYAN}cargo build --release${NC}" echo "" echo " 3. Install the binary:" echo " ${CYAN}cp target/release/ballistics $BALLISTICS_INSTALL_DIR/${NC}" echo "" echo "Or you can request support for your platform at:" echo " ${BLUE}https://github.com/ajokela/ballistics-engine/issues${NC}" echo "" echo "Please include the following information:" echo " OS: $(uname -s) ($os)" echo " Architecture: $(uname -m) ($arch)" echo " Full uname: $(uname -a)" echo "" exit 0 } # Main installation function install_ballistics() { info "Installing ballistics CLI tool..." # Detect platform local os=$(detect_os) local arch=$(detect_arch) info "Detected platform: $os-$arch" # Check if platform is supported if [ "$os" = "unknown" ] || [ "$arch" = "unknown" ]; then show_fallback_instructions "$os" "$arch" fi # Get version if [ "$BALLISTICS_VERSION" = "latest" ]; then BALLISTICS_VERSION=$(get_latest_version) fi info "Installing version: $BALLISTICS_VERSION" # Create installation directory if it doesn't exist if [ ! -d "$BALLISTICS_INSTALL_DIR" ]; then info "Creating installation directory: $BALLISTICS_INSTALL_DIR" mkdir -p "$BALLISTICS_INSTALL_DIR" fi # Build download URL local download_url=$(build_download_url "$os" "$arch" "$BALLISTICS_VERSION") info "Downloading from: $download_url" # Download binary to temp location local temp_binary="/tmp/ballistics-$$" if [ "$os" = "windows" ]; then temp_binary="${temp_binary}.exe" fi info "Downloading ballistics binary..." if ! download_file "$download_url" "$temp_binary" 2>/dev/null; then warning "Failed to download binary for $os-$arch" show_fallback_instructions "$os" "$arch" fi # Check if file was actually downloaded if [ ! -f "$temp_binary" ] || [ ! -s "$temp_binary" ]; then warning "Downloaded file is empty or missing" show_fallback_instructions "$os" "$arch" fi # Verify binary (optional) local checksum_url="${download_url}.sha256" verify_binary "$temp_binary" "$checksum_url" # Make binary executable chmod +x "$temp_binary" # Move to final location local final_binary="$BALLISTICS_INSTALL_DIR/ballistics" if [ "$os" = "windows" ]; then final_binary="${final_binary}.exe" fi info "Installing to: $final_binary" mv "$temp_binary" "$final_binary" # Verify installation if [ -f "$final_binary" ]; then success "ballistics CLI installed successfully!" # Check if install directory is in PATH case ":$PATH:" in *":$BALLISTICS_INSTALL_DIR:"*) success "Installation directory is already in your PATH" ;; *) warning "Installation directory is not in your PATH" echo "" echo "To add it to your PATH, run one of the following commands:" echo "" echo " # For bash:" echo " echo 'export PATH=\"\$PATH:$BALLISTICS_INSTALL_DIR\"' >> ~/.bashrc" echo " source ~/.bashrc" echo "" echo " # For zsh:" echo " echo 'export PATH=\"\$PATH:$BALLISTICS_INSTALL_DIR\"' >> ~/.zshrc" echo " source ~/.zshrc" echo "" echo " # For fish:" echo " fish_add_path $BALLISTICS_INSTALL_DIR" echo "" ;; esac # Test the binary echo "" info "Testing installation..." if "$final_binary" --version 2>/dev/null; then success "ballistics is working correctly!" echo "" echo "Run 'ballistics --help' to get started" else warning "Could not run ballistics --version. You may need to add the directory to your PATH first." fi else error "Installation failed: binary not found at $final_binary" fi } # Check for help flag if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then cat <