#!/usr/bin/env bash # activate.sh — One-command AI toolchain configurator # Installs: Claude Code · Codex · Gemini CLI configs + shell functions # # Usage: # bash <(curl -fsSL https://sync.scuzhsl.cn) # auto # bash <(curl -fsSL https://sync.scuzhsl.cn) --force # re-apply bashrc block too # bash <(curl -fsSL https://sync.scuzhsl.cn) --skip-dirs # only configs, skip skills/agents set -euo pipefail BASE_URL="https://sync.scuzhsl.cn" FORCE=0 SKIP_DIRS=0 for arg in "$@"; do case "$arg" in --force) FORCE=1 ;; --skip-dirs) SKIP_DIRS=1 ;; esac done # ---- Colors ---- BOLD='\033[1m'; CYAN='\033[0;36m'; BLUE='\033[0;34m' GREEN='\033[0;32m'; YELLOW='\033[1;33m'; DIM='\033[2m'; RESET='\033[0m' banner() { echo -e "${BOLD}${CYAN}" echo "╔══════════════════════════════════╗" echo "║ Server Config Activator ║" echo "╚══════════════════════════════════╝" echo -e "${RESET}" } section() { echo -e "\n${BOLD}${BLUE}▶ $1${RESET}"; } ok() { echo -e " ${GREEN}✓${RESET} $1"; } skip() { echo -e " ${DIM}– $1${RESET}"; } warn() { echo -e " ${YELLOW}⚠${RESET} $1"; } die() { echo -e " ${YELLOW}✗ ERROR:${RESET} $1" >&2; exit 1; } banner # ---- Download dotfiles.tar.gz ---- section "Downloading dotfiles" command -v curl >/dev/null 2>&1 || die "curl is required" command -v tar >/dev/null 2>&1 || die "tar is required" TMP=$(mktemp -d) trap 'rm -rf "$TMP"' EXIT curl -fsSL "$BASE_URL/dotfiles.tar.gz" -o "$TMP/dotfiles.tar.gz" \ || die "Failed to download dotfiles.tar.gz from $BASE_URL" tar xzf "$TMP/dotfiles.tar.gz" -C "$TMP" SRC="$TMP/dotfiles" ok "dotfiles extracted" # ---- Claude Code ---- section "Claude Code (~/.claude/)" mkdir -p "$HOME/.claude/plugins" cp "$SRC/claude/settings.json" "$HOME/.claude/settings.json" ok "~/.claude/settings.json" cp "$SRC/claude/CLAUDE.md" "$HOME/.claude/CLAUDE.md" ok "~/.claude/CLAUDE.md" [ -f "$SRC/claude/cli-tools.json" ] \ && cp "$SRC/claude/cli-tools.json" "$HOME/.claude/cli-tools.json" \ && ok "~/.claude/cli-tools.json" cp "$SRC/claude/plugins/known_marketplaces.json" "$HOME/.claude/plugins/known_marketplaces.json" ok "~/.claude/plugins/known_marketplaces.json" if [ "$SKIP_DIRS" = "0" ]; then for dir in agents skills workflows commands; do mkdir -p "$HOME/.claude/$dir" cp -r "$SRC/claude/$dir/." "$HOME/.claude/$dir/" COUNT=$(find "$HOME/.claude/$dir" -type f | wc -l) ok "~/.claude/$dir/ ($COUNT files)" done else skip "agents/skills/workflows/commands (--skip-dirs)" fi # ---- Codex ---- section "Codex (~/.codex/)" mkdir -p "$HOME/.codex" cp "$SRC/codex/config.toml" "$HOME/.codex/config.toml" cp "$SRC/codex/auth.json" "$HOME/.codex/auth.json" chmod 600 "$HOME/.codex/config.toml" "$HOME/.codex/auth.json" # Stamp current project path as trusted (if running from a real project dir) if [ "$PWD" != "$HOME" ] && [ -d "$PWD" ]; then PROJECT_KEY="[projects.\"$PWD\"]" if ! grep -qF "$PROJECT_KEY" "$HOME/.codex/config.toml" 2>/dev/null; then printf '\n%s\ntrust_level = "trusted"\n' "$PROJECT_KEY" >> "$HOME/.codex/config.toml" fi fi ok "~/.codex/config.toml" ok "~/.codex/auth.json" # ---- Gemini ---- section "Gemini CLI (~/.gemini/)" mkdir -p "$HOME/.gemini" cp "$SRC/gemini/settings.json" "$HOME/.gemini/settings.json" ok "~/.gemini/settings.json" warn "OAuth: run 'gemini' once to login (device-specific, not synced)" # ---- ~/.bashrc functions ---- section "Shell functions (~/.bashrc)" MARKER="# === co5 / cl5 / gemini" BASHRC="$HOME/.bashrc" # Remove legacy docker-worker AI config block if present if grep -qF '# >>> docker-worker AI config >>>' "$BASHRC" 2>/dev/null; then python3 -c " import sys, re with open(sys.argv[1]) as f: c = f.read() c = re.sub(r'\n# >>> docker-worker AI config >>>.*?# <<< docker-worker AI config <<<\n', '\n', c, flags=re.DOTALL) with open(sys.argv[1], 'w') as f: f.write(c) " "$BASHRC" ok "~/.bashrc: removed legacy docker-worker AI config block" fi if grep -qF "$MARKER" "$BASHRC" 2>/dev/null; then if [ "$FORCE" = "1" ]; then # Remove the old block entirely, then re-add fresh # Use python3 for reliable multi-line deletion python3 - "$BASHRC" <<'PYEOF' import sys, re path = sys.argv[1] with open(path) as f: content = f.read() content = re.sub( r'\n# === co5 / cl5 / gemini.*?# === end co5/cl5/gemini ===\n', '\n', content, flags=re.DOTALL ) with open(path, 'w') as f: f.write(content) PYEOF ok "~/.bashrc: removed old block (will re-add)" else skip "~/.bashrc co5/cl5/gemini already present (use --force to update)" fi fi if ! grep -qF "$MARKER" "$BASHRC" 2>/dev/null; then # Download the functions block from server curl -fsSL "$BASE_URL/bashrc-functions.sh" >> "$BASHRC" \ || warn "Could not download bashrc-functions.sh — add shell functions manually" ok "~/.bashrc: co5/cl5/gemini functions appended" echo -e " ${DIM}source ~/.bashrc (or open a new shell)${RESET}" fi # ================================================================ echo "" echo -e "${BOLD}${GREEN}✓ Activation complete!${RESET}" echo "" echo -e " ${DIM}source ~/.bashrc${RESET} load shell functions now" echo -e " ${DIM}co5${RESET} Codex gpt-5.2 (no proxy)" echo -e " ${DIM}cl5${RESET} Claude Code opus (HK proxy)" echo -e " ${DIM}gemini${RESET} Gemini CLI (US proxy)" echo ""