#!/bin/bash # Graylog AI CLI Installer # Usage: sudo curl -fsSL https://graylog-ai.dev.torch.sh/install.sh | bash # # Note: sudo is recommended for installation to /usr/local/bin and to # remove macOS quarantine attributes (the binary is unsigned). # # Environment variables: # INSTALL_DIR - Installation directory (default: /usr/local/bin) # VERSION - Specific version to install (default: latest) # SKIP_CHECKSUM - Set to 1 to skip checksum verification (not recommended) set -euo pipefail # Configuration BASE_URL="${BASE_URL:-https://graylog-ai.dev.torch.sh}" BINARY_NAME="graylog-ai" INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}" VERSION="${VERSION:-latest}" SKIP_CHECKSUM="${SKIP_CHECKSUM:-0}" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Helper functions info() { echo -e "${BLUE}[INFO]${NC} $1" } success() { echo -e "${GREEN}[OK]${NC} $1" } warn() { echo -e "${YELLOW}[WARN]${NC} $1" } error() { echo -e "${RED}[ERROR]${NC} $1" >&2 } fatal() { error "$1" exit 1 } # Check for required commands check_requirements() { local missing=() for cmd in curl; do if ! command -v "$cmd" &> /dev/null; then missing+=("$cmd") fi done # Need either shasum (macOS) or sha256sum (Linux) if ! command -v shasum &> /dev/null && ! command -v sha256sum &> /dev/null; then missing+=("shasum or sha256sum") fi if [ ${#missing[@]} -ne 0 ]; then fatal "Missing required commands: ${missing[*]}" fi } # Detect OS and architecture detect_platform() { local os arch os="$(uname -s)" arch="$(uname -m)" case "$os" in Linux) case "$arch" in x86_64|amd64) PLATFORM="linux" BINARY_SUFFIX="linux-x86_64" ;; *) fatal "Unsupported Linux architecture: $arch (only x86_64 is supported)" ;; esac ;; Darwin) # macOS universal binary supports both Intel and Apple Silicon PLATFORM="macos" BINARY_SUFFIX="macos-universal" ;; MINGW*|MSYS*|CYGWIN*) fatal "Windows is not supported via this installer. Please download from GitHub releases." ;; *) fatal "Unsupported operating system: $os" ;; esac info "Detected platform: $os ($arch)" } # Get version info get_version_info() { info "Fetching version information..." local version_url="$BASE_URL/latest/version.json" local version_json version_json=$(curl -fsSL "$version_url") || fatal "Failed to fetch version info from $version_url" # Parse version info (simple parsing without jq dependency) RELEASE_VERSION=$(echo "$version_json" | grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d'"' -f4) if [ -z "$RELEASE_VERSION" ]; then fatal "Failed to parse version from version.json" fi info "Latest version: $RELEASE_VERSION" } # Download binary download_binary() { local binary_url="$BASE_URL/latest/$BINARY_NAME-$BINARY_SUFFIX" local checksum_url="$BASE_URL/latest/checksums.txt" # Create temp directory TEMP_DIR=$(mktemp -d) trap 'rm -rf "$TEMP_DIR"' EXIT local temp_binary="$TEMP_DIR/$BINARY_NAME" local temp_checksums="$TEMP_DIR/checksums.txt" info "Downloading $BINARY_NAME v$RELEASE_VERSION for $PLATFORM..." curl -fsSL -o "$temp_binary" "$binary_url" || fatal "Failed to download binary from $binary_url" # Download checksums if [ "$SKIP_CHECKSUM" != "1" ]; then info "Downloading checksums..." curl -fsSL -o "$temp_checksums" "$checksum_url" || fatal "Failed to download checksums from $checksum_url" # Verify checksum verify_checksum "$temp_binary" "$temp_checksums" else warn "Skipping checksum verification (SKIP_CHECKSUM=1)" fi DOWNLOADED_BINARY="$temp_binary" } # Verify SHA256 checksum verify_checksum() { local binary_file="$1" local checksums_file="$2" local binary_filename="$BINARY_NAME-$BINARY_SUFFIX" info "Verifying SHA256 checksum..." # Get expected checksum from checksums file local expected_checksum expected_checksum=$(grep "$binary_filename" "$checksums_file" | awk '{print $1}') if [ -z "$expected_checksum" ]; then fatal "Could not find checksum for $binary_filename in checksums.txt" fi # Calculate actual checksum local actual_checksum if command -v sha256sum &> /dev/null; then actual_checksum=$(sha256sum "$binary_file" | awk '{print $1}') else # macOS uses shasum actual_checksum=$(shasum -a 256 "$binary_file" | awk '{print $1}') fi if [ "$expected_checksum" != "$actual_checksum" ]; then error "Checksum mismatch!" error " Expected: $expected_checksum" error " Actual: $actual_checksum" fatal "Binary verification failed. The download may be corrupted." fi success "Checksum verified" } # Install binary install_binary() { local install_path="$INSTALL_DIR/$BINARY_NAME" info "Installing to $install_path..." # Check if we need sudo if [ -w "$INSTALL_DIR" ]; then # Can write directly cp "$DOWNLOADED_BINARY" "$install_path" chmod +x "$install_path" else # Need elevated permissions if [ "$(id -u)" -eq 0 ]; then cp "$DOWNLOADED_BINARY" "$install_path" chmod +x "$install_path" else warn "Installation directory $INSTALL_DIR requires elevated permissions" sudo cp "$DOWNLOADED_BINARY" "$install_path" sudo chmod +x "$install_path" fi fi # macOS: Remove quarantine attribute (binary is unsigned) if [ "$PLATFORM" = "macos" ]; then info "Removing macOS quarantine attribute (unsigned binary)..." if [ "$(id -u)" -eq 0 ]; then xattr -d com.apple.quarantine "$install_path" 2>/dev/null || true else sudo xattr -d com.apple.quarantine "$install_path" 2>/dev/null || true fi fi # Verify installation if [ -x "$install_path" ]; then success "Installed $BINARY_NAME to $install_path" else fatal "Installation failed - binary not executable at $install_path" fi } # Check if already installed and compare versions check_existing() { local install_path="$INSTALL_DIR/$BINARY_NAME" if [ -x "$install_path" ]; then local current_version current_version=$("$install_path" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo "unknown") if [ "$current_version" = "$RELEASE_VERSION" ]; then success "$BINARY_NAME v$RELEASE_VERSION is already installed and up to date" exit 0 else info "Currently installed: v$current_version" info "Upgrading to: v$RELEASE_VERSION" fi fi } # Print post-install instructions print_instructions() { echo "" success "Installation complete!" echo "" echo "To get started:" echo " $BINARY_NAME --help" echo "" echo "To authenticate:" echo " $BINARY_NAME" echo "" echo "To update in the future:" echo " sudo $BINARY_NAME update" echo "" # Check if install dir is in PATH if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then warn "$INSTALL_DIR is not in your PATH" echo "Add it to your shell profile:" echo " export PATH=\"\$PATH:$INSTALL_DIR\"" echo "" fi } # Main installation flow main() { echo "" echo " Graylog AI CLI Installer" echo " ========================" echo "" check_requirements detect_platform get_version_info check_existing download_binary install_binary print_instructions } main "$@"