#!/bin/bash # Graylog AI CLI Installer # # Usage: # curl -fsSL https://updater.ai.torch.sh/install.sh | sudo bash # # Installation: # - Requires root/sudo privileges # - Installs binary to /usr/local/bin/graylog-ai # - /usr/local/bin is the standard location for locally-installed binaries # - Always in PATH on all Unix systems # # Environment variables: # INSTALL_DIR - Binary install 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://updater.ai.torch.sh}" BINARY_NAME="graylog-ai" VERSION="${VERSION:-latest}" SKIP_CHECKSUM="${SKIP_CHECKSUM:-0}" # INSTALL_DIR is set after we verify root privileges # 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 # Require root/sudo privileges if [ "$(id -u)" -ne 0 ]; then fatal "This installer requires root privileges. Please run with sudo: curl -fsSL https://updater.ai.torch.sh/install.sh | sudo bash" fi INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}" info "Installing to $INSTALL_DIR" } # 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" ;; aarch64|arm64) PLATFORM="linux" BINARY_SUFFIX="linux-arm64" ;; *) fatal "Unsupported Linux architecture: $arch (only x86_64 and arm64 are 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" # Create install directory if needed if [ ! -d "$INSTALL_DIR" ]; then info "Creating $INSTALL_DIR..." mkdir -p "$INSTALL_DIR" fi info "Installing to $install_path..." cp "$DOWNLOADED_BINARY" "$install_path" chmod +x "$install_path" # macOS: Remove quarantine attribute (binary is unsigned) if [ "$PLATFORM" = "macos" ]; then info "Removing macOS quarantine attribute (unsigned binary)..." xattr -d com.apple.quarantine "$install_path" 2>/dev/null || true 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 INSTALLED_PATH="$install_path" } # 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 "Binary location: $INSTALLED_PATH" echo "" echo "To update in the future:" echo " sudo $BINARY_NAME update" echo "" } # Install managed configuration (no authentication - that happens on first use) install_config() { echo "" info "Installing managed configuration..." # Run the binary - it will install managed config (no browser auth) if "$INSTALLED_PATH" --managed; then success "Managed configuration installed" echo "" echo "Authentication will happen automatically when you first use Claude Code." else warn "Failed to install managed configuration" echo "You can install later by running: sudo $BINARY_NAME --managed" 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 install_config } main "$@"