2026-04-05 23:34:27 +08:00
#!/bin/bash
# ============================================================
2026-04-17 00:22:17 +08:00
# Bookworm Smart Assistant - macOS 全自动安装 v2.3.0
2026-04-05 23:34:27 +08:00
#
# 用法 (任选一种):
# 方式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 ""
2026-04-17 00:22:17 +08:00
echo -e " ${ BOLD } 全自动安装 v2.3.0 — macOS ${ NC } "
2026-04-05 23:34:27 +08:00
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 "未找到安装脚本, 执行基础配置..."
2026-04-06 14:18:13 +08:00
# 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
}
2026-04-06 15:04:02 +08:00
# 解密工具: 优先 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
}
2026-04-06 14:18:13 +08:00
# 解密凭证 (先查缓存)
2026-04-05 23:34:27 +08:00
SECRETS_ENC = " $BOOT_DIR /secrets.enc "
2026-04-06 14:18:13 +08:00
if _kc_load 2>/dev/null; then
: # 缓存命中
2026-04-06 15:04:02 +08:00
elif [ -f " $SECRETS_ENC " ] ; then
2026-04-05 23:34:27 +08:00
echo ""
for attempt in 1 2 3; do
read -rs -p " 输入主密码解密凭证 (第 $attempt /3 次): " PASSWORD
echo ""
2026-04-06 15:04:02 +08:00
DECRYPTED = $( _do_decrypt " $PASSWORD " " $SECRETS_ENC " ) || true
2026-04-05 23:34:27 +08:00
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 "
2026-04-06 14:18:13 +08:00
DECRYPTED = ""
echo ""
read -p " 今日内免密启动? (y/n): " _cache_yn
[ " $_cache_yn " = "y" ] || [ " $_cache_yn " = "Y" ] && _kc_save
2026-04-05 23:34:27 +08:00
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 " << 'ALIA SES'
# Bookworm Portable aliases
alias bw = 'cd ~/bookworm-boot && NO_PROXY="bww.letcareme.com,code.letcareme.com,localhost,127.0.0.1" claude'
2026-04-06 14:05:29 +08:00
alias bw-update= 'cd ~/bookworm-boot && git pull && cd ~/.claude && git pull && echo "Updated!"'
2026-04-05 23:34:27 +08:00
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