bookworm-boot/Bookworm-OneClick-Mac.sh
bookworm f3a58e1c6d feat: v2.3.0 - Phase 4 改为用户直接输入中转站凭证
Win: Show-ApiKeyDialog GUI + Test-ApiKey 验证
Mac: 调用 ~/.claude/change-key.js (统一逻辑)
两者都保留旧授权码流程作为向后兼容

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 00:22:17 +08:00

367 lines
12 KiB
Bash

#!/bin/bash
# ============================================================
# Bookworm Smart Assistant - macOS 全自动安装 v2.3.0
#
# 用法 (任选一种):
# 方式1: 下载后运行
# chmod +x Bookworm-OneClick-Mac.sh && ./Bookworm-OneClick-Mac.sh
#
# 方式2: 一行命令远程安装
# curl -fsSL https://bookworm.letcareme.com/download/Bookworm-OneClick-Mac.sh | bash
#
# 兼容: macOS 12+ (Monterey/Ventura/Sonoma/Sequoia), Intel & Apple Silicon
# ============================================================
set -e
# ─── 颜色 ───
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
BLUE='\033[0;34m'; CYAN='\033[0;36m'; NC='\033[0m'; BOLD='\033[1m'
# ─── 配置 ───
GITEA_URL="https://code.letcareme.com/bookworm/bookworm-boot.git"
BOOT_DIR="$HOME/bookworm-boot"
CLAUDE_DIR="$HOME/.claude"
TOTAL_STEPS=8
info() { echo -e "${BLUE} [INFO]${NC} $1"; }
success() { echo -e "${GREEN} [OK]${NC} $1"; }
warn() { echo -e "${YELLOW} [!]${NC} $1"; }
fail() { echo -e "${RED} [!!]${NC} $1"; }
step() { echo -e "\n${BOLD} [$1/$TOTAL_STEPS]${NC} ${CYAN}$2${NC}"; }
# ─── Banner ───
echo ""
echo -e "${CYAN}"
echo " ____ _"
echo " | __ ) ___ ___ | | ____ _____ _ __ _ __ ___"
echo " | _ \\ / _ \\ / _ \\| |/ /\\ \\ /\\ / / _ \\| '__| '\`_ \` _ \\"
echo " | |_) | (_) | (_) | < \\ V V / (_) | | | | | | | |"
echo " |____/ \\___/ \\___/|_|\\_\\ \\_/\\_/ \\___/|_| |_| |_| |_|"
echo ""
echo -e " ${BOLD}全自动安装 v2.3.0 — macOS${NC}"
echo -e " ${BLUE}92 Skills | 18 Agents | 34 Hooks${NC}"
echo -e "${NC}"
# ============================================================
# 1. Homebrew
# ============================================================
step 1 "检查 Homebrew"
if ! command -v brew &>/dev/null; then
warn "Homebrew 未安装, 正在安装 (可能需要输入系统密码)..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Apple Silicon PATH
if [ -f /opt/homebrew/bin/brew ]; then
eval "$(/opt/homebrew/bin/brew shellenv)"
# 持久化到 shell profile
PROFILE="$HOME/.zprofile"
if ! grep -q 'homebrew' "$PROFILE" 2>/dev/null; then
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> "$PROFILE"
fi
fi
success "Homebrew 安装完成"
else
success "Homebrew $(brew --version | head -1 | awk '{print $2}')"
fi
# ============================================================
# 2. Node.js
# ============================================================
step 2 "检查 Node.js"
if ! command -v node &>/dev/null; then
info "通过 Homebrew 安装 Node.js LTS..."
brew install node
success "Node.js $(node -v) 安装完成"
else
success "Node.js $(node -v)"
fi
# ============================================================
# 3. Git
# ============================================================
step 3 "检查 Git"
if ! command -v git &>/dev/null; then
info "通过 Homebrew 安装 Git..."
brew install git
success "Git $(git --version | awk '{print $3}') 安装完成"
else
success "Git $(git --version | awk '{print $3}')"
fi
# ============================================================
# 4. OpenSSL (凭证解密需要)
# ============================================================
step 4 "检查 OpenSSL"
OPENSSL_CMD=""
for p in /opt/homebrew/opt/openssl/bin/openssl /usr/local/opt/openssl/bin/openssl openssl; do
if command -v "$p" &>/dev/null; then
OPENSSL_CMD="$p"
break
fi
done
if [ -z "$OPENSSL_CMD" ]; then
info "通过 Homebrew 安装 OpenSSL..."
brew install openssl
OPENSSL_CMD="/opt/homebrew/opt/openssl/bin/openssl"
success "OpenSSL 安装完成"
else
success "OpenSSL: $($OPENSSL_CMD version 2>/dev/null | head -1)"
fi
# ============================================================
# 5. Claude Code
# ============================================================
step 5 "检查 Claude Code"
if ! command -v claude &>/dev/null; then
info "通过 npm 安装 Claude Code..."
npm i -g @anthropic-ai/claude-code
success "Claude Code 安装完成"
else
success "Claude Code $(claude --version 2>/dev/null || echo 'installed')"
fi
# ============================================================
# 6. 代理检测
# ============================================================
step 6 "检测网络代理"
export NO_PROXY="bww.letcareme.com,code.letcareme.com,letcareme.com,localhost,127.0.0.1"
export no_proxy="$NO_PROXY"
PROXY_FOUND=""
# 环境变量
if [ -n "$HTTPS_PROXY" ] || [ -n "$https_proxy" ]; then
PROXY_FOUND="${HTTPS_PROXY:-$https_proxy}"
success "环境变量代理: $PROXY_FOUND"
fi
# macOS 系统代理
if [ -z "$PROXY_FOUND" ]; then
PROXY_HOST=$(scutil --proxy 2>/dev/null | grep "HTTPSProxy" | awk '{print $3}')
PROXY_PORT=$(scutil --proxy 2>/dev/null | grep "HTTPSPort" | awk '{print $3}')
if [ -n "$PROXY_HOST" ] && [ "$PROXY_HOST" != "0" ] && [ -n "$PROXY_PORT" ] && [ "$PROXY_PORT" != "0" ]; then
PROXY_FOUND="http://$PROXY_HOST:$PROXY_PORT"
export HTTPS_PROXY="$PROXY_FOUND"
export HTTP_PROXY="$PROXY_FOUND"
success "macOS 系统代理: $PROXY_FOUND"
fi
fi
# 常见端口扫描
if [ -z "$PROXY_FOUND" ]; then
for PORT in 7890 7893 7891 1087 1080 8118; do
if nc -z -w1 127.0.0.1 $PORT 2>/dev/null; then
PROXY_FOUND="http://127.0.0.1:$PORT"
export HTTPS_PROXY="$PROXY_FOUND"
export HTTP_PROXY="$PROXY_FOUND"
success "本地代理端口: $PORT"
break
fi
done
fi
if [ -z "$PROXY_FOUND" ]; then
warn "未检测到代理。在国内 Claude Code 可能无法启动。"
warn "请启动代理软件 (ClashX / Surge / V2Ray) 后重试。"
echo ""
read -p " 无代理继续? (y/n): " CONTINUE
if [ "$CONTINUE" != "y" ]; then exit 1; fi
fi
success "NO_PROXY: bww.letcareme.com,code.letcareme.com"
# ============================================================
# 7. 克隆/更新 Bookworm
# ============================================================
step 7 "同步 Bookworm 配置"
git config --global credential.helper osxkeychain 2>/dev/null || true
if [ -d "$BOOT_DIR/.git" ]; then
info "引导仓库已存在, 更新..."
cd "$BOOT_DIR"
git pull --ff-only 2>/dev/null || git pull
success "引导仓库已更新"
else
if [ -d "$BOOT_DIR" ]; then rm -rf "$BOOT_DIR"; fi
info "首次下载 (需输入 Gitea 用户名密码)..."
git clone "$GITEA_URL" "$BOOT_DIR"
cd "$BOOT_DIR"
success "引导仓库克隆完成"
fi
# 执行 macOS 安装脚本 (如存在)
if [ -f "$BOOT_DIR/install-mac.sh" ]; then
info "执行 install-mac.sh..."
bash "$BOOT_DIR/install-mac.sh"
elif [ -f "$BOOT_DIR/install.sh" ]; then
info "执行 install.sh..."
bash "$BOOT_DIR/install.sh"
else
# 回退: 手动执行核心配置步骤
info "未找到安装脚本, 执行基础配置..."
# Keychain 缓存
KC_SVC="bookworm-secrets"
KC_ACCT="$(whoami)"
_kc_load() {
local cached
cached=$(security find-generic-password -s "$KC_SVC" -a "$KC_ACCT" -w 2>/dev/null) || return 1
local expiry_date
expiry_date=$(echo "$cached" | head -1 | sed 's/EXPIRY=//')
[ "$expiry_date" != "$(date +%Y-%m-%d)" ] && { security delete-generic-password -s "$KC_SVC" -a "$KC_ACCT" 2>/dev/null; return 1; }
local count=0
while IFS= read -r line; do
[ -z "$line" ] && continue; [[ "$line" == EXPIRY=* ]] && continue
local key="${line%%=*}" value="${line#*=}"
key=$(echo "$key" | tr -d ' ')
[ -n "$key" ] && [ -n "$value" ] && export "$key=$value" && count=$((count + 1))
done <<< "$cached"
[ $count -gt 0 ] && [ -n "$ANTHROPIC_API_KEY" ] && { success "从 Keychain 缓存加载 $count 个凭证 (免密)"; return 0; }
return 1
}
_kc_save() {
local data="EXPIRY=$(date +%Y-%m-%d)"
for k in ANTHROPIC_API_KEY ANTHROPIC_BASE_URL GITHUB_PERSONAL_ACCESS_TOKEN SLACK_BOT_TOKEN ATLASSIAN_API_TOKEN BROWSERBASE_API_KEY FIRECRAWL_API_KEY; do
local v="${!k}"; [ -n "$v" ] && data="$data
$k=$v"
done
security add-generic-password -s "$KC_SVC" -a "$KC_ACCT" -w "$data" -U 2>/dev/null && \
success "凭证已缓存至今日 23:59 (下次免密)" || true
}
# 解密工具: 优先 node crypto-helper.js (BWENC1 格式), 回退 openssl
CRYPTO_HELPER="$BOOT_DIR/crypto-helper.js"
_do_decrypt() {
local pass="$1" enc="$2"
if command -v node &>/dev/null && [ -f "$CRYPTO_HELPER" ]; then
node "$CRYPTO_HELPER" decrypt "$pass" "$enc" 2>/dev/null
elif [ -n "$OPENSSL_CMD" ]; then
$OPENSSL_CMD enc -aes-256-cbc -d -pbkdf2 -iter 600000 -in "$enc" -pass pass:"$pass" 2>/dev/null
else
return 1
fi
}
# 解密凭证 (先查缓存)
SECRETS_ENC="$BOOT_DIR/secrets.enc"
if _kc_load 2>/dev/null; then
: # 缓存命中
elif [ -f "$SECRETS_ENC" ]; then
echo ""
for attempt in 1 2 3; do
read -rs -p " 输入主密码解密凭证 (第 $attempt/3 次): " PASSWORD
echo ""
DECRYPTED=$(_do_decrypt "$PASSWORD" "$SECRETS_ENC") || true
PASSWORD=""
if [ -n "$DECRYPTED" ]; then
while IFS= read -r line; do
[ -z "$line" ] && continue
key="${line%%=*}"
value="${line#*=}"
key=$(echo "$key" | tr -d ' ')
if [ -n "$key" ] && [ -n "$value" ]; then
export "$key=$value"
success "已注入: $key"
fi
done <<< "$DECRYPTED"
DECRYPTED=""
echo ""
read -p " 今日内免密启动? (y/n): " _cache_yn
[ "$_cache_yn" = "y" ] || [ "$_cache_yn" = "Y" ] && _kc_save
break
else
if [ $attempt -lt 3 ]; then
warn "密码错误, 剩余重试: $((3 - attempt))"
else
fail "3 次密码均错误, 凭证未解密"
warn "可稍后手动配置 API Key"
fi
fi
done
fi
# 克隆 .claude 配置仓库
CLAUDE_REPO="https://code.letcareme.com/bookworm/bookworm-config.git"
if [ -d "$CLAUDE_DIR/.git" ]; then
info "更新 .claude 配置..."
cd "$CLAUDE_DIR" && git pull 2>/dev/null || true
elif [ ! -f "$CLAUDE_DIR/CLAUDE.md" ]; then
info "克隆 .claude 配置..."
if [ -d "$CLAUDE_DIR" ]; then
mv "$CLAUDE_DIR" "$CLAUDE_DIR.bak.$(date +%s)"
fi
git clone --depth 1 "$CLAUDE_REPO" "$CLAUDE_DIR" 2>/dev/null || warn "配置仓库克隆失败"
fi
# 创建本地目录
for d in debug sessions cache backups telemetry memory projects; do
mkdir -p "$CLAUDE_DIR/$d" 2>/dev/null
done
success "基础配置完成"
fi
# ============================================================
# 8. 终端别名 + 完成
# ============================================================
step 8 "配置终端快捷命令"
# 检测 shell
SHELL_RC="$HOME/.zshrc"
if [ -n "$BASH_VERSION" ] && [ -f "$HOME/.bashrc" ]; then
SHELL_RC="$HOME/.bashrc"
fi
ALIAS_MARKER="# Bookworm Portable aliases"
if ! grep -q "$ALIAS_MARKER" "$SHELL_RC" 2>/dev/null; then
cat >> "$SHELL_RC" << 'ALIASES'
# Bookworm Portable aliases
alias bw='cd ~/bookworm-boot && NO_PROXY="bww.letcareme.com,code.letcareme.com,localhost,127.0.0.1" claude'
alias bw-update='cd ~/bookworm-boot && git pull && cd ~/.claude && git pull && echo "Updated!"'
ALIASES
success "已添加到 $SHELL_RC:"
info " bw — 启动 Bookworm"
info " bw-update — 更新 Bookworm"
else
success "终端别名已配置"
fi
# ─── 完成 ───
echo ""
echo -e "${GREEN} ============================================================${NC}"
echo -e "${GREEN} Bookworm Smart Assistant for macOS 安装完成!${NC}"
echo -e "${GREEN} ============================================================${NC}"
echo ""
echo -e " 已安装:"
echo -e " ${GREEN}[v]${NC} Homebrew ${GREEN}[v]${NC} Node.js $(node -v 2>/dev/null)"
echo -e " ${GREEN}[v]${NC} Git ${GREEN}[v]${NC} OpenSSL"
echo -e " ${GREEN}[v]${NC} Claude Code ${GREEN}[v]${NC} Bookworm (92 Skills)"
echo ""
echo -e " ${BOLD}启动方式:${NC}"
echo -e " 终端输入: ${CYAN}bw${NC}"
echo -e " 或: ${CYAN}cd ~/bookworm-boot && claude${NC}"
echo ""
echo -e " ${BOLD}更新:${NC}"
echo -e " 终端输入: ${CYAN}bw-update${NC}"
echo ""
# 询问是否立即启动
read -p " 立即启动 Bookworm? (y/n): " START_NOW
if [ "$START_NOW" = "y" ] || [ "$START_NOW" = "Y" ]; then
echo ""
info "正在启动 Claude Code..."
cd "$HOME"
export NO_PROXY="bww.letcareme.com,code.letcareme.com,letcareme.com,localhost,127.0.0.1"
exec claude
fi