# Bookworm Portable 项目经验与避坑指南 > 2026-04-01 | 从可行性评估到 E2E 验证的完整实战记录 --- ## 一、项目概要 **目标**: 让任意 Windows 电脑通过一行命令激活 Bookworm (97 Skills + 18 Agents + 28 Hooks) + 中转站 API **最终架构**: Gitea 私有仓 (code.letcareme.com) + AES-256 加密凭证 + 自动代理检测 + 一键安装脚本 **耗时**: 1 天 (评估 → 设计 → 三路专家审查 → P0/P1 修复 → 部署 → E2E 验证 → 踩坑修复) --- ## 二、关键决策与转折点 ### 决策 1: USB → Git 方案切换 - **初始方案**: U盘存储全部系统文件 + 加密凭证 - **问题**: Windows NTFS Junction 跨驱动器失败 (实测 Accessible: False)、USB IOPS 瓶颈 (hook 延迟 200-600ms)、体积 2.1GB - **最终方案**: Git + 模板渲染,USB 变为可选 (纯云端即可) - **教训**: 方案评估阶段多投入时间做实测,不要假设跨驱动器操作能工作 ### 决策 2: GitHub → 国内 Gitea - **问题**: 国内无 VPN 无法访问 GitHub - **方案**: 阿里云 ECS 自建 Gitea,国内直连 - **教训**: 面向国内用户的工具链必须全程可达,不能依赖海外服务 ### 决策 3: bookworm.letcareme.com → code.letcareme.com - **问题**: bookworm.letcareme.com 已被 Bookworm Web 项目 Nginx 配置占用 - **解决**: 换用 code.letcareme.com,申请新证书 - **教训**: 部署前检查域名占用,`grep -r "域名" /etc/nginx/` 先扫一遍 --- ## 三、必踩的坑 (按严重度排序) ### 坑 1: Claude Code 国内启动检查 (CRITICAL) ``` 现象: "Failed to connect to api.anthropic.com: ERR_BAD_REQUEST" 原因: Claude Code 启动时硬编码检查 api.anthropic.com,与 ANTHROPIC_BASE_URL 无关 影响: 国内无代理 = Claude Code 完全无法启动 解决: 必须有代理/VPN,且必须设 HTTPS_PROXY 环境变量 (Node.js 不读系统代理) 耗时: 2 小时排查 ``` ### 坑 2: Node.js 不读 Windows 系统代理 (CRITICAL) ``` 现象: PowerShell (Invoke-WebRequest) 能通,Claude Code 不通 原因: Node.js 只读 HTTPS_PROXY/HTTP_PROXY 环境变量,不读 Windows IE/系统代理设置 解决: 用 [System.Net.WebRequest]::DefaultWebProxy.GetProxy() 发现实际代理端口 然后 $env:HTTPS_PROXY = "http://127.0.0.1:端口" 教训: 系统代理 ≠ 进程代理,Node.js 应用必须显式设环境变量 ``` ### 坑 3: settings.json 中 ${VAR} 不展开 (CRITICAL) ``` 现象: "API Error: Invalid URL" 原因: settings.template.json 中 "ANTHROPIC_BASE_URL": "${ANTHROPIC_BASE_URL}" Claude Code 把 ${ANTHROPIC_BASE_URL} 当字面字符串,不展开环境变量 实际发送的 URL 是 "${ANTHROPIC_BASE_URL}/v1/messages" 解决: 从 settings.json env 段删除 ANTHROPIC_API_KEY 和 ANTHROPIC_BASE_URL 让 install.ps1 注入的进程环境变量直接生效 教训: Claude Code settings.json 的 ${} 语法行为未文档化,不要假设它能展开 ``` ### 坑 4: PowerShell 5.1 的 ?. 语法 (HIGH) ``` 现象: "表达式或语句中包含意外的标记 '?.Source'" 原因: ?. (null-conditional operator) 是 PS 7+ 专属语法,PS 5.1 不支持 解决: 改为 if ($x) { $x.Property } else { $null } 教训: 目标机可能只有 PS 5.1 (Windows 自带),所有脚本必须 PS 5.1 兼容 ``` ### 坑 5: PowerShell 5.1 的 git stderr 问题 (HIGH) ``` 现象: git clone 实际成功但脚本报 [ERROR] 克隆失败 原因: git 把进度信息写到 stderr,PS 5.1 的 $ErrorActionPreference="Stop" 把 stderr 输出当成终止性错误抛出异常 解决: git 命令前临时设 $ErrorActionPreference = "Continue" 用 $LASTEXITCODE 判断实际成功/失败 教训: PS 5.1 + 外部命令 + Stop 模式 = 必炸,所有外部命令都要处理 ``` ### 坑 6: UTF-8 BOM 问题 (HIGH) ``` 现象: "<# : 无法将 '<#' 项识别为 cmdlet" 或中文乱码 原因: Write 工具生成的文件无 BOM,PS 5.1 按 ANSI 解析 → 中文乱码 修复时 BOM 加了两次 → 双 BOM 导致 <# 注释块被当成代码 解决: 统一用 bash 添加一次 BOM: printf '\xEF\xBB\xBF' > tmp && cat file >> tmp 每次编辑后检查: head -c 3 file | xxd -p 应为 efbbbf 教训: 面向 PS 5.1 的 .ps1 文件必须有且仅有一个 UTF-8 BOM ``` ### 坑 7: openssl 路径不在默认位置 (MEDIUM) ``` 现象: "[ERROR] openssl 未找到" 原因: Git for Windows 安装在 D:\Git 而非默认 C:\Program Files\Git 解决: 搜索多个路径: C:\Program Files\Git, D:\Git, D:\Git\mingw64\bin 教训: 不要硬编码第三方软件路径,提供多路径搜索 ``` ### 坑 8: Gitea 端口被占用 (MEDIUM) ``` 现象: 部署脚本设 3000 端口但已被 my-oa-frontend 容器占用 解决: 改为 3300 端口 教训: 部署前 netstat -tlnp | grep 端口 先检查 ``` ### 坑 9: Nginx 反代不传 Authorization 头 (MEDIUM) ``` 现象: git push 通过 Nginx 反代时 404 原因: Nginx 默认传 Authorization 但旧的 bookworm-web.conf 先匹配了域名 解决: 换域名 + 添加 proxy_set_header Authorization $http_authorization 教训: 多个 Nginx 配置文件可能竞争同一个 server_name ``` ### 坑 10: git clone 私有仓需要凭证 (LOW) ``` 现象: install.ps1 中 git clone 静默失败 (无密码提示) 解决: 先手动 git clone 触发凭证输入 + git config credential.helper store 教训: 自动化脚本中的 git 操作需要预配置凭证 ``` --- ## 四、安全经验 ### 三路专家审查的价值 - **红队**: 发现 14 个攻击向量,其中 INSTALL_LOCK=false 成功率 95% - **代码审查**: 发现 5 个 Blocker + 13 个 Warning - **架构审查**: 发现仓库体积失控 (预估 50MB 实际 500MB+) - **交叉验证**: 三路独立发现的 openssl 命令行密码泄露问题,置信度极高 - **教训**: 多专家并行审查在 P0 修复前执行,投入产出比极高 ### 安全加固清单 (实际落地的) - [x] Gitea INSTALL_LOCK=true + 注册关闭 + CLI 创建管理员 - [x] Gitea 二进制 SHA256 校验 - [x] HTTPS (Let's Encrypt + Nginx 反代 + HSTS) - [x] fail2ban (5次/小时 → 封24h) - [x] Gitea 仅 127.0.0.1 监听 - [x] openssl 密码通过 stdin 管道 (不暴露进程列表) - [x] PBKDF2 600,000 迭代 (OWASP 2023) - [x] 凭证仅进程级环境变量 (不写磁盘) - [x] .gitignore 排除凭证/大文件/敏感数据 - [x] git add 前人工确认 + 大文件检测 - [x] stop.ps1 清理 Git Credential Manager 缓存 --- ## 五、PowerShell 跨版本兼容速查表 | PS 7+ 写法 | PS 5.1 兼容写法 | |---|---| | `$obj?.Property` | `if ($obj) { $obj.Property } else { $null }` | | `$cmd?.Source` | `$cmd = Get-Command x -EA 0; if ($cmd) { $cmd.Source }` | | `??` 空合并 | `if ($x) { $x } else { $default }` | | UTF-8 无 BOM | 必须有 BOM: `\xEF\xBB\xBF` | | `$ErrorActionPreference="Stop"` + git | 临时改 `Continue`,用 `$LASTEXITCODE` 判断 | --- ## 六、代理/VPN 检测方法速查 ```powershell # 最可靠: .NET DefaultWebProxy (覆盖所有系统代理) [System.Net.WebRequest]::DefaultWebProxy.GetProxy("https://api.anthropic.com") # IE 注册表 Get-ItemProperty "HKCU:\...\Internet Settings" | Select ProxyServer, ProxyEnable # 端口扫描 (Clash 7890, V2Ray 10808, 快柠檬 10792) Test-NetConnection 127.0.0.1 -Port 7890 ``` --- ## 七、项目最终产出 ``` 13 个文件 (bookworm-portable/) 2 个 Gitea 仓库 (bookworm-config 514文件/14MB, bookworm-boot 6文件) 3 个 ECS 部署脚本 (Gitea + HTTPS + 防火墙) 7 项 P0 安全修复 4 项 P1 安全加固 1 个保姆式 HTML 教程 1 个快速参考 TXT 手册 1 份经验避坑指南 (本文档) E2E 验证: 远程 Windows Server → 从零安装 → Claude Code 启动成功 ``` --- ## 八、如果重新来过,我会... 1. **一开始就做 PS 5.1 兼容测试** — 不假设目标机有 PS 7 2. **一开始就加代理检测** — 国内 Claude Code 的核心门槛 3. **不在 settings.json env 段放 ${} 变量** — 行为未文档化 4. **先检查域名和端口占用** — 再写部署脚本 5. **三路专家审查提前到设计阶段** — 而非代码写完后再审 --- *Bookworm Portable v1.2 | code.letcareme.com | 2026-04-01*