#!/usr/bin/env bash # OpenClaw Farm — Claude Code One-Line Installer (macOS / Linux) # Usage: curl -fsSL openclawfarm.com/install.sh | bash # # What it does: # 1. Check / install Git # 2. Install Claude Code (native install → npm fallback) # 3. Configure ANTHROPIC_BASE_URL → token.openclawfarm.com # 4. Verify installation # 5. Recommend optional tools (gh, VS Code) # # Pass API Key via env var: # KEY="sk-xxx" curl -fsSL openclawfarm.com/install.sh | bash set -euo pipefail # ── Constants ──────────────────────────────────────────────── BASE_URL="https://token.openclawfarm.com" NODE_VERSION="22.14.0" NPM_REGISTRY="https://registry.npmmirror.com" NODE_MIRROR="https://npmmirror.com/mirrors/node" NATIVE_INSTALL_URL="https://claude.ai/install.sh" NATIVE_TIMEOUT=30 # seconds — give up if GCS is slow/blocked # ── Colors ─────────────────────────────────────────────────── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' CYAN='\033[0;36m' DIM='\033[2m' BOLD='\033[1m' RESET='\033[0m' info() { printf " ${YELLOW}%s${RESET}\n" "$*"; } success() { printf " ${GREEN}%s${RESET}\n" "$*"; } warn() { printf " ${YELLOW}%s${RESET}\n" "$*"; } error() { printf " ${RED}%s${RESET}\n" "$*"; } # ── Platform detection ─────────────────────────────────────── OS="$(uname -s)" ARCH="$(uname -m)" case "$OS" in Darwin) PLATFORM="darwin" ;; Linux) PLATFORM="linux" ;; *) error "Unsupported OS: $OS"; exit 1 ;; esac case "$ARCH" in arm64|aarch64) ARCH_NAME="arm64" ;; x86_64|amd64) ARCH_NAME="x64" ;; *) error "Unsupported architecture: $ARCH"; exit 1 ;; esac # ── Helpers ────────────────────────────────────────────────── has_cmd() { command -v "$1" &>/dev/null; } version_major() { echo "$1" | grep -oE '[0-9]+' | head -1 } ensure_path() { local dir="$1" case ":$PATH:" in *":$dir:"*) ;; *) export PATH="$dir:$PATH" ;; esac } # Detect the user's shell rc file detect_rcfile() { local shell_name shell_name="$(basename "${SHELL:-/bin/bash}")" case "$shell_name" in zsh) echo "$HOME/.zshrc" ;; bash) if [[ -f "$HOME/.bash_profile" ]]; then echo "$HOME/.bash_profile" else echo "$HOME/.profile" fi ;; *) echo "$HOME/.profile" ;; esac } # Write an idempotent config block to the rc file write_rc_block() { local rcfile="$1" local marker_start="# --- OpenClaw Farm (Claude Code) ---" local marker_end="# --- /OpenClaw Farm ---" # Remove existing block if present if grep -qF "$marker_start" "$rcfile" 2>/dev/null; then if [[ "$PLATFORM" == "darwin" ]]; then sed -i '' "/$marker_start/,/$marker_end/d" "$rcfile" else sed -i "/$marker_start/,/$marker_end/d" "$rcfile" fi fi # Build the block local block="" block+="$marker_start"$'\n' block+="export ANTHROPIC_BASE_URL=\"$BASE_URL\""$'\n' if [[ -n "${API_KEY:-}" ]]; then block+="export ANTHROPIC_API_KEY=\"$API_KEY\""$'\n' fi block+="export CLAUDE_CODE_ENABLE_TELEMETRY=0"$'\n' block+="unset ANTHROPIC_AUTH_TOKEN"$'\n' block+="$marker_end"$'\n' printf '\n%s' "$block" >> "$rcfile" } # ── Banner ─────────────────────────────────────────────────── printf "\n" printf " ${RED}========================================${RESET}\n" printf " ${RED} OpenClaw Farm — Claude Code Installer${RESET}\n" printf " ${RED}========================================${RESET}\n" printf "\n" printf " ${DIM}Platform: %s/%s${RESET}\n" "$PLATFORM" "$ARCH_NAME" printf "\n" # ── Step 0: API Key ────────────────────────────────────────── API_KEY="${KEY:-}" if [[ -z "$API_KEY" ]]; then printf " ${CYAN}Enter your API Key (sk-xxx)${RESET}\n" printf " ${DIM}Leave empty to skip (configure later)${RESET}\n" if [[ -t 0 ]]; then printf " API Key: " read -r API_KEY else # stdin is a pipe (curl | bash), read from tty printf " API Key: " read -r API_KEY < /dev/tty || API_KEY="" fi fi if [[ -n "$API_KEY" ]]; then key_preview="${API_KEY:0:6}...${API_KEY: -4}" success "API Key: $key_preview" else warn "API Key: skipped" fi printf "\n" # ── Step 1/5: Git ──────────────────────────────────────────── info "[1/5] Checking Git..." if has_cmd git; then success " $(git --version)" else if [[ "$PLATFORM" == "darwin" ]]; then warn " Git not found. Installing Xcode Command Line Tools..." warn " A system dialog may appear — click 'Install' and wait." xcode-select --install 2>/dev/null || true until xcode-select -p &>/dev/null; do sleep 5 done if has_cmd git; then success " $(git --version)" else error " Git still not found after CLT install." error " Please install Xcode CLT manually: xcode-select --install" exit 1 fi else if has_cmd apt-get; then warn " Installing git via apt..." sudo apt-get update -qq && sudo apt-get install -y -qq git elif has_cmd yum; then warn " Installing git via yum..." sudo yum install -y git elif has_cmd dnf; then warn " Installing git via dnf..." sudo dnf install -y git elif has_cmd pacman; then warn " Installing git via pacman..." sudo pacman -Sy --noconfirm git else error " Cannot install git: no supported package manager found." error " Please install git manually and re-run this script." exit 1 fi if has_cmd git; then success " $(git --version)" else error " Git installation failed." exit 1 fi fi fi # ── Step 2/5: Install Claude Code ──────────────────────────── info "[2/5] Installing Claude Code..." CLAUDE_INSTALLED=false # Skip install if claude is already available if has_cmd claude; then CLAUDE_VER="$(claude --version 2>/dev/null || echo "unknown")" success " Claude Code $CLAUDE_VER already installed" CLAUDE_INSTALLED=true fi # Strategy A: Native install (official binary, no Node.js needed) if ! $CLAUDE_INSTALLED; then info " Trying native install (official binary)..." TMPDIR_NATIVE="$(mktemp -d)" if curl -fsSL --connect-timeout "$NATIVE_TIMEOUT" --max-time 120 \ "$NATIVE_INSTALL_URL" -o "$TMPDIR_NATIVE/install.sh" 2>/dev/null; then # Validate: must be a shell script, not an HTML challenge page if head -1 "$TMPDIR_NATIVE/install.sh" | grep -qE '^#!|^#|^set '; then info " Downloaded official installer, running..." if bash "$TMPDIR_NATIVE/install.sh" 2>/dev/null; then # Refresh PATH to pick up the newly installed binary ensure_path "$HOME/.claude/local/bin" ensure_path "$HOME/.local/bin" if has_cmd claude; then CLAUDE_INSTALLED=true success " Claude Code installed (native binary)" fi fi else warn " Official installer returned invalid content (blocked?)" fi fi rm -rf "$TMPDIR_NATIVE" 2>/dev/null || true if ! $CLAUDE_INSTALLED; then warn " Native install unavailable (network timeout or blocked)" info " Falling back to npm install via npmmirror..." fi fi # Strategy B: npm install (needs Node.js, uses npmmirror for China) if ! $CLAUDE_INSTALLED; then # Check / install Node.js >= 22 NEED_NODE=true if has_cmd node; then CURRENT_NODE="$(node --version 2>/dev/null || echo "")" CURRENT_MAJOR="$(version_major "$CURRENT_NODE")" if [[ "${CURRENT_MAJOR:-0}" -ge 22 ]]; then NEED_NODE=false success " Node.js $CURRENT_NODE (OK)" else warn " Node.js $CURRENT_NODE found, but need >= 22" fi fi if $NEED_NODE; then info " Installing Node.js v${NODE_VERSION} from npmmirror..." if [[ "$PLATFORM" == "darwin" ]]; then NODE_PKG="node-v${NODE_VERSION}-darwin-${ARCH_NAME}.tar.gz" else NODE_PKG="node-v${NODE_VERSION}-linux-${ARCH_NAME}.tar.xz" fi NODE_URL="${NODE_MIRROR}/v${NODE_VERSION}/${NODE_PKG}" TMPDIR_NODE="$(mktemp -d)" trap "rm -rf '$TMPDIR_NODE'" EXIT if curl -fSL --progress-bar "$NODE_URL" -o "$TMPDIR_NODE/$NODE_PKG"; then info " Extracting..." INSTALL_PREFIX="" NEED_SUDO="" if [[ -w /usr/local ]]; then INSTALL_PREFIX="/usr/local" elif has_cmd sudo; then INSTALL_PREFIX="/usr/local" NEED_SUDO=true else INSTALL_PREFIX="$HOME/.local/node" mkdir -p "$INSTALL_PREFIX" fi if [[ "$NODE_PKG" == *.tar.gz ]]; then tar xzf "$TMPDIR_NODE/$NODE_PKG" -C "$TMPDIR_NODE" else tar xJf "$TMPDIR_NODE/$NODE_PKG" -C "$TMPDIR_NODE" fi NODE_DIR="$(ls -d "$TMPDIR_NODE"/node-v*/)" if [[ "$INSTALL_PREFIX" == "$HOME/.local/node" ]]; then cp -R "$NODE_DIR"/* "$INSTALL_PREFIX/" ensure_path "$INSTALL_PREFIX/bin" elif [[ "${NEED_SUDO:-}" == "true" ]]; then sudo cp -R "$NODE_DIR"/{bin,include,lib,share} "$INSTALL_PREFIX/" 2>/dev/null || \ sudo cp -R "$NODE_DIR"/bin "$NODE_DIR"/lib "$INSTALL_PREFIX/" else cp -R "$NODE_DIR"/{bin,include,lib,share} "$INSTALL_PREFIX/" 2>/dev/null || \ cp -R "$NODE_DIR"/bin "$NODE_DIR"/lib "$INSTALL_PREFIX/" fi success " Node.js v${NODE_VERSION} installed to ${INSTALL_PREFIX}" elif [[ "$PLATFORM" == "darwin" ]] && has_cmd brew; then warn " npmmirror download failed, trying Homebrew..." brew install node@22 brew link --overwrite node@22 2>/dev/null || true success " Node.js installed via Homebrew" else error " Failed to install Node.js." error " Please install Node.js 22+ manually: https://nodejs.org" exit 1 fi ensure_path "/usr/local/bin" if ! has_cmd node; then error " node not found in PATH after installation." error " You may need to restart your terminal." exit 1 fi fi # npm install Claude Code info " Installing Claude Code via npm..." npm config set registry "$NPM_REGISTRY" NPM_PREFIX="$(npm config get prefix 2>/dev/null || echo "/usr/local")" if [[ -w "$NPM_PREFIX/lib" ]] || [[ -w "$NPM_PREFIX/lib/node_modules" ]] 2>/dev/null; then npm install -g @anthropic-ai/claude-code else warn " Using sudo for global npm install (prefix: $NPM_PREFIX)" sudo npm install -g @anthropic-ai/claude-code fi NPM_BIN="$(npm config get prefix 2>/dev/null)/bin" ensure_path "$NPM_BIN" success " Claude Code installed (npm)" fi # ── Step 3/5: Configure environment ───────────────────────── info "[3/5] Configuring environment..." RCFILE="$(detect_rcfile)" info " Shell config: $RCFILE" touch "$RCFILE" write_rc_block "$RCFILE" success " Config block written to $RCFILE" export ANTHROPIC_BASE_URL="$BASE_URL" export CLAUDE_CODE_ENABLE_TELEMETRY=0 unset ANTHROPIC_AUTH_TOKEN 2>/dev/null || true success " ANTHROPIC_BASE_URL = $BASE_URL" if [[ -n "${API_KEY:-}" ]]; then export ANTHROPIC_API_KEY="$API_KEY" success " ANTHROPIC_API_KEY = $key_preview" else warn " ANTHROPIC_API_KEY = (not set)" fi # ── Step 4/5: Verify ──────────────────────────────────────── info "[4/5] Verifying..." if has_cmd claude; then CLAUDE_VER="$(claude --version 2>/dev/null || echo "unknown")" success " Claude Code $CLAUDE_VER" else warn " 'claude' not in PATH yet. Restart your terminal, then run: claude" fi # ── Step 5/5: Recommended tools ───────────────────────────── info "[5/5] Recommended tools..." if has_cmd gh; then success " GitHub CLI: installed" elif [[ "$PLATFORM" == "darwin" ]] && has_cmd brew; then printf " ${DIM} Tip: brew install gh (GitHub CLI, for 'claude' git features)${RESET}\n" else printf " ${DIM} Tip: Install GitHub CLI → https://cli.github.com${RESET}\n" fi if has_cmd code; then success " VS Code: installed" else printf " ${DIM} Tip: Install VS Code → https://code.visualstudio.com${RESET}\n" fi # ── Done ───────────────────────────────────────────────────── printf "\n" printf " ${GREEN}========================================${RESET}\n" printf " ${GREEN} Installation Complete!${RESET}\n" printf " ${GREEN}========================================${RESET}\n" printf "\n" if [[ -z "${API_KEY:-}" ]]; then printf " ${YELLOW}Set API Key:${RESET}\n" printf " ${BOLD}export ANTHROPIC_API_KEY=\"sk-xxx\"${RESET}\n" printf " ${DIM}(add to $RCFILE to persist)${RESET}\n" printf "\n" fi printf " ${CYAN}Open a NEW terminal and run 'claude' to start.${RESET}\n" printf "\n"