Files
dev/hanzo.sh
T
zooqueen 0cc10f99aa feat(installer): add cli component to install hanzo Python CLI
Adds install_hanzo_cli() that uses uv/pip to install hanzo[all] from
PyPI. The cli component is now included in the default install
(dev,cli,mcp) and in 'all'. Ensures Python and uv are available
before installing.
2026-02-21 13:47:17 -08:00

1253 lines
35 KiB
Bash
Executable File

#!/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 [email protected] 2>/dev/null || true
brew link [email protected] --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 "$@"