#!/usr/bin/env bash # hanzo.sh - Full-stack Hanzo installation script # Supports macOS (arm64/x64) and Linux (x64/arm64) # # Usage: # curl -fsSL https://hanzo.sh | bash # curl -fsSL https://hanzo.sh | bash -s -- --components all # ./hanzo.sh [options] # # Options: # --dev Install development dependencies and build from source # --prod Production mode (use pre-built binaries) [default] # --components Comma-separated list: dev,cli,llm,chat,mcp,hanzod (default: dev,cli,mcp) # --no-docker Skip Docker-based services # --uninstall Remove Hanzo installation # --version Show version information # --quiet Minimal output (for scripted installs) # --force Skip confirmation prompts # --help Show this help message # # Environment: # HANZO_HOME Base directory (default: ~/.hanzo) # HANZO_VERSION Version to install (default: latest) set -euo pipefail # Determine script directory (works even when piped from curl) SCRIPT_DIR="${BASH_SOURCE[0]:-}" if [[ -n "$SCRIPT_DIR" ]] && [[ -f "$SCRIPT_DIR" ]]; then SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_DIR")" && pwd)" else SCRIPT_DIR="" fi # Colors for output (disabled if not a terminal or in quiet mode) if [[ -t 1 ]] && [[ "${QUIET:-false}" != "true" ]]; then RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' BLUE='\033[0;34m' MAGENTA='\033[0;35m' CYAN='\033[0;36m' NC='\033[0m' # No Color BOLD='\033[1m' else RED='' GREEN='' YELLOW='' BLUE='' MAGENTA='' CYAN='' NC='' BOLD='' fi # Configuration HANZO_HOME="${HANZO_HOME:-$HOME/.hanzo}" HANZO_BIN="${HANZO_BIN:-$HANZO_HOME/bin}" HANZO_CONFIG="${HANZO_CONFIG:-$HANZO_HOME/config}" HANZO_DATA="${HANZO_DATA:-$HANZO_HOME/data}" HANZO_LOGS="${HANZO_LOGS:-$HANZO_HOME/logs}" # Default options MODE="prod" COMPONENTS="dev,cli,mcp" SKIP_DOCKER=false VERBOSE=false QUIET=false FORCE=false UNINSTALL=false # Version info HANZO_VERSION="${HANZO_VERSION:-latest}" SCRIPT_VERSION="1.1.0" NODE_VERSION="20.18.1" # shellcheck disable=SC2034 PYTHON_VERSION="3.11" RUST_VERSION="stable" # Cleanup trap for partial installations CLEANUP_NEEDED=false cleanup_on_error() { if [[ "$CLEANUP_NEEDED" == "true" ]]; then log_warn "Installation interrupted. Partial installation may remain at $HANZO_HOME" log_info "Run './hanzo.sh --uninstall' to clean up, then retry." fi } trap cleanup_on_error EXIT # Logging functions log_info() { [[ "$QUIET" == "true" ]] || echo -e "${BLUE}[INFO]${NC} $*"; } log_success() { [[ "$QUIET" == "true" ]] || echo -e "${GREEN}[OK]${NC} $*"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } log_step() { [[ "$QUIET" == "true" ]] || echo -e "${MAGENTA}[STEP]${NC} ${BOLD}$*${NC}"; } log_debug() { [[ "$VERBOSE" == "true" ]] && echo -e "${CYAN}[DEBUG]${NC} $*" || true; } # Print banner print_banner() { [[ "$QUIET" == "true" ]] && return 0 echo -e "${CYAN}" cat << 'EOF' __ __ / / / /___ _____ ____ ____ / /_/ / __ `/ __ \/_ / / __ \ / __ / /_/ / / / / / /_/ /_/ / /_/ /_/\__,_/_/ /_/ /___/\____/ AI Development Platform EOF echo -e "${NC}" echo -e "${BOLD}Hanzo Full-Stack Installer v${SCRIPT_VERSION}${NC}" echo "" } # Show version show_version() { echo "hanzo.sh version $SCRIPT_VERSION" echo "Hanzo installer for version: $HANZO_VERSION" } # Show help show_help() { cat << 'EOF' Hanzo Full-Stack Installation Script USAGE: hanzo.sh [OPTIONS] curl -fsSL https://hanzo.sh | bash curl -fsSL https://hanzo.sh | bash -s -- [OPTIONS] OPTIONS: --dev Development mode (build from source) --prod Production mode (pre-built binaries) [default] --components LIST Components to install (comma-separated) Available: dev,cli,llm,chat,mcp,hanzod,all Default: dev,mcp --no-docker Skip Docker-based services (llm, chat) --uninstall Remove Hanzo installation --verbose Enable verbose output --quiet Minimal output (for scripted installs) --force Skip confirmation prompts --version Show version information --help Show this help message COMPONENTS: dev Hanzo Dev CLI - AI coding assistant cli Hanzo Python CLI - Unified platform CLI (hanzo command) llm LLM Gateway - Unified proxy for 100+ LLM providers chat Hanzo Chat - LibreChat fork with MCP integration mcp MCP Tools - Model Context Protocol server hanzod Hanzod - AI blockchain daemon ENVIRONMENT VARIABLES: HANZO_HOME Base directory (default: ~/.hanzo) HANZO_VERSION Version to install (default: latest) OPENAI_API_KEY OpenAI API key ANTHROPIC_API_KEY Anthropic API key EXAMPLES: # Quick install (dev CLI + MCP) ./hanzo.sh # Full stack with all components ./hanzo.sh --components all # Development setup (build from source) ./hanzo.sh --dev --components dev,mcp,llm # Production with specific components ./hanzo.sh --prod --components dev,llm,chat # Scripted install (quiet mode) curl -fsSL https://hanzo.sh | bash -s -- --quiet --force # Uninstall ./hanzo.sh --uninstall For more information: https://hanzo.ai/docs EOF } # Detect platform detect_platform() { local os arch os="$(uname -s)" arch="$(uname -m)" case "$os" in Darwin) PLATFORM="darwin" ;; Linux) PLATFORM="linux" ;; MINGW*|MSYS*|CYGWIN*) log_error "Windows is not directly supported. Use WSL2." log_info "Install WSL2: https://docs.microsoft.com/en-us/windows/wsl/install" exit 1 ;; *) log_error "Unsupported operating system: $os" exit 1 ;; esac case "$arch" in x86_64|amd64) ARCH="x64" ;; aarch64|arm64) ARCH="arm64" ;; *) log_error "Unsupported architecture: $arch" exit 1 ;; esac log_info "Detected platform: $PLATFORM-$ARCH" } # Uninstall Hanzo uninstall_hanzo() { log_step "Uninstalling Hanzo..." # Confirm uninstall if [[ "$FORCE" != "true" ]]; then echo -e "${YELLOW}This will remove:${NC}" echo " - $HANZO_HOME (all Hanzo data)" echo " - Global npm packages (@hanzo/dev, @hanzo/mcp)" echo "" read -rp "Are you sure? [y/N] " confirm if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then log_info "Uninstall cancelled" exit 0 fi fi # Remove npm packages if check_command npm; then log_info "Removing npm packages..." npm uninstall -g @hanzo/dev 2>/dev/null || true npm uninstall -g @hanzo/mcp 2>/dev/null || true fi # Stop Docker services if running if check_command docker; then for dir in "$HANZO_DATA/llm" "$HANZO_DATA/chat"; do if [[ -f "$dir/compose.yml" ]]; then log_info "Stopping services in $dir..." (cd "$dir" && docker compose down 2>/dev/null) || true fi done fi # Remove Hanzo home directory if [[ -d "$HANZO_HOME" ]]; then log_info "Removing $HANZO_HOME..." rm -rf "$HANZO_HOME" fi # Clean up shell config (careful not to break user's config) for rc_file in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.profile"; do if [[ -f "$rc_file" ]] && grep -q "# Hanzo AI - Added by hanzo.sh" "$rc_file"; then log_info "Cleaning shell config in $rc_file..." # Remove the Hanzo block (from comment to empty line or EOF) sed -i.bak '/# Hanzo AI - Added by hanzo.sh/,/^$/d' "$rc_file" 2>/dev/null || \ sed -i '' '/# Hanzo AI - Added by hanzo.sh/,/^$/d' "$rc_file" 2>/dev/null || true rm -f "${rc_file}.bak" fi done log_success "Hanzo has been uninstalled" log_info "Please restart your shell or run: exec \$SHELL" } # Check for required commands check_command() { command -v "$1" >/dev/null 2>&1 } # Install Homebrew on macOS install_homebrew() { if check_command brew; then log_success "Homebrew already installed" return 0 fi log_step "Installing Homebrew..." /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" # Add to PATH for current session if [[ "$ARCH" == "arm64" ]]; then eval "$(/opt/homebrew/bin/brew shellenv)" else eval "$(/usr/local/bin/brew shellenv)" fi log_success "Homebrew installed" } # Install system dependencies install_system_deps() { log_step "Installing system dependencies..." if [[ "$PLATFORM" == "darwin" ]]; then install_homebrew # Core dependencies local deps=(git curl wget jq openssl) # Development dependencies if [[ "$MODE" == "dev" ]]; then deps+=(cmake pkg-config protobuf) fi for dep in "${deps[@]}"; do if ! check_command "$dep"; then log_info "Installing $dep..." brew install "$dep" 2>/dev/null || true fi done log_success "System dependencies installed" elif [[ "$PLATFORM" == "linux" ]]; then # Detect package manager if check_command apt-get; then PKG_MANAGER="apt" sudo apt-get update -qq sudo apt-get install -y -qq git curl wget jq build-essential libssl-dev pkg-config if [[ "$MODE" == "dev" ]]; then sudo apt-get install -y -qq cmake protobuf-compiler fi elif check_command yum; then PKG_MANAGER="yum" sudo yum install -y git curl wget jq gcc gcc-c++ openssl-devel pkgconfig if [[ "$MODE" == "dev" ]]; then sudo yum install -y cmake protobuf-compiler fi elif check_command dnf; then PKG_MANAGER="dnf" sudo dnf install -y git curl wget jq gcc gcc-c++ openssl-devel pkgconfig if [[ "$MODE" == "dev" ]]; then sudo dnf install -y cmake protobuf-compiler fi elif check_command pacman; then PKG_MANAGER="pacman" sudo pacman -Sy --noconfirm git curl wget jq base-devel openssl pkgconf if [[ "$MODE" == "dev" ]]; then sudo pacman -S --noconfirm cmake protobuf fi else log_warn "Unknown package manager. Please install dependencies manually." fi log_success "System dependencies installed" fi } # Install Node.js install_nodejs() { log_step "Setting up Node.js..." if check_command node; then local current_version current_version=$(node --version | sed 's/v//' | cut -d. -f1) if [[ "$current_version" -ge 20 ]]; then log_success "Node.js $(node --version) already installed" return 0 fi fi if [[ "$PLATFORM" == "darwin" ]]; then # Use Homebrew for Node.js brew install node@20 2>/dev/null || true brew link node@20 --force --overwrite 2>/dev/null || true else # Use n for version management if ! check_command n; then curl -fsSL https://raw.githubusercontent.com/tj/n/master/bin/n | sudo bash -s lts fi sudo n "$NODE_VERSION" fi # Install pnpm if ! check_command pnpm; then log_info "Installing pnpm..." npm install -g pnpm 2>/dev/null || true fi log_success "Node.js $(node --version) ready" } # Install Python install_python() { log_step "Setting up Python..." if check_command python3; then local current_version current_version=$(python3 --version | awk '{print $2}' | cut -d. -f1,2) if [[ "$current_version" == "3.11" ]] || [[ "$current_version" == "3.12" ]]; then log_success "Python $current_version already installed" install_uv return 0 fi fi if [[ "$PLATFORM" == "darwin" ]]; then brew install python@3.11 2>/dev/null || true brew link python@3.11 --force --overwrite 2>/dev/null || true else if check_command apt-get; then sudo apt-get install -y -qq python3.11 python3.11-venv python3.11-dev elif check_command yum || check_command dnf; then sudo "${PKG_MANAGER:-dnf}" install -y python3.11 python3.11-devel fi fi install_uv log_success "Python ready" } # Install uv (fast Python package manager) install_uv() { if check_command uv; then log_success "uv already installed" return 0 fi log_info "Installing uv..." curl -LsSf https://astral.sh/uv/install.sh | sh export PATH="$HOME/.cargo/bin:$PATH" log_success "uv installed" } # Install Rust install_rust() { log_step "Setting up Rust..." if check_command rustc; then log_success "Rust $(rustc --version | awk '{print $2}') already installed" return 0 fi curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "$RUST_VERSION" # shellcheck source=/dev/null source "$HOME/.cargo/env" log_success "Rust $(rustc --version | awk '{print $2}') installed" } # Install Docker install_docker() { if [[ "$SKIP_DOCKER" == true ]]; then log_info "Skipping Docker installation (--no-docker flag)" return 0 fi log_step "Setting up Docker..." if check_command docker; then if docker info >/dev/null 2>&1; then log_success "Docker already installed and running" return 0 else log_warn "Docker installed but not running. Please start Docker." fi return 0 fi if [[ "$PLATFORM" == "darwin" ]]; then log_info "Installing Docker Desktop..." brew install --cask docker 2>/dev/null || true log_warn "Please open Docker Desktop to complete setup" else log_info "Installing Docker Engine..." curl -fsSL https://get.docker.com | sh sudo usermod -aG docker "$USER" 2>/dev/null || true sudo systemctl enable docker 2>/dev/null || true sudo systemctl start docker 2>/dev/null || true log_warn "You may need to log out and back in for Docker permissions" fi # Install docker-compose if needed if ! check_command docker-compose && ! docker compose version >/dev/null 2>&1; then if [[ "$PLATFORM" == "linux" ]]; then sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" \ -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose fi fi log_success "Docker setup complete" } # Create directory structure create_directories() { log_step "Creating Hanzo directories..." mkdir -p "$HANZO_HOME" mkdir -p "$HANZO_BIN" mkdir -p "$HANZO_CONFIG" mkdir -p "$HANZO_DATA" mkdir -p "$HANZO_LOGS" log_success "Directories created at $HANZO_HOME" } # Install Hanzo Dev CLI install_dev() { log_step "Installing Hanzo Dev CLI..." if [[ "$MODE" == "dev" ]]; then # Build from source - try multiple locations local source_dir="" # Priority 1: Script directory (if running from repo) if [[ -n "$SCRIPT_DIR" ]] && [[ -f "$SCRIPT_DIR/build-fast.sh" ]]; then source_dir="$SCRIPT_DIR" # Priority 2: Current directory elif [[ -f "./build-fast.sh" ]]; then source_dir="$(pwd)" # Priority 3: Common development path elif [[ -d "$HOME/work/hanzo/dev" ]] && [[ -f "$HOME/work/hanzo/dev/build-fast.sh" ]]; then source_dir="$HOME/work/hanzo/dev" fi if [[ -n "$source_dir" ]]; then log_info "Building from source at $source_dir..." local original_dir original_dir="$(pwd)" cd "$source_dir" if ./build-fast.sh; then local bin_path="./hanzo-dev/target/dev-fast/dev" if [[ -f "$bin_path" ]]; then cp "$bin_path" "$HANZO_BIN/dev" chmod +x "$HANZO_BIN/dev" log_success "Built and installed dev CLI from source" cd "$original_dir" return 0 else log_warn "Build completed but binary not found at $bin_path" fi else log_warn "Build failed" fi cd "$original_dir" fi log_warn "Source build not available, falling back to binary install" fi # Production mode: Install from npm (pre-built binary) log_info "Installing from npm..." if npm install -g @hanzo/dev 2>/dev/null; then log_success "Hanzo Dev CLI installed via npm" return 0 fi # Fallback: try with pnpm if check_command pnpm && pnpm add -g @hanzo/dev 2>/dev/null; then log_success "Hanzo Dev CLI installed via pnpm" return 0 fi # Final fallback: direct binary download log_info "Package managers failed, downloading binary directly..." if download_dev_binary; then log_success "Hanzo Dev CLI installed via direct download" return 0 fi log_error "Failed to install Hanzo Dev CLI" return 1 } # Download pre-built dev binary download_dev_binary() { local triple case "$PLATFORM-$ARCH" in darwin-arm64) triple="aarch64-apple-darwin" ;; darwin-x64) triple="x86_64-apple-darwin" ;; linux-x64) triple="x86_64-unknown-linux-musl" ;; linux-arm64) triple="aarch64-unknown-linux-musl" ;; *) log_error "Unsupported platform for binary download: $PLATFORM-$ARCH" return 1 ;; esac local base_url="https://github.com/hanzoai/dev/releases" local version_path="latest/download" # If specific version requested, use that if [[ "$HANZO_VERSION" != "latest" ]]; then version_path="download/v${HANZO_VERSION}" fi local url="${base_url}/${version_path}/code-${triple}.tar.gz" local tmp_dir tmp_dir="$(mktemp -d)" local tmp_file="${tmp_dir}/hanzo-dev.tar.gz" log_info "Downloading from $url..." log_debug "Temp directory: $tmp_dir" # Download with retry logic local max_retries=3 local retry=0 while [[ $retry -lt $max_retries ]]; do if curl -fsSL --connect-timeout 30 --max-time 300 "$url" -o "$tmp_file" 2>/dev/null; then break fi retry=$((retry + 1)) if [[ $retry -lt $max_retries ]]; then log_warn "Download failed, retrying ($retry/$max_retries)..." sleep 2 fi done if [[ ! -f "$tmp_file" ]]; then log_error "Failed to download binary after $max_retries attempts" rm -rf "$tmp_dir" return 1 fi # Verify it's a valid gzip file if ! file "$tmp_file" | grep -q "gzip"; then log_error "Downloaded file is not a valid gzip archive" log_debug "File type: $(file "$tmp_file")" rm -rf "$tmp_dir" return 1 fi # Extract to temp directory first log_info "Extracting binary..." if ! tar -xzf "$tmp_file" -C "$tmp_dir" 2>/dev/null; then log_error "Failed to extract archive" rm -rf "$tmp_dir" return 1 fi # Find the binary (handle various naming conventions) local binary_path="" for candidate in "$tmp_dir/code" "$tmp_dir/code-${triple}" "$tmp_dir/dev"; do if [[ -f "$candidate" ]]; then binary_path="$candidate" break fi done # Also check subdirectories if [[ -z "$binary_path" ]]; then binary_path="$(find "$tmp_dir" -type f -name 'code' -o -name 'dev' 2>/dev/null | head -1)" fi if [[ -z "$binary_path" ]] || [[ ! -f "$binary_path" ]]; then log_error "Binary not found in archive" log_debug "Archive contents: $(ls -la "$tmp_dir")" rm -rf "$tmp_dir" return 1 fi # Install to HANZO_BIN cp "$binary_path" "$HANZO_BIN/dev" chmod +x "$HANZO_BIN/dev" # Verify the binary works if ! "$HANZO_BIN/dev" --version >/dev/null 2>&1; then log_warn "Binary installed but version check failed (this may be ok)" fi # Cleanup rm -rf "$tmp_dir" log_debug "Binary installed to $HANZO_BIN/dev" return 0 } # Install MCP Tools install_mcp() { log_step "Installing MCP Tools..." npm install -g @hanzo/mcp 2>/dev/null || pnpm add -g @hanzo/mcp 2>/dev/null || { log_warn "Failed to install MCP from npm" } log_success "MCP Tools installed" } # Install LLM Gateway install_llm() { if [[ "$SKIP_DOCKER" == true ]]; then log_warn "Skipping LLM Gateway (requires Docker)" return 0 fi log_step "Installing LLM Gateway..." local llm_dir="$HANZO_DATA/llm" mkdir -p "$llm_dir" # Create docker-compose.yml for LLM Gateway cat > "$llm_dir/compose.yml" << 'YAML' services: llm: image: ghcr.io/hanzoai/llm:latest container_name: hanzo-llm ports: - "4000:4000" environment: - DATABASE_URL=postgresql://hanzo:hanzo@db:5432/llm - REDIS_HOST=redis - MASTER_KEY=${HANZO_LLM_MASTER_KEY:-} - OPENAI_API_KEY=${OPENAI_API_KEY:-} - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} depends_on: - db - redis restart: unless-stopped db: image: postgres:16-alpine container_name: hanzo-llm-db environment: - POSTGRES_USER=hanzo - POSTGRES_PASSWORD=hanzo - POSTGRES_DB=llm volumes: - llm_pgdata:/var/lib/postgresql/data restart: unless-stopped redis: image: redis:7-alpine container_name: hanzo-llm-redis restart: unless-stopped volumes: llm_pgdata: YAML # Create .env file cat > "$llm_dir/.env" << ENV HANZO_LLM_MASTER_KEY=${HANZO_LLM_MASTER_KEY:-$(openssl rand -hex 16)} OPENAI_API_KEY=${OPENAI_API_KEY:-} ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} ENV log_info "LLM Gateway configuration created at $llm_dir" log_info "Start with: cd $llm_dir && docker compose up -d" log_success "LLM Gateway installed" } # Install Hanzo Chat install_chat() { if [[ "$SKIP_DOCKER" == true ]]; then log_warn "Skipping Hanzo Chat (requires Docker)" return 0 fi log_step "Installing Hanzo Chat..." local chat_dir="$HANZO_DATA/chat" mkdir -p "$chat_dir" # Create docker-compose.yml for Chat cat > "$chat_dir/compose.yml" << 'YAML' services: api: image: ghcr.io/danny-avila/librechat-dev:latest container_name: hanzo-chat ports: - "3081:3081" environment: - HOST=0.0.0.0 - PORT=3081 - MONGO_URI=mongodb://mongodb:27017/HanzoChat - MEILI_HOST=http://meilisearch:7700 - RAG_PORT=8000 - RAG_API_URL=http://rag_api:8000 volumes: - ./images:/app/client/public/images - ./uploads:/app/uploads - ./logs:/app/api/logs - ./.env:/app/.env depends_on: - mongodb - meilisearch restart: unless-stopped mongodb: image: mongo:7 container_name: hanzo-chat-mongodb volumes: - chat_mongo:/data/db restart: unless-stopped meilisearch: image: getmeili/meilisearch:v1.12.3 container_name: hanzo-chat-meilisearch environment: - MEILI_NO_ANALYTICS=true volumes: - chat_meili:/meili_data restart: unless-stopped vectordb: image: ankane/pgvector:latest container_name: hanzo-chat-vectordb environment: - POSTGRES_DB=hanzo_chat - POSTGRES_USER=hanzo - POSTGRES_PASSWORD=hanzo volumes: - chat_pgvector:/var/lib/postgresql/data restart: unless-stopped rag_api: image: ghcr.io/danny-avila/librechat-rag-api-dev-lite:latest container_name: hanzo-chat-rag environment: - DB_HOST=vectordb - RAG_PORT=8000 depends_on: - vectordb restart: unless-stopped volumes: chat_mongo: chat_meili: chat_pgvector: YAML # Create .env file cat > "$chat_dir/.env" << ENV OPENAI_API_KEY=${OPENAI_API_KEY:-} ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} ENV log_info "Hanzo Chat configuration created at $chat_dir" log_info "Start with: cd $chat_dir && docker compose up -d" log_success "Hanzo Chat installed" } # Install Hanzo Python CLI install_hanzo_cli() { log_step "Installing Hanzo CLI (Python)..." # Ensure Python and uv are available install_python if check_command uv; then log_info "Installing via uv..." uv tool install "hanzo[all]" --force 2>/dev/null && { log_success "Hanzo CLI installed via uv" return 0 } # Fallback: uv pip uv pip install --system "hanzo[all]" 2>/dev/null && { log_success "Hanzo CLI installed via uv pip" return 0 } fi if check_command pip3; then log_info "Installing via pip3..." pip3 install "hanzo[all]" 2>/dev/null && { log_success "Hanzo CLI installed via pip3" return 0 } fi log_warn "Failed to install Hanzo CLI - install manually: pip install hanzo[all]" return 1 } # Install Hanzod install_hanzod() { log_step "Installing Hanzod..." if [[ "$MODE" == "dev" ]]; then # Build from source requires the hanzo-dev directory if [[ -d "/Users/z/work/hanzo/dev/hanzo-dev" ]]; then log_info "Building Hanzod from source..." cd /Users/z/work/hanzo/dev/hanzo-dev cargo build --release cp target/release/hanzod "$HANZO_BIN/" 2>/dev/null || true else log_warn "Hanzod source not found" fi else log_info "Hanzod binary distribution not yet available" log_info "Build from source with: ./hanzo.sh --dev --components hanzod" fi log_success "Hanzod setup complete" } # Setup shell integration setup_shell() { log_step "Setting up shell integration..." local shell_config case "$SHELL" in */zsh) shell_config="$HOME/.zshrc" ;; */bash) shell_config="$HOME/.bashrc" ;; *) shell_config="$HOME/.profile" ;; esac # Check if already configured (look for the comment marker) if grep -q "# Hanzo AI - Added by hanzo.sh" "$shell_config" 2>/dev/null; then log_info "Shell already configured in $shell_config" return 0 fi # Also check if HANZO_HOME is set via any means if grep -q "export HANZO_HOME=" "$shell_config" 2>/dev/null; then log_info "HANZO_HOME already set in $shell_config" return 0 fi # Add to shell config cat >> "$shell_config" << EOF # Hanzo AI - Added by hanzo.sh installer export HANZO_HOME="$HANZO_HOME" export PATH="\$HANZO_HOME/bin:\$PATH" EOF log_success "Shell integration added to $shell_config" log_info "Run: source $shell_config" } # Generate configuration generate_config() { log_step "Generating configuration..." # Main Hanzo config cat > "$HANZO_CONFIG/config.toml" << TOML # Hanzo AI Configuration # Generated by hanzo.sh installer [general] home = "$HANZO_HOME" log_level = "info" [dev] # Dev CLI settings model = "gpt-4.1" model_provider = "openai" approval_policy = "on-request" model_reasoning_effort = "medium" [llm] # LLM Gateway settings port = 4000 database_url = "postgresql://hanzo:hanzo@localhost:5432/llm" [chat] # Hanzo Chat settings port = 3081 [mcp] # MCP server settings enabled = true TOML log_success "Configuration generated at $HANZO_CONFIG/config.toml" } # Print completion message print_completion() { # In quiet mode, just output success indicator if [[ "$QUIET" == "true" ]]; then echo "OK" return 0 fi echo "" echo -e "${GREEN}${BOLD}Installation Complete!${NC}" echo "" echo "Installed components:" for comp in ${COMPONENTS//,/ }; do echo -e " ${GREEN}*${NC} $comp" done echo "" echo -e "${BOLD}Quick Start:${NC}" echo "" echo " 1. Reload your shell:" # Detect shell for accurate instructions case "$SHELL" in */zsh) echo " source ~/.zshrc" ;; */bash) echo " source ~/.bashrc" ;; *) echo " source ~/.profile # or restart your terminal" ;; esac echo "" echo " 2. Set API keys (recommended):" echo " export OPENAI_API_KEY=sk-..." echo " export ANTHROPIC_API_KEY=sk-ant-..." echo "" echo " 3. Start coding:" echo " dev" echo "" local step=4 if [[ "$COMPONENTS" == *"llm"* ]] && [[ "$SKIP_DOCKER" == false ]]; then echo " $step. Start LLM Gateway:" echo " cd $HANZO_DATA/llm && docker compose up -d" echo " Access at http://localhost:4000" echo "" step=$((step + 1)) fi if [[ "$COMPONENTS" == *"chat"* ]] && [[ "$SKIP_DOCKER" == false ]]; then echo " $step. Start Hanzo Chat:" echo " cd $HANZO_DATA/chat && docker compose up -d" echo " Open http://localhost:3081" echo "" fi echo -e "${BOLD}Documentation:${NC} https://hanzo.ai/docs" echo -e "${BOLD}Support:${NC} https://github.com/hanzoai/dev/issues" echo "" } # Parse command line arguments parse_args() { while [[ $# -gt 0 ]]; do case "$1" in --dev) MODE="dev" shift ;; --prod) MODE="prod" shift ;; --components) if [[ -z "${2:-}" ]]; then log_error "--components requires a value" exit 1 fi COMPONENTS="$2" shift 2 ;; --no-docker) SKIP_DOCKER=true shift ;; --verbose) VERBOSE=true export VERBOSE set -x shift ;; --quiet|-q) QUIET=true export QUIET shift ;; --force|-f) FORCE=true export FORCE shift ;; --uninstall) UNINSTALL=true shift ;; --version|-V) show_version exit 0 ;; --help|-h) show_help exit 0 ;; -*) log_error "Unknown option: $1" log_info "Run './hanzo.sh --help' for usage information" exit 1 ;; *) # Allow positional arguments for curl | bash -s -- usage log_warn "Ignoring unknown argument: $1" shift ;; esac done # Handle 'all' components if [[ "$COMPONENTS" == "all" ]]; then COMPONENTS="dev,cli,llm,chat,mcp,hanzod" fi # Validate components local valid_components="dev cli llm chat mcp hanzod" for component in ${COMPONENTS//,/ }; do # shellcheck disable=SC2076 if [[ ! " $valid_components " =~ " $component " ]]; then log_error "Unknown component: $component" log_info "Valid components: $valid_components" exit 1 fi done } # Verify installation verify_installation() { log_step "Verifying installation..." local failed=false # Check HANZO_BIN is in PATH or add it temporarily export PATH="$HANZO_BIN:$PATH" # Check dev CLI if installed if [[ "$COMPONENTS" == *"dev"* ]]; then if [[ -x "$HANZO_BIN/dev" ]]; then log_success "dev CLI binary found at $HANZO_BIN/dev" elif check_command dev; then log_success "dev CLI found in PATH" else log_warn "dev CLI not found - may need shell restart" failed=true fi fi # Check Hanzo CLI if installed if [[ "$COMPONENTS" == *"cli"* ]]; then if check_command hanzo; then log_success "Hanzo CLI found in PATH" else log_warn "Hanzo CLI not found - may need shell restart" failed=true fi fi # Check MCP if installed if [[ "$COMPONENTS" == *"mcp"* ]]; then if check_command hanzo-mcp 2>/dev/null || npm list -g @hanzo/mcp >/dev/null 2>&1; then log_success "MCP tools installed" else log_warn "MCP tools may not be fully installed" fi fi # Check Docker services if [[ "$SKIP_DOCKER" == false ]]; then if [[ "$COMPONENTS" == *"llm"* ]] && [[ -f "$HANZO_DATA/llm/compose.yml" ]]; then log_success "LLM Gateway config ready at $HANZO_DATA/llm" fi if [[ "$COMPONENTS" == *"chat"* ]] && [[ -f "$HANZO_DATA/chat/compose.yml" ]]; then log_success "Hanzo Chat config ready at $HANZO_DATA/chat" fi fi # Check config if [[ -f "$HANZO_CONFIG/config.toml" ]]; then log_success "Configuration file created" fi if [[ "$failed" == "true" ]]; then log_warn "Some verifications failed - installation may be incomplete" return 1 fi return 0 } # Main installation function main() { # Parse args first (before banner, to handle --quiet, --version, --help early) parse_args "$@" # Handle uninstall if [[ "$UNINSTALL" == "true" ]]; then uninstall_hanzo exit 0 fi print_banner log_info "Installation mode: $MODE" log_info "Components: $COMPONENTS" log_info "Skip Docker: $SKIP_DOCKER" [[ "$QUIET" != "true" ]] && echo "" # Mark that cleanup may be needed CLEANUP_NEEDED=true # Detect platform detect_platform # Create directories create_directories # Install base dependencies install_system_deps install_nodejs # Install Python if CLI component requested if [[ "$COMPONENTS" == *"cli"* ]]; then install_python fi # Install optional dependencies based on mode if [[ "$MODE" == "dev" ]]; then install_rust fi # Install Docker if needed if [[ "$COMPONENTS" == *"llm"* ]] || [[ "$COMPONENTS" == *"chat"* ]]; then install_docker fi # Install components local install_failed=false for component in ${COMPONENTS//,/ }; do case "$component" in dev) install_dev || install_failed=true ;; llm) install_llm || install_failed=true ;; chat) install_chat || install_failed=true ;; cli) install_hanzo_cli || install_failed=true ;; mcp) install_mcp || install_failed=true ;; hanzod) install_hanzod || install_failed=true ;; esac done if [[ "$install_failed" == "true" ]]; then log_warn "Some components failed to install" fi # Setup shell and config setup_shell generate_config # Verify installation verify_installation || true # Mark cleanup not needed (successful install) CLEANUP_NEEDED=false # Done print_completion } # Run main function main "$@"