From d87b92319fd23a0642efb2634c11fa6fca99215e Mon Sep 17 00:00:00 2001 From: bookworm Date: Fri, 10 Apr 2026 03:20:39 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E6=B8=85=E7=90=86=E8=BF=87=E6=9C=9F?= =?UTF-8?q?=E6=96=87=E4=BB=B6=20(-2731=20=E8=A1=8C)=20+=20AuthGen=20?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除: - 5 个旧版 .bat 安装器 (已被 auto-setup.ps1 EXE 替代) - 4 个旧版 HTML 指南 (已被 guide-unified.html 替代) - encrypt-secrets.ps1 (已被 gen-authcode.js 替代) - lessons-learned.md (开发备忘, 非功能文件) - 6 个未提交测试 .enc + 备份文件 (本地清理) 新增: - AuthGen GUI: 生成成功后自动追加 authcode-history.log - AuthGen GUI: "推送到 Gitea" 按钮 (git add + commit + push 一键完成) - AuthGen GUI: $args→$nodeArgs (PowerShell 保留变量冲突) - AuthGen GUI: UTF-8 Process 编码 (修复 PS2EXE 中文乱码) - .gitignore: +authcode-history.log +Bookworm-AuthGen.exe Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 2 + Bookworm-AutoSetup.bat | 80 ---- Bookworm-Install.bat | 37 -- Bookworm-OneClick-Win10.bat | 266 ------------ Bookworm-Setup.bat | 163 -------- admin-authcode-gui.ps1 | 443 +++++++++++++------- download-panel.html | 85 ---- download.html | 125 ------ encrypt-secrets.ps1 | 177 -------- guide-mac.html | 788 ------------------------------------ guide.html | 787 ----------------------------------- lessons-learned.md | 206 ---------- 启动Bookworm-v3.bat | 17 - 13 files changed, 293 insertions(+), 2883 deletions(-) delete mode 100644 Bookworm-AutoSetup.bat delete mode 100644 Bookworm-Install.bat delete mode 100644 Bookworm-OneClick-Win10.bat delete mode 100644 Bookworm-Setup.bat delete mode 100644 download-panel.html delete mode 100644 download.html delete mode 100644 encrypt-secrets.ps1 delete mode 100644 guide-mac.html delete mode 100644 guide.html delete mode 100644 lessons-learned.md delete mode 100644 启动Bookworm-v3.bat diff --git a/.gitignore b/.gitignore index 8c59990..a0d4c94 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ secrets.txt users.txt +authcode-history.log auto-setup.ps1.bak-* .tmp-authcodes.json .tmp-release.json .tmp-*.png +Bookworm-AuthGen.exe 管理员SOP.html dist/ diff --git a/Bookworm-AutoSetup.bat b/Bookworm-AutoSetup.bat deleted file mode 100644 index fdf612e..0000000 --- a/Bookworm-AutoSetup.bat +++ /dev/null @@ -1,80 +0,0 @@ -@echo off -chcp 65001 > nul -title Bookworm Portable - 全自动安装 -echo. -echo ==================================================== -echo Bookworm Portable - 全自动安装器 -echo 双击即可完成全部配置,无需手动操作 -echo ==================================================== -echo. - -:: 检查 auto-setup.ps1 是否在当前目录 -if exist "%~dp0auto-setup.ps1" ( - cd /d "%~dp0" - goto :RUN_SETUP -) - -:: 检查 bookworm-boot 是否已克隆 -if exist "%USERPROFILE%\bookworm-boot\auto-setup.ps1" ( - echo [OK] 检测到 bookworm-boot 仓库 - cd /d "%USERPROFILE%\bookworm-boot" - goto :RUN_SETUP -) - -:: 检查 git -where git >nul 2>nul -if %errorlevel% neq 0 ( - echo [!!] Git 未安装,请先安装 Git: - echo https://git-scm.com/download/win - echo. - echo 安装 Git 后重新双击本文件即可。 - pause - exit /b 1 -) - -:: bookworm-boot 目录已存在但不完整 — 先删除再克隆 -if exist "%USERPROFILE%\bookworm-boot" ( - echo [..] 检测到不完整的 bookworm-boot,重新下载... - rmdir /s /q "%USERPROFILE%\bookworm-boot" 2>nul -) - -echo [..] 首次运行,下载配置文件 (需输入 Gitea 账号密码)... -echo. -git clone https://code.letcareme.com/bookworm/bookworm-boot.git "%USERPROFILE%\bookworm-boot" -if %errorlevel% neq 0 ( - echo. - echo [!!] 克隆失败,请检查: - echo 1. 网络是否正常 - echo 2. Gitea 账号密码是否正确 - echo 3. 能否访问 https://code.letcareme.com - pause - exit /b 1 -) -cd /d "%USERPROFILE%\bookworm-boot" -echo [OK] 引导仓库下载完成 -echo. - -:RUN_SETUP -:: 更新到最新版本 -git pull >nul 2>nul - -:: 检测 PowerShell 7 (pwsh): 新窗口启动, 退出 cmd -where pwsh >nul 2>nul -if %errorlevel% equ 0 ( - echo [OK] 使用 PowerShell 7 - start "Bookworm 全自动安装" pwsh -NoLogo -ExecutionPolicy Bypass -File "%CD%\auto-setup.ps1" - exit -) - -:: 回退 PowerShell 5.1 (auto-setup 会安装 pwsh7, Phase 7 再切换) -echo [..] PowerShell 7 未安装, 先用 5.1 执行安装 (将自动安装 pwsh7) -powershell -ExecutionPolicy Bypass -File auto-setup.ps1 - -if %errorlevel% neq 0 ( - echo. - echo [!!] 安装过程中出现错误 - echo 请截图上方信息联系管理员 - echo. -) - -pause diff --git a/Bookworm-Install.bat b/Bookworm-Install.bat deleted file mode 100644 index f167576..0000000 --- a/Bookworm-Install.bat +++ /dev/null @@ -1,37 +0,0 @@ -@echo off -setlocal -chcp 65001 > nul 2>&1 -title Bookworm Smart Assistant - 全自动安装 v3.0 - -:: 极简入口: 只负责确保 Node.js 存在, 核心逻辑全在 setup-all.js 中 -:: 规则: 不超过 30 行, 不用 if(), 不嵌 PowerShell, 用 %~s 短路径 - -net session >nul 2>&1 -if %errorlevel% equ 0 goto :HAS_ADMIN -echo Set s = CreateObject("Shell.Application") > "%TEMP%\bw_uac.vbs" -echo s.ShellExecute "cmd.exe", "/k cd /d ""%~sdp0"" ^& ""%~snx0""", "", "runas", 1 >> "%TEMP%\bw_uac.vbs" -cscript //nologo "%TEMP%\bw_uac.vbs" -del /f /q "%TEMP%\bw_uac.vbs" 2>nul -exit /b - -:HAS_ADMIN -where node >nul 2>nul -if %errorlevel% equ 0 goto :HAS_NODE -echo [..] Node.js 未安装, 正在通过 winget 安装... -winget install OpenJS.NodeJS.LTS --accept-source-agreements --accept-package-agreements --silent 2>nul -for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v Path 2^>nul') do set "PATH=%%b" -set "PATH=%PATH%;C:\Program Files\nodejs" -where node >nul 2>nul -if %errorlevel% equ 0 goto :HAS_NODE -echo [!!] Node.js 安装失败。请手动下载: https://nodejs.org/ -goto :END - -:HAS_NODE -echo [OK] Node.js 就绪, 启动安装引擎... -node "%~dp0setup-all.js" %* || echo [!] 安装过程出错, 请查看上方日志 - -:END -echo. -echo 按任意键关闭... -pause > nul -endlocal diff --git a/Bookworm-OneClick-Win10.bat b/Bookworm-OneClick-Win10.bat deleted file mode 100644 index 2ddf9b5..0000000 --- a/Bookworm-OneClick-Win10.bat +++ /dev/null @@ -1,266 +0,0 @@ -@echo off -chcp 65001 > nul 2>&1 -title Bookworm Smart Assistant - 全自动安装 (Win10 兼容) - -:: ─── 自动提升管理员权限 ── -:: 用 goto 而非 if() 避免文件名含括号(如"(2)")导致解析崩溃 -net session >nul 2>&1 -if %errorlevel% equ 0 goto :IS_ADMIN - -echo 需要管理员权限来安装软件,正在请求... -echo Set objShell = CreateObject("Shell.Application") > "%TEMP%\bw_elevate.vbs" -echo objShell.ShellExecute "cmd.exe", "/k cd /d ""%~dp0"" & ""%~nx0""", "", "runas", 1 >> "%TEMP%\bw_elevate.vbs" -cscript //nologo "%TEMP%\bw_elevate.vbs" -del /f /q "%TEMP%\bw_elevate.vbs" 2>nul -exit /b - -:IS_ADMIN - -:: ─── 初始化 ───────────────────────────────────────── -setlocal EnableDelayedExpansion -color 1F -set "NO_PROXY=bww.letcareme.com,code.letcareme.com,letcareme.com,localhost,127.0.0.1" -set "no_proxy=%NO_PROXY%" -set "INSTALL_DIR=%USERPROFILE%\bookworm-boot" -set "GITEA_URL=https://code.letcareme.com/bookworm/bookworm-boot.git" -set "TEMP_DL=%TEMP%\bookworm-setup" -set "ERRORS=0" -set "NEED_PATH_REFRESH=0" - -echo. -echo +============================================================+ -echo ^| ^| -echo ^| Bookworm Smart Assistant ^| -echo ^| 全自动安装 v2.0 (Windows 10 兼容版) ^| -echo ^| ^| -echo ^| 兼容 Windows 10 1809+ / Windows 11 ^| -echo ^| 无需 winget, 通过直接下载安装包实现全自动 ^| -echo ^| ^| -echo +============================================================+ -echo. - -if not exist "%TEMP_DL%" mkdir "%TEMP_DL%" - -:: ─── 检测安装方式: winget 优先, 回退直接下载 ───────── -set "HAS_WINGET=0" -where winget >nul 2>nul -if %errorlevel% equ 0 set "HAS_WINGET=1" - -if "%HAS_WINGET%"=="1" ( - echo [OK] 检测到 winget, 使用 winget 安装 -) else ( - echo [i] 未检测到 winget, 使用直接下载方式安装 -) -echo. - -:: ─── 步骤 1/7: 安装 Git ──────────────────────────── -echo [1/7] 检查 Git... -where git >nul 2>nul -if %errorlevel% neq 0 ( - if "%HAS_WINGET%"=="1" ( - echo [..] 通过 winget 安装 Git... - winget install Git.Git --accept-source-agreements --accept-package-agreements --silent - ) else ( - echo [..] 下载 Git 安装包... - powershell -Command "& {[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12; $ProgressPreference='SilentlyContinue'; Invoke-WebRequest -Uri 'https://github.com/git-for-windows/git/releases/download/v2.47.1.windows.2/Git-2.47.1.2-64-bit.exe' -OutFile '%TEMP_DL%\git-install.exe'}" - if exist "%TEMP_DL%\git-install.exe" ( - echo [..] 静默安装 Git... - "%TEMP_DL%\git-install.exe" /VERYSILENT /NORESTART /NOCANCEL /SP- /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS /COMPONENTS="icons,ext\reg\shellhere,assoc,assoc_sh" - if !errorlevel! neq 0 ( - echo [!!] Git 安装失败 ^(exit: !errorlevel!^) - set /a ERRORS+=1 - ) else ( - echo [OK] Git 安装完成 - ) - ) else ( - echo [!!] Git 下载失败, 请手动安装: https://git-scm.com - set /a ERRORS+=1 - ) - ) - set "NEED_PATH_REFRESH=1" -) else ( - echo [OK] Git 已安装 -) -echo. - -:: ─── 步骤 2/7: 安装 Node.js ──────────────────────── -echo [2/7] 检查 Node.js... -where node >nul 2>nul -if %errorlevel% neq 0 ( - if "%HAS_WINGET%"=="1" ( - echo [..] 通过 winget 安装 Node.js LTS... - winget install OpenJS.NodeJS.LTS --accept-source-agreements --accept-package-agreements --silent - ) else ( - echo [..] 下载 Node.js LTS 安装包... - powershell -Command "& {[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12; $ProgressPreference='SilentlyContinue'; Invoke-WebRequest -Uri 'https://nodejs.org/dist/v22.14.0/node-v22.14.0-x64.msi' -OutFile '%TEMP_DL%\node-install.msi'}" - if exist "%TEMP_DL%\node-install.msi" ( - echo [..] 静默安装 Node.js... - msiexec /i "%TEMP_DL%\node-install.msi" /qn /norestart - echo [OK] Node.js 安装完成 - ) else ( - echo [!!] Node.js 下载失败, 请手动安装: https://nodejs.org - set /a ERRORS+=1 - ) - ) - set "NEED_PATH_REFRESH=1" -) else ( - echo [OK] Node.js 已安装 -) -echo. - -:: ─── 刷新 PATH ────────────────────────────────────── -if "%NEED_PATH_REFRESH%"=="1" ( - echo [..] 刷新系统 PATH... - for /f "tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v Path 2^>nul') do set "SYS_PATH=%%b" - for /f "tokens=2*" %%a in ('reg query "HKCU\Environment" /v Path 2^>nul') do set "USR_PATH=%%b" - set "PATH=!SYS_PATH!;!USR_PATH!" - set "PATH=!PATH!;C:\Program Files\nodejs;C:\Program Files\Git\cmd;C:\Program Files\Git\usr\bin" - set "PATH=!PATH!;%APPDATA%\npm" - echo [OK] PATH 已刷新 - echo. -) - -:: ─── 二次验证 ──────────────────────────────────────── -where git >nul 2>nul -if %errorlevel% neq 0 ( - echo [!!] Git 仍不可用 — 可能需要重启电脑后重新运行本程序 - pause - exit /b 1 -) -where node >nul 2>nul -if %errorlevel% neq 0 ( - echo [!!] Node.js 仍不可用 — 可能需要重启电脑后重新运行本程序 - pause - exit /b 1 -) - -:: ─── 步骤 3/7: 安装 Claude Code ───────────────────── -echo [3/7] 检查 Claude Code... -:: 国内 npm 镜像 - 淘宝源, 避免 npmjs.org 超时 -call npm config set registry https://registry.npmmirror.com 2>nul -where claude >nul 2>nul -if %errorlevel% neq 0 ( - echo [..] 通过 npm 安装 Claude Code - 淘宝镜像加速... - call npm i -g @anthropic-ai/claude-code 2>&1 - if !errorlevel! neq 0 ( - echo [!!] Claude Code 安装失败 - set /a ERRORS+=1 - ) else ( - echo [OK] Claude Code 安装成功 - ) -) else ( - echo [OK] Claude Code 已安装 -) -echo. - -:: ─── 步骤 4/7: 代理检测 ────────────────── -echo [4/7] 检测网络代理... -:: 纯 batch 实现, 不依赖 PowerShell, 不含括号 -set "PROXY_FOUND=" -netstat -an 2>nul | findstr "LISTENING" | findstr ":7890 :7893 :7891 :10792 :1080 :8118" >nul 2>nul -if !errorlevel! equ 0 set "PROXY_FOUND=1" -if defined PROXY_FOUND echo [OK] 检测到本地代理端口 -if not defined PROXY_FOUND echo [!] 未检测到代理, Claude Code 在国内可能无法启动 -if not defined PROXY_FOUND echo 请确保代理软件已启动 -echo. - -:: ─── 步骤 5/7: 克隆/更新 Bookworm ────────────────── -echo [5/7] 同步 Bookworm 配置... -git config --global credential.helper manager 2>nul - -if exist "%INSTALL_DIR%\.git" ( - echo 已有安装, 更新到最新版... - pushd "%INSTALL_DIR%" - git pull 2>&1 - popd -) else ( - if exist "%INSTALL_DIR%" rmdir /s /q "%INSTALL_DIR%" 2>nul - echo 首次下载 - 需输入 Gitea 用户名密码... - git clone "%GITEA_URL%" "%INSTALL_DIR%" 2>&1 - if !errorlevel! neq 0 ( - echo [!!] 下载失败, 请检查网络和 Gitea 凭证 - pause - exit /b 1 - ) -) -echo [OK] Bookworm 文件已就绪 -echo. - -:: ─── 步骤 6/7: 执行安装配置 ──────────────────────── -echo [6/7] 执行安装配置... -echo. -echo 首次安装需要输入主密码来解密 API 凭证 -echo - 主密码由管理员提供, 区分大小写 -echo. -where pwsh >nul 2>nul -if !errorlevel! equ 0 ( - pwsh -NoLogo -ExecutionPolicy Bypass -File "%INSTALL_DIR%\install.ps1" -) else ( - powershell -NoLogo -ExecutionPolicy Bypass -File "%INSTALL_DIR%\install.ps1" -) -echo. - -:: ─── 步骤 7/7: 桌面快捷方式 + 完成 ───────────────── -echo [7/7] 创建桌面快捷方式... -:: 用 VBScript 创建快捷方式, 避免 PowerShell 花括号被 cmd 截获 -echo Set ws = CreateObject("WScript.Shell") > "%TEMP%\bw_shortcut.vbs" -echo Set sc = ws.CreateShortcut(ws.SpecialFolders("Desktop") ^& "\Bookworm.lnk") >> "%TEMP%\bw_shortcut.vbs" -echo sc.TargetPath = "%INSTALL_DIR%\启动Bookworm.bat" >> "%TEMP%\bw_shortcut.vbs" -echo sc.WorkingDirectory = "%INSTALL_DIR%" >> "%TEMP%\bw_shortcut.vbs" -echo sc.Description = "Bookworm Smart Assistant" >> "%TEMP%\bw_shortcut.vbs" -echo sc.Save >> "%TEMP%\bw_shortcut.vbs" -echo Set sc = ws.CreateShortcut(ws.SpecialFolders("Desktop") ^& "\更新Bookworm.lnk") >> "%TEMP%\bw_shortcut.vbs" -echo sc.TargetPath = "%INSTALL_DIR%\更新并启动Bookworm.bat" >> "%TEMP%\bw_shortcut.vbs" -echo sc.WorkingDirectory = "%INSTALL_DIR%" >> "%TEMP%\bw_shortcut.vbs" -echo sc.Save >> "%TEMP%\bw_shortcut.vbs" -cscript //nologo "%TEMP%\bw_shortcut.vbs" 2>nul -if !errorlevel! equ 0 echo [OK] 桌面快捷方式已创建 -del /f /q "%TEMP%\bw_shortcut.vbs" 2>nul - -if exist "%INSTALL_DIR%\guide.html" start "" "%INSTALL_DIR%\guide.html" - -:: 清理临时文件 -if exist "%TEMP_DL%" rmdir /s /q "%TEMP_DL%" 2>nul - -echo. -echo +============================================================+ -echo ^| ^| -echo ^| 安装完成! ^| -echo ^| ^| -echo ^| 已安装: ^| -echo ^| [v] Node.js LTS [v] Git ^| -echo ^| [v] Claude Code [v] Bookworm - 92 Skills ^| -echo ^| ^| -echo ^| 桌面快捷方式: ^| -echo ^| Bookworm — 日常启动 ^| -echo ^| 更新Bookworm — 同步最新版后启动 ^| -echo ^| ^| -echo +============================================================+ -echo. - -if %ERRORS% gtr 0 ( - echo [注意] 安装过程中有 %ERRORS% 个警告, 请查看上方日志 - echo. -) - -echo 按任意键启动 Bookworm... -pause > nul - -cd /d "%INSTALL_DIR%" -if exist "启动Bookworm.bat" ( - call "启动Bookworm.bat" -) else ( - where pwsh >nul 2>nul - if !errorlevel! equ 0 ( - pwsh -NoLogo -ExecutionPolicy Bypass -File install.ps1 -StartOnly -AutoAccept - ) else ( - powershell -NoLogo -ExecutionPolicy Bypass -File install.ps1 -StartOnly -AutoAccept - ) -) - -endlocal - -:: ─── 兜底: 任何情况下窗口不自动关闭 ── -echo. -echo 如看到此消息说明流程已结束,按任意键关闭窗口... -pause > nul diff --git a/Bookworm-Setup.bat b/Bookworm-Setup.bat deleted file mode 100644 index c908df2..0000000 --- a/Bookworm-Setup.bat +++ /dev/null @@ -1,163 +0,0 @@ -@echo off -chcp 65001 > nul -title Bookworm Smart Assistant - 一键安装 -color 1F - -:: 中转站在国内,不走代理 -set NO_PROXY=bww.letcareme.com,code.letcareme.com,letcareme.com,localhost,127.0.0.1 -set no_proxy=%NO_PROXY% - -echo. -echo +================================================+ -echo ^| ^| -echo ^| Bookworm Smart Assistant ^| -echo ^| 一键安装程序 v1.5 ^| -echo ^| ^| -echo ^| 92 Skills / 18 Agents / 34 Hooks ^| -echo ^| ^| -echo +================================================+ -echo. - -:: ─── 检查依赖 ─────────────────────────────────────── -echo [1/4] 检查环境... -echo. - -where git >nul 2>nul -if %errorlevel% neq 0 ( - echo [!!] Git 未安装 - echo. - echo 请先安装 Git: - echo 下载: https://git-scm.com/download/win - echo 安装后重新运行本程序 - echo. - pause - exit /b 1 -) -echo [OK] Git - -where node >nul 2>nul -if %errorlevel% neq 0 ( - echo [!!] Node.js 未安装 - echo. - echo 请先安装 Node.js: - echo 下载: https://nodejs.org (选 LTS 版本) - echo 安装后重新打开本程序 - echo. - pause - exit /b 1 -) -echo [OK] Node.js - -where claude >nul 2>nul -if %errorlevel% neq 0 ( - echo [..] Claude Code 未安装,正在安装... - call npm i -g @anthropic-ai/claude-code - if %errorlevel% neq 0 ( - echo [!!] Claude Code 安装失败 - echo 手动安装: npm i -g @anthropic-ai/claude-code - pause - exit /b 1 - ) -) -echo [OK] Claude Code -echo. - -:: ─── 选择安装目录 ─────────────────────────────────── -set "INSTALL_DIR=%USERPROFILE%\bookworm-boot" - -:: ─── 克隆/更新仓库 ────────────────────────────────── -echo [2/4] 下载 Bookworm... -echo. - -if exist "%INSTALL_DIR%\.git" ( - echo 已安装,更新到最新版... - cd /d "%INSTALL_DIR%" - git pull 2>&1 -) else ( - if exist "%INSTALL_DIR%" ( - echo 目录已存在但非 git 仓库,清理后重新下载... - rmdir /s /q "%INSTALL_DIR%" 2>nul - ) - echo 首次下载... - git config --global credential.helper store - git clone https://code.letcareme.com/bookworm/bookworm-boot.git "%INSTALL_DIR%" 2>&1 - if %errorlevel% neq 0 ( - echo. - echo [!!] 下载失败 - echo 请检查: - echo - 网络是否正常 - echo - 是否能访问 https://code.letcareme.com - echo - Gitea 用户名密码是否正确 - echo. - pause - exit /b 1 - ) -) -echo. -echo [OK] Bookworm 文件已就绪 -echo. - -:: ─── 创建桌面快捷方式 ─────────────────────────────── -echo [3/4] 创建桌面快捷方式... -echo. - -:: 用 PowerShell 创建 .lnk -powershell -ExecutionPolicy Bypass -Command ^ - "$s=(New-Object -COM WScript.Shell).CreateShortcut('%USERPROFILE%\Desktop\Bookworm.lnk');^ - $s.TargetPath='%INSTALL_DIR%\启动Bookworm.bat';^ - $s.WorkingDirectory='%INSTALL_DIR%';^ - $s.Description='Bookworm Smart Assistant';^ - $s.Save()" 2>nul - -if %errorlevel% equ 0 ( - echo [OK] 桌面快捷方式: Bookworm -) else ( - echo [!] 快捷方式创建失败 (不影响使用) -) - -powershell -ExecutionPolicy Bypass -Command ^ - "$s=(New-Object -COM WScript.Shell).CreateShortcut('%USERPROFILE%\Desktop\更新Bookworm.lnk');^ - $s.TargetPath='%INSTALL_DIR%\更新并启动Bookworm.bat';^ - $s.WorkingDirectory='%INSTALL_DIR%';^ - $s.Description='Bookworm 更新并启动';^ - $s.Save()" 2>nul - -echo. - -:: ─── 启动 Bookworm ────────────────────────────────── -echo [4/4] 启动 Bookworm... -echo. -echo +================================================+ -echo ^| ^| -echo ^| 安装完成! ^| -echo ^| ^| -echo ^| 桌面已创建快捷方式: ^| -echo ^| Bookworm - 日常启动 ^| -echo ^| 更新Bookworm - 同步后启动 ^| -echo ^| ^| -echo ^| 接下来会启动 Bookworm ^| -echo ^| 请输入管理员提供的主密码 ^| -echo ^| ^| -echo +================================================+ -echo. - -cd /d "%INSTALL_DIR%" - -:: 打开使用教程 -if exist "%INSTALL_DIR%\guide.html" ( - start "" "%INSTALL_DIR%\guide.html" -) - -:: 启动安装脚本 -where pwsh >nul 2>nul -if %errorlevel% equ 0 ( - pwsh -ExecutionPolicy Bypass -File install.ps1 -AutoAccept -) else ( - powershell -ExecutionPolicy Bypass -File install.ps1 -AutoAccept -) - -if %errorlevel% neq 0 ( - echo. - echo 启动失败,请查看上方错误信息 - pause -) diff --git a/admin-authcode-gui.ps1 b/admin-authcode-gui.ps1 index e1eb40c..c9133e5 100644 --- a/admin-authcode-gui.ps1 +++ b/admin-authcode-gui.ps1 @@ -5,6 +5,10 @@ 内部调用 node gen-authcode.js, 提供可视化界面生成多用户授权码。 打包命令: 见 build.ps1 -Admin #> + +# 全局错误捕获 (PS2EXE -NoOutput 会吞掉所有错误, 这里确保弹窗显示) +try { + $ErrorActionPreference = "Stop" Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing @@ -28,216 +32,268 @@ if (-not (Test-Path $GenScript)) { $SecretsTxt = Join-Path $ScriptDir "secrets.txt" # ─── 品牌色 ─────────────────────────────────────────── -$brandBlue = [System.Drawing.Color]::FromArgb(88, 101, 242) -$brandDark = [System.Drawing.Color]::FromArgb(30, 31, 46) -$brandLight = [System.Drawing.Color]::FromArgb(245, 245, 250) -$successGreen = [System.Drawing.Color]::FromArgb(46, 160, 67) +$brandBlue = [System.Drawing.Color]::FromArgb(88, 101, 242) +$brandDark = [System.Drawing.Color]::FromArgb(24, 25, 38) +$brandLight = [System.Drawing.Color]::FromArgb(245, 246, 250) +$cardBg = [System.Drawing.Color]::FromArgb(248, 249, 253) +$successGreen = [System.Drawing.Color]::FromArgb(35, 134, 54) $warningOrange = [System.Drawing.Color]::FromArgb(227, 137, 29) +$textPrimary = [System.Drawing.Color]::FromArgb(36, 41, 47) +$textSecondary = [System.Drawing.Color]::FromArgb(110, 119, 129) +$inputBorder = [System.Drawing.Color]::FromArgb(208, 215, 222) # ─── 主窗口 ─────────────────────────────────────────── $form = New-Object System.Windows.Forms.Form -$form.Text = "Bookworm 授权码生成器 — 管理员工具" -$form.Size = New-Object System.Drawing.Size(560, 580) +$form.Text = "Bookworm 授权码生成器" +$form.ClientSize = New-Object System.Drawing.Size(540, 594) $form.StartPosition = "CenterScreen" -$form.FormBorderStyle = "FixedDialog" +$form.FormBorderStyle = "FixedSingle" $form.MaximizeBox = $false $form.BackColor = [System.Drawing.Color]::White $form.Font = New-Object System.Drawing.Font("Segoe UI", 9) -# ─── 标题栏 ─────────────────────────────────────────── +# ─── 标题栏 (深色渐变 + 副标题) ────────────────────── $header = New-Object System.Windows.Forms.Panel -$header.Location = New-Object System.Drawing.Point(0, 0) -$header.Size = New-Object System.Drawing.Size(560, 56) +$header.Dock = "Top" +$header.Size = New-Object System.Drawing.Size(540, 70) $header.BackColor = $brandDark $form.Controls.Add($header) $titleLabel = New-Object System.Windows.Forms.Label -$titleLabel.Location = New-Object System.Drawing.Point(20, 14) -$titleLabel.Size = New-Object System.Drawing.Size(520, 28) +$titleLabel.Location = New-Object System.Drawing.Point(24, 12) +$titleLabel.Size = New-Object System.Drawing.Size(500, 28) $titleLabel.Text = "Bookworm 授权码生成器" -$titleLabel.Font = New-Object System.Drawing.Font("Segoe UI", 14, [System.Drawing.FontStyle]::Bold) +$titleLabel.Font = New-Object System.Drawing.Font("Segoe UI", 15, [System.Drawing.FontStyle]::Bold) $titleLabel.ForeColor = [System.Drawing.Color]::White $header.Controls.Add($titleLabel) -# ─── 输入区 ─────────────────────────────────────────── -[int]$y = 72 +$subtitleLabel = New-Object System.Windows.Forms.Label +$subtitleLabel.Location = New-Object System.Drawing.Point(24, 42) +$subtitleLabel.Size = New-Object System.Drawing.Size(500, 18) +$subtitleLabel.Text = "管理员专用 · 为每位用户生成独立授权码与加密凭证" +$subtitleLabel.Font = New-Object System.Drawing.Font("Segoe UI", 8.5) +$subtitleLabel.ForeColor = [System.Drawing.Color]::FromArgb(160, 170, 200) +$header.Controls.Add($subtitleLabel) -# 用户名 +# ═══ 输入卡片 (Panel 模拟卡片) ═══════════════════════ +$inputCard = New-Object System.Windows.Forms.Panel +$inputCard.Location = New-Object System.Drawing.Point(20, 84) +$inputCard.Size = New-Object System.Drawing.Size(500, 186) +$inputCard.BackColor = $cardBg +$form.Controls.Add($inputCard) + +# ── 卡片标题 +$inputTitle = New-Object System.Windows.Forms.Label +$inputTitle.Location = New-Object System.Drawing.Point(16, 10) +$inputTitle.Size = New-Object System.Drawing.Size(200, 20) +$inputTitle.Text = "用户信息" +$inputTitle.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold) +$inputTitle.ForeColor = $brandBlue +$inputCard.Controls.Add($inputTitle) + +# ── 用户名 $lblUser = New-Object System.Windows.Forms.Label -$lblUser.Location = New-Object System.Drawing.Point(24, $y) -$lblUser.Size = New-Object System.Drawing.Size(120, 22) -$lblUser.Text = "用户标识 (--user):" -$form.Controls.Add($lblUser) +$lblUser.Location = New-Object System.Drawing.Point(16, 40) +$lblUser.Size = New-Object System.Drawing.Size(90, 22) +$lblUser.Text = "用户标识" +$lblUser.ForeColor = $textPrimary +$lblUser.TextAlign = [System.Drawing.ContentAlignment]::MiddleRight +$inputCard.Controls.Add($lblUser) $txtUser = New-Object System.Windows.Forms.TextBox -$txtUser.Location = New-Object System.Drawing.Point(150, $y - 2) -$txtUser.Size = New-Object System.Drawing.Size(370, 28) -$txtUser.Font = New-Object System.Drawing.Font("Consolas", 11) -$txtUser.PlaceholderText = "例如: alice, 张三, 茶师兄" -$form.Controls.Add($txtUser) -$y += 38 +$txtUser.Location = New-Object System.Drawing.Point(114, 38) +$txtUser.Size = New-Object System.Drawing.Size(370, 26) +$txtUser.Font = New-Object System.Drawing.Font("Segoe UI", 10) +$txtUser.BorderStyle = "FixedSingle" +$inputCard.Controls.Add($txtUser) -# Relay Sub-Key +# ── Relay Key $lblKey = New-Object System.Windows.Forms.Label -$lblKey.Location = New-Object System.Drawing.Point(24, $y) -$lblKey.Size = New-Object System.Drawing.Size(120, 22) -$lblKey.Text = "Relay Sub-Key:" -$form.Controls.Add($lblKey) +$lblKey.Location = New-Object System.Drawing.Point(16, 74) +$lblKey.Size = New-Object System.Drawing.Size(90, 22) +$lblKey.Text = "Relay Key" +$lblKey.ForeColor = $textPrimary +$lblKey.TextAlign = [System.Drawing.ContentAlignment]::MiddleRight +$inputCard.Controls.Add($lblKey) $txtKey = New-Object System.Windows.Forms.TextBox -$txtKey.Location = New-Object System.Drawing.Point(150, $y - 2) -$txtKey.Size = New-Object System.Drawing.Size(370, 28) -$txtKey.Font = New-Object System.Drawing.Font("Consolas", 10) +$txtKey.Location = New-Object System.Drawing.Point(114, 72) +$txtKey.Size = New-Object System.Drawing.Size(370, 26) +$txtKey.Font = New-Object System.Drawing.Font("Segoe UI", 10) $txtKey.PasswordChar = '*' -$txtKey.PlaceholderText = "sk-... (中转站后台创建的子 Key)" -$form.Controls.Add($txtKey) -$y += 28 +$txtKey.BorderStyle = "FixedSingle" +$inputCard.Controls.Add($txtKey) -# 显示/隐藏 Key $chkShowKey = New-Object System.Windows.Forms.CheckBox -$chkShowKey.Location = New-Object System.Drawing.Point(150, $y) -$chkShowKey.Size = New-Object System.Drawing.Size(120, 22) +$chkShowKey.Location = New-Object System.Drawing.Point(114, 102) +$chkShowKey.Size = New-Object System.Drawing.Size(100, 20) $chkShowKey.Text = "显示 Key" +$chkShowKey.ForeColor = $textSecondary +$chkShowKey.Font = New-Object System.Drawing.Font("Segoe UI", 8) $chkShowKey.Add_CheckedChanged({ $txtKey.PasswordChar = if ($chkShowKey.Checked) { [char]0 } else { '*' } }) -$form.Controls.Add($chkShowKey) -$y += 32 +$inputCard.Controls.Add($chkShowKey) -# 有效期 +# ── 有效期 $lblDays = New-Object System.Windows.Forms.Label -$lblDays.Location = New-Object System.Drawing.Point(24, $y) -$lblDays.Size = New-Object System.Drawing.Size(120, 22) -$lblDays.Text = "有效期 (天):" -$form.Controls.Add($lblDays) +$lblDays.Location = New-Object System.Drawing.Point(16, 132) +$lblDays.Size = New-Object System.Drawing.Size(90, 22) +$lblDays.Text = "有效期" +$lblDays.ForeColor = $textPrimary +$lblDays.TextAlign = [System.Drawing.ContentAlignment]::MiddleRight +$inputCard.Controls.Add($lblDays) $cmbDays = New-Object System.Windows.Forms.ComboBox -$cmbDays.Location = New-Object System.Drawing.Point(150, $y - 2) -$cmbDays.Size = New-Object System.Drawing.Size(100, 28) +$cmbDays.Location = New-Object System.Drawing.Point(114, 130) +$cmbDays.Size = New-Object System.Drawing.Size(80, 26) $cmbDays.DropDownStyle = "DropDownList" -@(7, 14, 30, 60, 90, 180, 365) | ForEach-Object { $cmbDays.Items.Add($_) | Out-Null } -$cmbDays.SelectedIndex = 2 # 默认 30 天 -$form.Controls.Add($cmbDays) +$cmbDays.Font = New-Object System.Drawing.Font("Segoe UI", 10) +$null = $cmbDays.Items.AddRange(@(7, 14, 30, 60, 90, 180, 365)) +$cmbDays.SelectedIndex = 2 +$inputCard.Controls.Add($cmbDays) + +$lblDaysUnit = New-Object System.Windows.Forms.Label +$lblDaysUnit.Location = New-Object System.Drawing.Point(198, 132) +$lblDaysUnit.Size = New-Object System.Drawing.Size(30, 22) +$lblDaysUnit.Text = "天" +$lblDaysUnit.ForeColor = $textSecondary +$inputCard.Controls.Add($lblDaysUnit) $lblDaysHint = New-Object System.Windows.Forms.Label -$lblDaysHint.Location = New-Object System.Drawing.Point(260, $y) -$lblDaysHint.Size = New-Object System.Drawing.Size(260, 22) +$lblDaysHint.Location = New-Object System.Drawing.Point(240, 132) +$lblDaysHint.Size = New-Object System.Drawing.Size(240, 22) $lblDaysHint.Text = "" -$lblDaysHint.ForeColor = [System.Drawing.Color]::Gray -$form.Controls.Add($lblDaysHint) +$lblDaysHint.ForeColor = $textSecondary +$lblDaysHint.Font = New-Object System.Drawing.Font("Segoe UI", 8.5) +$inputCard.Controls.Add($lblDaysHint) $cmbDays.Add_SelectedIndexChanged({ $d = [int]$cmbDays.SelectedItem - $exp = (Get-Date).AddDays($d).ToString("yyyy-MM-dd") - $lblDaysHint.Text = "到期: $exp" + $lblDaysHint.Text = "-> 到期: " + (Get-Date).AddDays($d).ToString("yyyy-MM-dd") }) -# 触发初始值 $cmbDays.SelectedIndex = 2 -$y += 38 -# secrets.txt 状态 -$lblSecrets = New-Object System.Windows.Forms.Label -$lblSecrets.Location = New-Object System.Drawing.Point(24, $y) -$lblSecrets.Size = New-Object System.Drawing.Size(500, 22) -if (Test-Path $SecretsTxt) { - $lineCount = (Get-Content $SecretsTxt -ErrorAction SilentlyContinue | Where-Object { $_ -match '=' }).Count - $lblSecrets.Text = "secrets.txt: $lineCount 个凭证已配置" - $lblSecrets.ForeColor = $successGreen -} else { - $lblSecrets.Text = "secrets.txt: 未找到 ($SecretsTxt)" - $lblSecrets.ForeColor = [System.Drawing.Color]::Red -} -$form.Controls.Add($lblSecrets) -$y += 28 - -# Node.js 状态 -$lblNode = New-Object System.Windows.Forms.Label -$lblNode.Location = New-Object System.Drawing.Point(24, $y) -$lblNode.Size = New-Object System.Drawing.Size(500, 22) +# ── 环境状态行 ── $nodeOK = [bool](Get-Command node -ErrorAction SilentlyContinue) +$secretsOK = Test-Path $SecretsTxt + +$lblStatus1 = New-Object System.Windows.Forms.Label +$lblStatus1.Location = New-Object System.Drawing.Point(16, 160) +$lblStatus1.Size = New-Object System.Drawing.Size(230, 18) +$lblStatus1.Font = New-Object System.Drawing.Font("Segoe UI", 8) +if ($secretsOK) { + $lineCount = (Get-Content $SecretsTxt -ErrorAction SilentlyContinue | Where-Object { $_ -match '=' }).Count + $lblStatus1.Text = "secrets.txt: $lineCount 个凭证" + $lblStatus1.ForeColor = $successGreen +} else { + $lblStatus1.Text = "secrets.txt: 未找到" + $lblStatus1.ForeColor = [System.Drawing.Color]::Red +} +$inputCard.Controls.Add($lblStatus1) + +$lblStatus2 = New-Object System.Windows.Forms.Label +$lblStatus2.Location = New-Object System.Drawing.Point(250, 160) +$lblStatus2.Size = New-Object System.Drawing.Size(230, 18) +$lblStatus2.Font = New-Object System.Drawing.Font("Segoe UI", 8) if ($nodeOK) { $nodeVer = try { (& node --version 2>$null) } catch { "" } - $lblNode.Text = "Node.js: $nodeVer" - $lblNode.ForeColor = $successGreen + $lblStatus2.Text = "Node.js: $nodeVer" + $lblStatus2.ForeColor = $successGreen } else { - $lblNode.Text = "Node.js: 未安装 (必需)" - $lblNode.ForeColor = [System.Drawing.Color]::Red + $lblStatus2.Text = "Node.js: 未安装" + $lblStatus2.ForeColor = [System.Drawing.Color]::Red } -$form.Controls.Add($lblNode) -$y += 36 +$inputCard.Controls.Add($lblStatus2) -# ─── 生成按钮 ───────────────────────────────────────── +# ═══ 操作按钮 ═════════════════════════════════════════ $btnGenerate = New-Object System.Windows.Forms.Button -$btnGenerate.Location = New-Object System.Drawing.Point(150, $y) -$btnGenerate.Size = New-Object System.Drawing.Size(160, 40) -$btnGenerate.Text = "生成授权码" +$btnGenerate.Location = New-Object System.Drawing.Point(138, 282) +$btnGenerate.Size = New-Object System.Drawing.Size(180, 42) +$btnGenerate.Text = " 生成授权码" $btnGenerate.Font = New-Object System.Drawing.Font("Segoe UI", 11, [System.Drawing.FontStyle]::Bold) $btnGenerate.FlatStyle = "Flat" $btnGenerate.BackColor = $brandBlue $btnGenerate.ForeColor = [System.Drawing.Color]::White $btnGenerate.FlatAppearance.BorderSize = 0 $btnGenerate.Cursor = [System.Windows.Forms.Cursors]::Hand +$btnGenerate.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter $form.Controls.Add($btnGenerate) $btnClear = New-Object System.Windows.Forms.Button -$btnClear.Location = New-Object System.Drawing.Point(320, $y) -$btnClear.Size = New-Object System.Drawing.Size(80, 40) +$btnClear.Location = New-Object System.Drawing.Point(330, 282) +$btnClear.Size = New-Object System.Drawing.Size(72, 42) $btnClear.Text = "清空" +$btnClear.Font = New-Object System.Drawing.Font("Segoe UI", 9) $btnClear.FlatStyle = "Flat" -$btnClear.FlatAppearance.BorderColor = [System.Drawing.Color]::LightGray +$btnClear.BackColor = [System.Drawing.Color]::White +$btnClear.ForeColor = $textSecondary +$btnClear.FlatAppearance.BorderColor = $inputBorder +$btnClear.FlatAppearance.BorderSize = 1 $form.Controls.Add($btnClear) -$y += 54 -# ─── 分隔线 ─────────────────────────────────────────── -$sep = New-Object System.Windows.Forms.Label -$sep.Location = New-Object System.Drawing.Point(20, $y) -$sep.Size = New-Object System.Drawing.Size(510, 1) -$sep.BorderStyle = "Fixed3D" -$form.Controls.Add($sep) -$y += 10 +# ═══ 结果卡片 ═════════════════════════════════════════ +$resultCard = New-Object System.Windows.Forms.Panel +$resultCard.Location = New-Object System.Drawing.Point(20, 338) +$resultCard.Size = New-Object System.Drawing.Size(500, 220) +$resultCard.BackColor = $cardBg +$form.Controls.Add($resultCard) -# ─── 结果区 ─────────────────────────────────────────── $lblResultTitle = New-Object System.Windows.Forms.Label -$lblResultTitle.Location = New-Object System.Drawing.Point(24, $y) -$lblResultTitle.Size = New-Object System.Drawing.Size(200, 22) +$lblResultTitle.Location = New-Object System.Drawing.Point(16, 10) +$lblResultTitle.Size = New-Object System.Drawing.Size(200, 20) $lblResultTitle.Text = "生成结果" -$lblResultTitle.Font = New-Object System.Drawing.Font("Segoe UI", 10, [System.Drawing.FontStyle]::Bold) -$lblResultTitle.ForeColor = $brandDark -$form.Controls.Add($lblResultTitle) -$y += 26 +$lblResultTitle.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold) +$lblResultTitle.ForeColor = $brandBlue +$resultCard.Controls.Add($lblResultTitle) -# 授权码 (大字 Consolas, 可选中复制) $txtAuthCode = New-Object System.Windows.Forms.TextBox -$txtAuthCode.Location = New-Object System.Drawing.Point(24, $y) -$txtAuthCode.Size = New-Object System.Drawing.Size(400, 32) +$txtAuthCode.Location = New-Object System.Drawing.Point(16, 38) +$txtAuthCode.Size = New-Object System.Drawing.Size(380, 34) $txtAuthCode.Font = New-Object System.Drawing.Font("Consolas", 13, [System.Drawing.FontStyle]::Bold) $txtAuthCode.ReadOnly = $true -$txtAuthCode.BackColor = $brandLight +$txtAuthCode.BackColor = [System.Drawing.Color]::White $txtAuthCode.ForeColor = $brandDark +$txtAuthCode.BorderStyle = "FixedSingle" $txtAuthCode.Text = "" -$form.Controls.Add($txtAuthCode) +$resultCard.Controls.Add($txtAuthCode) $btnCopy = New-Object System.Windows.Forms.Button -$btnCopy.Location = New-Object System.Drawing.Point(430, $y) -$btnCopy.Size = New-Object System.Drawing.Size(90, 32) +$btnCopy.Location = New-Object System.Drawing.Point(404, 38) +$btnCopy.Size = New-Object System.Drawing.Size(80, 34) $btnCopy.Text = "复制" +$btnCopy.Font = New-Object System.Drawing.Font("Segoe UI", 9) $btnCopy.FlatStyle = "Flat" -$btnCopy.BackColor = $brandLight -$btnCopy.FlatAppearance.BorderColor = [System.Drawing.Color]::LightGray +$btnCopy.BackColor = $brandBlue +$btnCopy.ForeColor = [System.Drawing.Color]::White +$btnCopy.FlatAppearance.BorderSize = 0 +$btnCopy.Cursor = [System.Windows.Forms.Cursors]::Hand $btnCopy.Enabled = $false -$form.Controls.Add($btnCopy) -$y += 40 +$resultCard.Controls.Add($btnCopy) -# 详细信息 $lblDetails = New-Object System.Windows.Forms.Label -$lblDetails.Location = New-Object System.Drawing.Point(24, $y) -$lblDetails.Size = New-Object System.Drawing.Size(500, 66) -$lblDetails.Text = "" -$lblDetails.ForeColor = [System.Drawing.Color]::FromArgb(100, 100, 120) -$form.Controls.Add($lblDetails) -$y += 70 +$lblDetails.Location = New-Object System.Drawing.Point(16, 82) +$lblDetails.Size = New-Object System.Drawing.Size(468, 66) +$lblDetails.Text = "点击「生成授权码」后,结果将显示在此处。" +$lblDetails.ForeColor = $textSecondary +$lblDetails.Font = New-Object System.Drawing.Font("Segoe UI", 8.5) +$resultCard.Controls.Add($lblDetails) -# 状态栏 +# 推送到 Gitea 按钮 +$btnPush = New-Object System.Windows.Forms.Button +$btnPush.Location = New-Object System.Drawing.Point(16, 156) +$btnPush.Size = New-Object System.Drawing.Size(468, 38) +$btnPush.Text = "推送到 Gitea (git add + commit + push)" +$btnPush.Font = New-Object System.Drawing.Font("Segoe UI", 10) +$btnPush.FlatStyle = "Flat" +$btnPush.BackColor = $successGreen +$btnPush.ForeColor = [System.Drawing.Color]::White +$btnPush.FlatAppearance.BorderSize = 0 +$btnPush.Cursor = [System.Windows.Forms.Cursors]::Hand +$btnPush.Enabled = $false +$resultCard.Controls.Add($btnPush) + +# ═══ 状态栏 ═══════════════════════════════════════════ $statusBar = New-Object System.Windows.Forms.Label -$statusBar.Location = New-Object System.Drawing.Point(0, $y) -$statusBar.Size = New-Object System.Drawing.Size(560, 24) -$statusBar.BackColor = $brandLight -$statusBar.ForeColor = [System.Drawing.Color]::Gray -$statusBar.Text = " 就绪 | $ScriptDir" +$statusBar.Location = New-Object System.Drawing.Point(0, 568) +$statusBar.Size = New-Object System.Drawing.Size(540, 26) +$statusBar.BackColor = $brandDark +$statusBar.ForeColor = [System.Drawing.Color]::FromArgb(160, 170, 200) +$statusBar.Text = " 就绪 | $ScriptDir" $statusBar.Font = New-Object System.Drawing.Font("Segoe UI", 8) $statusBar.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft $form.Controls.Add($statusBar) @@ -252,6 +308,65 @@ $btnCopy.Add_Click({ } }) +$btnPush.Add_Click({ + $btnPush.Enabled = $false + $btnPush.Text = "推送中..." + $statusBar.Text = " git add + commit + push ..." + $statusBar.ForeColor = $warningOrange + [System.Windows.Forms.Application]::DoEvents() + + try { + $gitExe = (Get-Command git -ErrorAction Stop).Source + $userName = if ($global:lastGenUser) { $global:lastGenUser } else { "user" } + + # git add secrets-*.enc + $psi = New-Object System.Diagnostics.ProcessStartInfo + $psi.FileName = $gitExe + $psi.Arguments = "add secrets-*.enc" + $psi.UseShellExecute = $false + $psi.RedirectStandardOutput = $true + $psi.RedirectStandardError = $true + $psi.StandardOutputEncoding = [System.Text.Encoding]::UTF8 + $psi.StandardErrorEncoding = [System.Text.Encoding]::UTF8 + $psi.CreateNoWindow = $true + $psi.WorkingDirectory = $ScriptDir + $p = [System.Diagnostics.Process]::Start($psi) + $p.WaitForExit(15000) + + # git commit + $psi.Arguments = "commit -m `"add user $userName`"" + $p = [System.Diagnostics.Process]::Start($psi) + $p.StandardOutput.ReadToEnd() | Out-Null + $p.WaitForExit(15000) + + # git push + $statusBar.Text = " git push 中 (可能需要几秒)..." + [System.Windows.Forms.Application]::DoEvents() + $psi.Arguments = "push" + $p = [System.Diagnostics.Process]::Start($psi) + $pushOut = $p.StandardError.ReadToEnd() # git push 输出在 stderr + $p.WaitForExit(30000) + + if ($p.ExitCode -eq 0) { + $btnPush.Text = "已推送" + $btnPush.BackColor = [System.Drawing.Color]::FromArgb(200, 220, 200) + $btnPush.ForeColor = $successGreen + $statusBar.Text = " 推送成功 — 现在可以把授权码发给 $userName" + $statusBar.ForeColor = $successGreen + } else { + throw "git push 失败: $pushOut" + } + } catch { + $btnPush.Text = "推送失败 (点击重试)" + $btnPush.BackColor = [System.Drawing.Color]::FromArgb(220, 200, 200) + $btnPush.ForeColor = [System.Drawing.Color]::Red + $btnPush.Enabled = $true + $statusBar.Text = " 推送失败: $_" + $statusBar.ForeColor = [System.Drawing.Color]::Red + [System.Windows.Forms.MessageBox]::Show("推送失败:`n$_`n`n请检查 Git 配置和网络。", "Git 错误", "OK", "Error") + } +}) + $btnClear.Add_Click({ $txtUser.Text = "" $txtKey.Text = "" @@ -291,38 +406,45 @@ $btnGenerate.Add_Click({ [System.Windows.Forms.Application]::DoEvents() try { - $args = @($GenScript, $days, "-k", $key, "-u", $user) - $proc = Start-Process node -ArgumentList $args -NoNewWindow -PassThru ` - -RedirectStandardOutput "$env:TEMP\bw-gen-out.tmp" ` - -RedirectStandardError "$env:TEMP\bw-gen-err.tmp" ` - -WorkingDirectory $ScriptDir - - $sw = [System.Diagnostics.Stopwatch]::StartNew() - while (-not $proc.HasExited -and $sw.ElapsedMilliseconds -lt 30000) { - [System.Windows.Forms.Application]::DoEvents() - Start-Sleep -Milliseconds 100 - } - if (-not $proc.HasExited) { $proc.Kill(); throw "超时 (30s)" } - - $stdout = Get-Content "$env:TEMP\bw-gen-out.tmp" -Raw -ErrorAction SilentlyContinue - $stderr = Get-Content "$env:TEMP\bw-gen-err.tmp" -Raw -ErrorAction SilentlyContinue - Remove-Item "$env:TEMP\bw-gen-out.tmp", "$env:TEMP\bw-gen-err.tmp" -Force -ErrorAction SilentlyContinue + # 用 Process + UTF8 编码读取 (PS2EXE 默认 GBK 会乱码) + $psi = New-Object System.Diagnostics.ProcessStartInfo + $psi.FileName = (Get-Command node -ErrorAction Stop).Source + $psi.Arguments = "`"$GenScript`" $days -k `"$key`" -u `"$user`"" + $psi.UseShellExecute = $false + $psi.RedirectStandardOutput = $true + $psi.RedirectStandardError = $true + $psi.StandardOutputEncoding = [System.Text.Encoding]::UTF8 + $psi.StandardErrorEncoding = [System.Text.Encoding]::UTF8 + $psi.CreateNoWindow = $true + $psi.WorkingDirectory = $ScriptDir + $proc = [System.Diagnostics.Process]::Start($psi) + $stdout = $proc.StandardOutput.ReadToEnd() + $stderr = $proc.StandardError.ReadToEnd() + $proc.WaitForExit() if ($proc.ExitCode -ne 0) { throw "gen-authcode.js 退出码 $($proc.ExitCode)`n$stderr" } - # 解析输出 - $authCode = if ($stdout -match '授权码:\s*(BW-\d{8}-[A-F0-9]{24})') { $Matches[1] } else { "" } - $fileId = if ($stdout -match '文件 ID:\s*([a-f0-9]{8})\s*→\s*(secrets-[a-f0-9]{8}\.enc)') { $Matches[2] } else { "" } - $expiry = if ($stdout -match '有效期:\s*\d+\s*天\s*\(至\s*(\d{4}-\d{2}-\d{2})\)') { $Matches[1] } else { "" } + # 解析输出 (编码无关: PS2EXE 下中文可能乱码, 只匹配 ASCII 格式) + $authCode = if ($stdout -match '(BW-\d{8}-[A-F0-9]{24})') { $Matches[1] } else { "" } + $fileId = if ($stdout -match '(secrets-[a-f0-9]{8}\.enc)') { $Matches[1] } else { "" } + $expiry = if ($stdout -match '(\d{4}-\d{2}-\d{2})') { $Matches[1] } else { "" } if ($authCode) { $txtAuthCode.Text = $authCode $lblDetails.Text = "用户: $user`n加密文件: $fileId`n有效期: $days 天 (至 $expiry)`n路径: $ScriptDir\$fileId" $btnCopy.Enabled = $true - $statusBar.Text = " 生成成功 — $fileId | 请 git add + push 后将授权码发给 $user" + $btnPush.Enabled = $true + $global:lastGenUser = $user + $global:lastGenFile = $fileId + $statusBar.Text = " 生成成功 — 点击「推送到 Gitea」完成部署" $statusBar.ForeColor = $successGreen + + # 追加历史记录 (仅本机, 已在 .gitignore) + $historyFile = Join-Path $ScriptDir "authcode-history.log" + $logLine = "$(Get-Date -Format 'yyyy-MM-dd HH:mm') $($user.PadRight(12)) $fileId $($days)天 至$expiry $authCode" + try { Add-Content -Path $historyFile -Value $logLine -Encoding utf8 } catch {} } else { throw "无法解析授权码输出:`n$stdout" } @@ -341,3 +463,20 @@ $btnGenerate.Add_Click({ # ─── 启动 ───────────────────────────────────────────── $form.Add_Shown({ $txtUser.Focus() }) [System.Windows.Forms.Application]::Run($form) + +} catch { + # 全局错误弹窗 (PS2EXE 下唯一的错误可见方式) + $errMsg = "Bookworm AuthGen 启动失败:`n`n" + + "错误: $($_.Exception.Message)`n" + + "行号: $($_.InvocationInfo.ScriptLineNumber)`n" + + "代码: $($_.InvocationInfo.Line.Trim())`n`n" + + "ScriptDir: $ScriptDir`n" + + "GenScript: $GenScript`n" + + "SecretsTxt: $SecretsTxt" + try { + [System.Windows.Forms.MessageBox]::Show($errMsg, "AuthGen 错误", "OK", "Error") + } catch { + # 如果连 MsgBox 都失败 (WinForms 未加载), 写文件 + $errMsg | Out-File "$env:TEMP\bookworm-authgen-error.txt" -Encoding utf8 + } +} diff --git a/download-panel.html b/download-panel.html deleted file mode 100644 index d5de565..0000000 --- a/download-panel.html +++ /dev/null @@ -1,85 +0,0 @@ - - diff --git a/download.html b/download.html deleted file mode 100644 index b4beb8f..0000000 --- a/download.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - - -Bookworm - 下载安装 - - - - -
- -

Bookworm Portable

-

AI 编程助手 — 一键安装,即刻使用

-
- 92 Skills - 18 Agents - 34 Hooks - AES-256 加密 -
- - - ⬇ 下载安装程序 - -

Bookworm-Setup.bat (4 KB) — 双击即可安装

- -
- 1 下载上方 .bat 文件
- 2 双击运行 (如提示安全警告,选 "仍要运行")
- 3 输入管理员提供的密码
- 完成!桌面出现 Bookworm 图标 -
- -
- ⚠ 前置要求:
- • Node.js (下载) + Git (下载)
- • 代理/VPN 软件 (国内必须,用于首次连接验证) -
-
- - diff --git a/encrypt-secrets.ps1 b/encrypt-secrets.ps1 deleted file mode 100644 index 72a0fb5..0000000 --- a/encrypt-secrets.ps1 +++ /dev/null @@ -1,177 +0,0 @@ -<# -.SYNOPSIS - Bookworm Portable - 凭证加密工具 -.DESCRIPTION - 将 API Key 和 MCP 凭证加密为 secrets.enc, - 存放于 Gitea/USB 上, 安装时解密为进程级环境变量. -.USAGE - # 交互式创建加密凭证文件 - .\encrypt-secrets.ps1 - - # 从现有 .env 文件加密 - .\encrypt-secrets.ps1 -FromFile "C:\path\to\.env" - - # 解密验证 - .\encrypt-secrets.ps1 -Decrypt -#> - -param( - [string]$FromFile, - [switch]$Decrypt -) - -$ScriptDir = if ($MyInvocation.MyCommand.Path) { - Split-Path -Parent $MyInvocation.MyCommand.Path -} else { $PWD.Path } -$SecretsEnc = Join-Path $ScriptDir "secrets.enc" -$TempFile = Join-Path $env:TEMP "bw-secrets-$([guid]::NewGuid().ToString('N').Substring(0,8)).tmp" - -# 检查 openssl -$opensslCmd = Get-Command openssl -ErrorAction SilentlyContinue -if (-not $opensslCmd) { - # 搜索常见 Git 安装路径 - $searchPaths = @( - "C:\Program Files\Git\usr\bin\openssl.exe", - "D:\Git\usr\bin\openssl.exe", - "D:\Git\mingw64\bin\openssl.exe", - "C:\Program Files\Git\mingw64\bin\openssl.exe", - "C:\Program Files (x86)\Git\usr\bin\openssl.exe" - ) - $found = $searchPaths | Where-Object { Test-Path $_ } | Select-Object -First 1 - if ($found) { - $opensslCmd = $found - Write-Host "[INFO] 使用 Git 内置 openssl: $found" -ForegroundColor Gray - } - else { - Write-Host "[ERROR] openssl 未找到。请确认 Git for Windows 已安装。" -ForegroundColor Red - exit 1 - } -} else { - $opensslCmd = $opensslCmd.Source -} - -# ─── 解密模式 ───────────────────────────────────────── -if ($Decrypt) { - if (-not (Test-Path $SecretsEnc)) { - Write-Host "[ERROR] secrets.enc 不存在" -ForegroundColor Red - exit 1 - } - $password = Read-Host "输入主密码" -AsSecureString - $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password) - $plainPwd = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) - - Write-Host "`n=== 解密内容 ===" -ForegroundColor Cyan - # 通过 stdin 传入密码,避免进程列表泄露 - $plainPwd | & $opensslCmd enc -aes-256-cbc -d -pbkdf2 -iter 600000 -in $SecretsEnc -pass stdin - Write-Host "`n=== 结束 ===" -ForegroundColor Cyan - - $plainPwd = $null - [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) - exit 0 -} - -# ─── 加密模式 ───────────────────────────────────────── -Write-Host "" -Write-Host " Bookworm Portable - 凭证加密工具" -ForegroundColor Cyan -Write-Host " =================================" -ForegroundColor Cyan -Write-Host "" - -try { - -if ($FromFile -and (Test-Path $FromFile)) { - Write-Host "从文件加载: $FromFile" -ForegroundColor Gray - $invalidLines = Get-Content $FromFile | Where-Object { $_.Trim() -and $_ -notmatch '^[A-Z][A-Z0-9_]+=.+$' } - if ($invalidLines) { - Write-Host "[WARN] 以下行格式不正确 (应为 KEY=value):" -ForegroundColor Yellow - $invalidLines | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow } - } - Copy-Item $FromFile $TempFile -} -else { - Write-Host "请输入凭证 (key=value 格式, 每行一个, 空行结束):" -ForegroundColor White - Write-Host "常见 Key:" -ForegroundColor Gray - Write-Host " ANTHROPIC_API_KEY=sk-ant-..." -ForegroundColor DarkGray - Write-Host " ANTHROPIC_BASE_URL=https://your-relay.com/v1" -ForegroundColor DarkGray - Write-Host " GITHUB_PERSONAL_ACCESS_TOKEN=ghp_..." -ForegroundColor DarkGray - Write-Host " SLACK_BOT_TOKEN=xoxb-..." -ForegroundColor DarkGray - Write-Host " ATLASSIAN_API_TOKEN=..." -ForegroundColor DarkGray - Write-Host " BROWSERBASE_API_KEY=..." -ForegroundColor DarkGray - Write-Host " BROWSERBASE_PROJECT_ID=..." -ForegroundColor DarkGray - Write-Host " FIRECRAWL_API_KEY=..." -ForegroundColor DarkGray - Write-Host "" - - $lines = @() - while ($true) { - $line = Read-Host ">" - if ([string]::IsNullOrWhiteSpace($line)) { break } - if ($line -match '^[A-Z][A-Z0-9_]+=.+$') { - $lines += $line - $key = $line.Substring(0, $line.IndexOf('=')) - Write-Host " [+] $key" -ForegroundColor Green - } - else { - Write-Host " [!] 格式不正确,应为 KEY=value" -ForegroundColor Yellow - } - } - - if ($lines.Count -eq 0) { - Write-Host "[!] 未输入任何凭证,退出" -ForegroundColor Yellow - exit 0 - } - - $lines -join "`n" | Set-Content $TempFile -NoNewline -} - -# 设置密码并加密 -Write-Host "" -$password1 = Read-Host "设置主密码 (用于解密, 至少 12 位)" -AsSecureString -$password2 = Read-Host "确认主密码" -AsSecureString - -$bstr1 = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password1) -$bstr2 = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password2) -$pwd1 = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr1) -$pwd2 = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr2) - -if ($pwd1 -ne $pwd2) { - Write-Host "[ERROR] 密码不匹配" -ForegroundColor Red - exit 1 -} - -if ($pwd1.Length -lt 12) { - Write-Host "[ERROR] 密码至少 12 位 (推荐 16+ 位混合字符)" -ForegroundColor Red - exit 1 -} - -# AES-256-CBC 加密, PBKDF2 600000 迭代 (OWASP 2023), 通过 stdin 传入密码 -$pwd1 | & $opensslCmd enc -aes-256-cbc -pbkdf2 -iter 600000 -salt -in $TempFile -out $SecretsEnc -pass stdin - -# 安全删除临时文件 -if (Test-Path $TempFile) { - $bytes = [System.IO.File]::ReadAllBytes($TempFile) - [Array]::Clear($bytes, 0, $bytes.Length) - [System.IO.File]::WriteAllBytes($TempFile, $bytes) - Remove-Item $TempFile -Force -} - -# 清除内存 -$pwd1 = $null; $pwd2 = $null -[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr1) -[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr2) - -$size = (Get-Item $SecretsEnc).Length -Write-Host "" -Write-Host " [OK] secrets.enc 已生成 ($size bytes)" -ForegroundColor Green -Write-Host " 加密: AES-256-CBC + PBKDF2 (600000 迭代)" -ForegroundColor Gray -Write-Host " 路径: $SecretsEnc" -ForegroundColor Gray -Write-Host "" -Write-Host " 重要提醒:" -ForegroundColor Yellow -Write-Host " - 主密码无法找回,请牢记" -ForegroundColor Yellow -Write-Host " - 推送到 Gitea bookworm-boot 仓库即可" -ForegroundColor Yellow -Write-Host " - 验证: .\encrypt-secrets.ps1 -Decrypt" -ForegroundColor Yellow - -} finally { - # 确保任何退出路径都清理临时文件 - if (Test-Path $TempFile -ErrorAction SilentlyContinue) { - Remove-Item $TempFile -Force -ErrorAction SilentlyContinue - } -} diff --git a/guide-mac.html b/guide-mac.html deleted file mode 100644 index e07c644..0000000 --- a/guide-mac.html +++ /dev/null @@ -1,788 +0,0 @@ - - - - - -Bookworm Portable for Mac - 保姆式安装手册 - - - - - -
-
  ____              _
- | __ )  ___   ___ | | ____      _____  _ __ _ __ ___
- |  _ \ / _ \ / _ \| |/ /\ \ /\ / / _ \| '__| '_ ` _ \
- | |_) | (_) | (_) |   <  \ V  V / (_) | |  | | | | | |
- |____/ \___/ \___/|_|\_\  \_/\_/ \___/|_|  |_| |_| |_|
-  
-
macOS Edition
-

Bookworm Portable 保姆式安装手册

-

从零开始,一步步教你在任意 Mac 电脑上激活 Bookworm

-
- 92 Skills - 18 Agents - 34 Hooks - AES-256 加密 - HTTPS 传输 -
- - ⬇ 下载一键安装脚本 -
- -
- - -
-

整体流程概览

- -
-

最快方式:一键安装脚本

-

下载 Bookworm-Setup.sh → 在终端运行 → 输入密码 → 完成

-

脚本自动检测依赖、安装 Homebrew/Node.js/Git、下载配置、启动 Claude Code

-
- -

手动安装流程:

-
-
安装依赖
Homebrew + Node.js
- -
安装 Claude Code
npm 全局安装
- -
运行安装脚本
或 git clone
- -
输入密码
主密码
- -
开始使用
Bookworm 激活
-
-

首次安装约 10 分钟(含依赖下载),之后每次启动约 5-15 秒

-
- - - - -
-

1安装依赖软件

- -
- -
- 国内必须:代理/VPN 软件
- Claude Code 启动时会检查 api.anthropic.com,国内无法直连。
- 请先安装并启动代理软件(ClashX / Surge / V2Ray / 任意 VPN),安装脚本会自动检测系统代理。
- 无代理 = Claude Code 无法启动。 -
-
- -
- 💡 -
- 中转站不走代理
- API 中转站 bww.letcareme.com 部署在国内阿里云,不需要通过代理访问
- 安装脚本已自动设置 NO_PROXY=bww.letcareme.com,code.letcareme.com,无需手动配置。
- 如果代理软件有"绕过规则"设置,建议把 *.letcareme.com 加入直连列表。 -
-
- -

需要安装以下软件。如果已装过可跳到下一步。

- - -
-
A
-
-

安装 Homebrew(macOS 包管理器)

-

Homebrew 是 macOS 上最常用的包管理器,后续用它安装 Node.js 和 Git。

-
-
- -

打开 终端(按 ⌘ + 空格 搜索 "终端" 或 "Terminal"),执行:

-
- /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -
- -
- 💡 -
- 国内加速
- 如果下载太慢,可以使用清华镜像:
- export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git"
- export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git"
- 设置后重新执行上面的安装命令。 -
-
- -
- -
- Apple Silicon (M1/M2/M3/M4) 用户注意!
- 安装完成后,需要将 Homebrew 添加到 PATH。终端会提示你执行以下命令: -
-
-
- # Apple Silicon Mac 需要执行(Intel Mac 不需要) -echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile -eval "$(/opt/homebrew/bin/brew shellenv)" -
- -

验证安装成功:

-
- brew --version # 应显示 Homebrew 4.x.x -
- - -
-
B
-
-

安装 Node.js(必须)

-

用 Homebrew 一行命令安装。

-
-
-
- brew install node -
-

也可以从 nodejs.org 下载 .pkg 安装包。

- -

验证安装成功:

-
- node -v # 应显示 v22.x.x -npm -v # 应显示 10.x.x -
- - -
-
C
-
-

安装 Git(必须)

-

macOS 通常自带 Git,如没有则用 Homebrew 安装。

-
-
-
- # 检查是否已安装 -git --version - -# 如果提示安装 Xcode Command Line Tools,点击"安装"即可 -# 或者用 Homebrew 安装: -brew install git -
-
- - - - -
-

2安装 Claude Code

- -
-
1
-
-

全局安装

-

在终端中执行:

-
-
-
- npm i -g @anthropic-ai/claude-code -
-

安装过程需要几分钟,等待完成即可。如果报权限错误,在前面加 sudo

- -
-
2
-
-

验证安装

-
-
-
- claude --version # 应显示版本号 -
- -
- 💡 -
- 不需要登录 Claude 账号!
- Bookworm 使用中转站 API,安装 Claude Code 后直接进入下一步,不用执行 claude login。 -
-
-
- - - - -
-

3安装 Bookworm(核心步骤)

- -
-
1
-
-

克隆引导仓库

-

在终端中执行以下命令。系统会提示输入用户名和密码。

-
-
-
- git clone https://code.letcareme.com/bookworm/bookworm-boot.git -cd bookworm-boot -
- -
- 弹出用户名密码? 输入管理员提供给你的 Gitea 账号密码。这是 Gitea 的密码,不是主密码。 -
- -
-
2
-
-

运行安装脚本

-

在终端中执行安装脚本:

-
-
-
- bash Bookworm-Setup.sh -
-

如果提示权限不足:chmod +x Bookworm-Setup.sh && ./Bookworm-Setup.sh

- -
-
3
-
-

输入主密码

-

脚本会提示 "输入主密码解密凭证",输入管理员提供的主密码(不是 Gitea 密码),按回车。

-

密码输入时不显示字符,这是正常的。输错了可以重试,最多 3 次。

-
-
- -
- 两个密码不要搞混:
- Gitea 密码 = 克隆仓库时输入的,用于下载文件
- 主密码 = 解密 API 凭证时输入的,用于启动 Claude Code -
- -
-
4
-
-

等待完成

-

脚本会显示步骤进度 [1/8] 到 [8/8],自动完成:

-
    -
  • [1/6] 检查依赖 (Homebrew / Node.js / Git / OpenSSL / Claude Code)
  • -
  • [2/6] 自动检测代理 + 设置 NO_PROXY
  • -
  • [3/6] 同步配置 (克隆/更新 92 个 Skills)
  • -
  • [4/6] 解密凭证 (输入主密码) + 渲染配置模板
  • -
  • [5/6] 配置终端别名 (bw / bw-update)
  • -
  • [6/6] 完成 — 可选立即启动
  • -
-
-
- -
- -
- 看到 "Bookworm 就绪" 绿色横幅就说明成功了!
- Claude Code 启动后,脚本会验证 Skills/Hooks/配置 完整性,全部 [OK] 后进入 Bookworm 模式。
- 所有 API 请求通过中转站转发,不需要自己的 Claude 账号。 -
-
- -
- -
- 看到 "原生模式启动" 黄色横幅?
- 说明 Bookworm 配置不完整。请重新运行安装脚本,或联系管理员。 -
-
-
- - - - -
-

4日常使用

- -
-

方法一:终端别名(推荐,最简单)

-

安装脚本已自动添加别名到 ~/.zshrc,直接在终端输入:

-
-
- bw # 快速启动 -bw-update # 同步更新 -
- -
-

方法二:脚本命令

-

如果别名不可用,在终端中手动执行:

-
- - - - - - - - - - - - -
操作命令说明
快速启动bw直接启动 Claude Code + Bookworm
同步更新bw-update更新 boot + 配置仓库
- -
- 💡 -
- 启动时显示 "有 N 个新更新可用"?
- 说明管理员更新了 Skills 或 Hooks。执行 bw-update 即可同步。 -
-
-
- - - - -
-

5密码说明

- -
-

本系统有两个密码,不要搞混

- - - - -
名称用途何时输入
Gitea 密码下载文件(克隆仓库)首次安装时 git 弹出要求
主密码解密 API 凭证每次启动脚本提示输入
-
- -
- 💡 -
- 密码输错了? 最多可以重试 3 次,不用紧张。3 次都错才会退出。 -
-
- -
-

本日免密功能

-

首次解密成功后,脚本会询问 "今日内免密启动? (y/n)"

-

y 后,当天再次启动无需输入主密码,次日自动过期。

-

凭证缓存在 macOS 钥匙串 (Keychain) 中,仅当前用户可读。

-
- -
- 🔒 -
主密码无法找回 — 忘记后联系管理员重新生成 secrets.enc。
-
-
- - - - -
-

6使用完毕 — 清理 / 卸载

- -

在终端中执行清理命令:

- - - - - - - - - - - - - - - - - -
场景命令说明
基础清理rm -rf ~/.claude删除 Bookworm 配置,保留引导仓库供重新安装
完整恢复rm -rf ~/.claude ~/bookworm-boot删除所有 Bookworm 文件
深度清理rm -rf ~/.claude ~/bookworm-boot && sed -i '' '/Bookworm Portable/,+2d' ~/.zshrc && git credential-osxkeychain erase <<< "host=code.letcareme.com"完整恢复 + 清除别名 + 清除 Git 凭证
- -
- -
- 在他人电脑/公用电脑上务必清理:
- 执行深度清理命令,确保不留下任何凭证或配置 -
-
-
- - - - -
-

!常见问题排查

- -
-

❌ brew 命令找不到

-

原因:Homebrew 未添加到 PATH(Apple Silicon Mac 常见)。

-

解决:

-
-
- # Apple Silicon (M1/M2/M3/M4) -eval "$(/opt/homebrew/bin/brew shellenv)" - -# Intel Mac -eval "$(/usr/local/bin/brew shellenv)" -
- -
-

❌ npm 全局安装报 Permission denied

-

原因:macOS 默认目录权限限制。

-

解决方式一(推荐):修改 npm 全局目录:

-
-
- mkdir -p ~/.npm-global -npm config set prefix '~/.npm-global' -echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc -source ~/.zshrc - -# 然后重新安装 -npm i -g @anthropic-ai/claude-code -
-

解决方式二:在命令前加 sudo(简单但不推荐长期使用)。

- -
-

❌ git clone 失败 / 认证失败

-

解决步骤:

-
    -
  1. 浏览器打开 https://code.letcareme.com 确认网站可访问
  2. -
  3. 确认用户名密码正确(区分大小写)
  4. -
  5. 如果 macOS 弹出钥匙串对话框,点"始终允许"
  6. -
  7. 检查网络是否需要代理
  8. -
-
- -
-

❌ 解密凭证失败 / 主密码错误

-

原因:主密码区分大小写,且无法找回。

-

解决:仔细检查密码是否正确。如确认忘记,联系管理员重新生成 secrets.enc

-
- -
-

❌ openssl 版本不兼容

-

原因:macOS 自带 LibreSSL,部分加密参数可能不同。

-

解决:安装 OpenSSL:

-
-
- brew install openssl -# 脚本会自动检测 Homebrew 安装的 openssl 路径 -
- -
-

❌ ECONNRESET / "Unable to connect to API"

-

原因:代理软件把国内中转站 bww.letcareme.com 的流量也走了国际线路。

-

解决:手动设置 NO_PROXY 后重试:

-
-
- # 设置中转站直连(不走代理) -export NO_PROXY="bww.letcareme.com,code.letcareme.com" - -# 重新启动 -bw -
-

或在代理软件中将 *.letcareme.com 加入直连规则。

- -
-

❌ "Not logged in" / 直接运行 claude 报错

-

原因:API 凭证是进程级环境变量,只在安装脚本启动的进程中有效。

-

解决:不要直接运行 claude,必须通过以下方式启动:

-
    -
  • 终端输入 bw(推荐)
  • -
  • cd ~/bookworm-boot && bash Bookworm-Setup.sh
  • -
-
- -
-

❌ 安装包下载太慢

-

解决:设置淘宝镜像:

-
-
- # npm 淘宝镜像 -npm config set registry https://registry.npmmirror.com - -# Homebrew 清华镜像 -export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git" - -# 然后重新安装 -npm i -g @anthropic-ai/claude-code -
- -
-

❌ 需要自己的 Claude 账号吗?

-

不需要。所有 API 请求通过中转站转发,消耗中转站额度。目标机不需要任何 Anthropic 账号或订阅。

-
-
- - - - -
-

安装检查清单

-

逐项确认,全部打勾即可开始使用:

-
    -
  • Homebrew 已安装brew --version 显示版本号
  • -
  • Node.js 已安装node -v 显示版本号
  • -
  • Git 已安装git --version 显示版本号
  • -
  • npm 可用npm -v 显示版本号
  • -
  • Claude Code 已安装claude --version 显示版本号
  • -
  • 已获取 Gitea 账号密码 — 管理员提供
  • -
  • 已获取主密码 — 管理员提供(用于解密 API 凭证)
  • -
  • 能访问 code.letcareme.com — 浏览器打开确认
  • -
  • 代理/VPN 已启动 — 国内必须,脚本自动检测 (ClashX/Surge/V2Ray 等)
  • -
-
- - - - -
-

快速参考

- - - - - - - - -
操作快捷方式完整命令
首次安装git clone + bash Bookworm-Setup.shcd ~/bookworm-boot && bash Bookworm-Setup.sh
快速启动bwNO_PROXY="bww.letcareme.com,code.letcareme.com,localhost,127.0.0.1" claude
同步更新bw-updatecd ~/bookworm-boot && git pull && cd ~/.claude && git pull
基础清理rm -rf ~/.claude
完整恢复rm -rf ~/.claude ~/bookworm-boot
深度清理rm -rf ~/.claude ~/bookworm-boot && sed -i '' '/Bookworm/,+2d' ~/.zshrc
-
- - - - -
-

安全须知

- - - - - - -
特性规格
凭证加密AES-256-CBC + PBKDF2 (600,000 迭代)
传输加密HTTPS (TLS 1.2+, Let's Encrypt 证书)
凭证存储进程级环境变量 + 可选本日缓存 (macOS Keychain, 当日 23:59 过期)
登录保护fail2ban (5 次失败/小时 → 封禁 24 小时)
-
- 🔒 -
- 主密码无法找回 — 请妥善保管。忘记后需管理员重新生成加密凭证。 -
-
-
- -
- - - - - - - diff --git a/guide.html b/guide.html deleted file mode 100644 index 26c3189..0000000 --- a/guide.html +++ /dev/null @@ -1,787 +0,0 @@ - - - - - -Bookworm Portable - 保姆式安装手册 - - - - - -
-
-  ____              _
- | __ )  ___   ___ | | ____      _____  _ __ _ __ ___
- |  _ \ / _ \ / _ \| |/ /\ \ /\ / / _ \| '__| '_ ` _ \
- | |_) | (_) | (_) |   <  \ V  V / (_) | |  | | | | | |
- |____/ \___/ \___/|_|\_\  \_/\_/ \___/|_|  |_| |_| |_|
-  
-

Bookworm Portable 保姆式安装手册

-

从零开始,一步步教你在任意 Windows 电脑上激活 Bookworm

-
- 92 Skills - 18 Agents - 34 Hooks - AES-256 加密 - NDA 技术保密 -
- ⬇ 下载一键安装器 -
- -
- - -
-

整体流程概览

- -
-

最快方式:一键安装器

-

获取 Bookworm-Setup.bat (4KB) → 双击运行 → 输入密码 → 完成

-

安装器自动检测依赖、下载配置、创建桌面快捷方式、启动 Claude Code

-
- -

手动安装流程:

-
-
安装依赖
Node.js + Git
- -
安装 Claude Code
npm 全局安装
- -
双击安装器
或 git clone
- -
输入密码
主密码
- -
开始使用
Bookworm 激活
-
-

首次安装约 10 分钟(含依赖下载),之后每次双击启动约 10-30 秒

-
- - - - -
-

1安装依赖软件

- -
- -
- 国内必须:代理/VPN 软件
- Claude Code 启动时会检查 api.anthropic.com,国内无法直连。
- 请先安装并启动代理软件(Clash / V2Ray / 快柠檬 / 任意 VPN),安装脚本会自动检测系统代理。
- 无代理 = Claude Code 无法启动。 -
-
- -
- 💡 -
- 中转站不走代理
- API 中转站 bww.letcareme.com 部署在国内阿里云,不需要通过代理访问
- 安装脚本已自动设置 NO_PROXY=bww.letcareme.com,code.letcareme.com,无需手动配置。
- 如果代理软件有"绕过规则"设置,建议把 *.letcareme.com 加入直连列表。 -
-
- -

需要安装以下软件。如果已装过可跳到下一步。

- - -
-
A
-
-

安装 Node.js(必须)

-

去官网下载 LTS 版本安装包,双击安装,一路 Next 即可。

-
-
- -
-

方式一:官网下载(推荐)

-

打开浏览器访问 https://nodejs.org,点击绿色的 "LTS 推荐" 按钮下载,双击 .msi 文件安装,全部默认 Next。

-
- -
-

方式二:PowerShell 命令安装

-

右键开始菜单 → 选择 "PowerShell (管理员)""终端 (管理员)",然后执行:

-
-
- PowerShell (管理员) - # 下载 Node.js 安装包 -Invoke-WebRequest -Uri "https://nodejs.org/dist/v22.15.0/node-v22.15.0-x64.msi" -OutFile "$env:TEMP\node-install.msi" - -# 运行安装(会弹出安装向导,一路 Next) -Start-Process msiexec.exe -ArgumentList "/i $env:TEMP\node-install.msi" -Wait -
- -
- -
- 安装完成后必须重开 PowerShell!
- 关闭当前 PowerShell 窗口,重新打开一个新的,否则 nodenpm 命令找不到。 -
-
- -

验证安装成功:

-
- node -v # 应显示 v22.x.x -npm -v # 应显示 10.x.x -
- -
- 如果 npm 报 "执行策略" 错误? 执行以下命令后重试:
- Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
- 提示确认时输入 Y 回车。 -
- - -
-
B
-
-

安装 Git(必须)

-

去官网下载安装,全部默认设置即可。

-
-
-
-

打开 https://git-scm.com/download/win,下载 "64-bit Git for Windows Setup",双击安装,全部 Next。

-
-

验证:

-
- git --version # 应显示 git version 2.x.x -
- - -
-
C
-
-

安装 PowerShell 7(推荐)

-

Windows 自带的 PowerShell 5.1 有中文兼容问题,建议升级到 7。

-
-
-
- PowerShell (管理员) - winget install Microsoft.PowerShell -
-

如果没有 winget,去 GitHub Releases 下载 .msi 安装包。安装后用 pwsh 命令启动新版 PowerShell。

-
- - - - -
-

2安装 Claude Code

- -
-
1
-
-

全局安装

-

在 PowerShell 中执行(不需要管理员权限):

-
-
-
- npm i -g @anthropic-ai/claude-code -
-

安装过程需要几分钟,等待完成即可。

- -
-
2
-
-

验证安装

-
-
-
- claude --version # 应显示版本号 -
- -
- 💡 -
- 不需要登录 Claude 账号!
- Bookworm 使用中转站 API,安装 Claude Code 后直接进入下一步,不用执行 claude login。 -
-
-
- - - - -
-

3安装 Bookworm(核心步骤)

- -
-
1
-
-

克隆引导仓库

-

在 PowerShell 中执行以下命令。系统会提示输入用户名和密码。

-
-
-
- git clone https://code.letcareme.com/bookworm/bookworm-boot.git -cd bookworm-boot -
- -
- 弹出用户名密码? 输入管理员提供给你的 Gitea 账号密码。这是 Gitea 的密码,不是主密码。 -
- -
-
2
-
-

双击运行安装脚本

-

双击文件夹里的 更新并启动Bookworm.bat,脚本会自动完成所有配置。

-
-
- -
-

或者用命令行运行

-

如果双击 .bat 不起作用,在 PowerShell 中手动执行:

-
-
- pwsh -ExecutionPolicy Bypass -File install.ps1 -
-

没有 pwsh 可用 powershell 替代。

- -
-
3
-
-

输入主密码

-

脚本会提示 "输入主密码解密凭证",输入管理员提供的主密码(不是 Gitea 密码),按回车。

-

密码输入时不显示字符,这是正常的。输错了可以重试,最多 3 次。

-
-
- -
- 两个密码不要搞混:
- Gitea 密码 = 克隆仓库时输入的,用于下载文件
- 主密码 = 解密 API 凭证时输入的,用于启动 Claude Code -
- -
-
4
-
-

等待完成

-

脚本会显示步骤进度 [1/9] 到 [9/9],自动完成:

-
    -
  • [1/9] 前置检查 (Claude Code / Node.js / Git)
  • -
  • [2/9] 自动检测代理 + 设置 NO_PROXY
  • -
  • [3/9] 解密凭证 (输入主密码)
  • -
  • [4/9] 同步配置 (下载专家技能库)
  • -
  • [5/9] 完整性校验 (SHA256 哈希验证)
  • -
  • [6/9] 渲染配置模板
  • -
  • [7/9] 初始化本地目录
  • -
  • [8/9] Bookworm 系统验证 + MCP 检查
  • -
  • [9/9] 启动 Claude Code
  • -
-
-
- -
- -
- 看到 "Bookworm 就绪" 绿色横幅就说明成功了!
- Claude Code 启动后,脚本会验证配置完整性,全部 [OK] 后进入 Bookworm 模式。
- 所有 API 请求通过中转站转发,不需要自己的 Claude 账号。 -
-
- -
- -
- 看到 "原生模式启动" 黄色横幅?
- 说明 Bookworm 配置不完整。请不加 -StartOnly 重新运行安装脚本,或联系管理员。 -
-
-
- - - - -
-

4日常使用

- -
-

方法一:双击 .bat 文件(推荐,最简单)

-

bookworm-boot 文件夹里有两个 .bat 文件:

-
- - - - - - - - - - - - -
文件作用适用场景
启动Bookworm.bat快速启动,不更新配置每天日常使用
更新并启动Bookworm.bat先同步最新 Skills 再启动管理员通知有更新时
-

双击即可,无需打开 PowerShell,无需记命令。脚本会自动检测 PowerShell 7/5.1。

- -
- 💡 -
- 启动时显示 "有新更新可用"?
- 说明管理员推送了更新。双击 更新并启动Bookworm.bat 即可同步。 -
-
- -
-

方法二:命令行(备用)

-

如果 .bat 文件无法运行,在 PowerShell 中手动执行:

-
-
- # 快速启动 -cd bookworm-boot -pwsh -ExecutionPolicy Bypass -File install.ps1 -StartOnly - -# 同步更新后启动 -cd bookworm-boot -pwsh -ExecutionPolicy Bypass -File install.ps1 -
-
- - - - - - - -
-

5密码说明

- -
-

本系统有两个密码,不要搞混

- - - - -
名称用途何时输入
Gitea 密码下载文件(克隆仓库)首次安装时 git 弹出要求
主密码解密 API 凭证每次启动脚本提示输入
-
- -
- 💡 -
- 密码输错了? 最多可以重试 3 次,不用紧张。3 次都错才会退出。 -
-
- -
-

本日免密功能

-

首次解密成功后,脚本会询问 "今日内免密启动? (y/n)"

-

y 后,当天再次启动无需输入主密码,次日自动过期。

-

凭证缓存在 Windows Credential Manager 中(DPAPI 加密,仅当前用户可读)。

-
- -
- 🔒 -
主密码无法找回 — 忘记后联系管理员重新生成 secrets.enc。
-
-
- - - - -
-

6使用完毕 — 清理 / 卸载

- -
-

最简单:双击 卸载Bookworm.bat

-

bookworm-boot 文件夹里的 卸载Bookworm.bat,双击即可一键完整卸载:终止进程 + 清除凭证 + 恢复原始配置 + 删除桌面快捷方式。

-
- -

或者用命令行精细控制:

- - - - - - - - - - - - - - - - - -
场景命令说明
基础清理pwsh -File stop.ps1清除环境变量,保留配置供下次快速启动
完整恢复pwsh -File stop.ps1 -Restore删除 Bookworm,恢复电脑原始状态
深度清理pwsh -File stop.ps1 -Restore -Deep完整恢复 + 清除历史 + 清除 Git/凭证缓存
- -
- -
- 在他人电脑/公用电脑上务必卸载:
- 双击 卸载Bookworm.bat 或执行 pwsh -File stop.ps1 -Restore -Deep -
-
-
- - - - -
-

!常见问题排查

- -
-

❌ 输入 node -v 或 npm -v 提示 "无法识别"

-

原因:安装 Node.js 后没有重开 PowerShell 窗口,PATH 没刷新。

-

解决:关闭当前 PowerShell,重新打开一个新的窗口再试。

-

如果还不行,手动刷新 PATH:

-
-
- $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") -node -v -
- -
-

❌ npm 报 "执行策略" / "Execution Policy" 错误

-

原因:Windows 默认禁止运行脚本。

-

解决:

-
-
- Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -
-

提示确认时输入 Y 回车。之后 npm 和 pwsh 脚本都能正常运行。

- -
-

❌ 提示 "openssl 未找到"

-

原因:解密凭证需要 openssl,它随 Git for Windows 一起安装。

-

解决:确认 Git 已安装。脚本会自动搜索 C:\Program Files\GitD:\Git 下的 openssl。

-
- -
-

❌ git clone 失败 / 认证失败

-

解决步骤:

-
    -
  1. 浏览器打开 https://code.letcareme.com 确认网站可访问
  2. -
  3. 确认用户名密码正确(区分大小写)
  4. -
  5. 如果开了 2FA,需要用 Access Token 替代密码
  6. -
  7. 检查网络是否需要代理
  8. -
-
- -
-

❌ 解密凭证失败 / 主密码错误

-

原因:主密码区分大小写,且无法找回。

-

解决:仔细检查密码是否正确。如确认忘记,联系管理员重新生成 secrets.enc

-
- -
-

❌ Claude Code 启动后没有 Bookworm 横幅

-

原因:配置文件未正确同步。

-

解决:不加 -StartOnly 重新运行安装脚本,让它重新 clone:

-
-
- pwsh -ExecutionPolicy Bypass -File install.ps1 -
- -
-

❌ 安装包下载太慢

-

解决:Node.js 官网在国内可能较慢,可以用淘宝镜像:

-
-
- # 设置 npm 淘宝镜像(加速下载) -npm config set registry https://registry.npmmirror.com - -# 然后重新安装 Claude Code -npm i -g @anthropic-ai/claude-code -
- -
-

❌ ECONNRESET / "Unable to connect to API"

-

原因:代理软件把国内中转站 bww.letcareme.com 的流量也走了国际线路,导致连接被重置。

-

解决:在 PowerShell 中手动设置 NO_PROXY 后重试:

-
-
- # 设置中转站直连(不走代理) -$env:NO_PROXY = "bww.letcareme.com,code.letcareme.com" - -# 重新启动 -cd bookworm-boot -pwsh -ExecutionPolicy Bypass -File install.ps1 -StartOnly -
-

新版安装脚本已自动设置 NO_PROXY,git pull 更新后此问题不再出现。

- -
-

❌ "Not logged in" / 直接运行 claude 报错

-

原因:API 凭证是进程级环境变量,只在安装脚本启动的进程中有效。新开 PowerShell 窗口直接运行 claude 没有凭证。

-

解决:不要直接运行 claude,必须通过以下方式启动:

-
    -
  • 双击桌面 Bookworm 快捷方式
  • -
  • 双击 启动Bookworm.bat
  • -
  • 命令行:pwsh -ExecutionPolicy Bypass -File install.ps1 -StartOnly
  • -
-
- -
-

❌ 完整性校验不匹配(大量文件 WARN)

-

原因:本地配置文件已被更新(管理员推送了新版本),但 integrity.sha256 未同步更新。

-

解决:y 继续即可,不影响使用。管理员会在下个版本同步哈希文件。

-
- -
-

❌ 需要自己的 Claude 账号吗?

-

不需要。所有 API 请求通过中转站转发,消耗中转站额度。目标机不需要任何 Anthropic 账号或订阅。

-
- -
-

❌ 询问 AI 系统内部信息时被拒绝了?

-

这是正常行为。Bookworm 的技能库、路由引擎、配置架构属于技术保密范围,AI 被设定为不披露这些信息。

-

解决:直接告诉 AI 你要完成的任务(写代码、分析问题、设计方案等),它会自动调用最合适的专家能力来帮你。无需了解内部机制即可获得完整服务。

-
-
- - - - -
-

安装检查清单

-

逐项确认,全部打勾即可开始使用:

-
    -
  • Node.js 已安装node -v 显示版本号
  • -
  • Git 已安装git --version 显示版本号
  • -
  • npm 可用npm -v 显示版本号(如报错先设 ExecutionPolicy)
  • -
  • Claude Code 已安装claude --version 显示版本号
  • -
  • PowerShell 7 已安装pwsh --version 显示 7.x(推荐但非必须)
  • -
  • 已获取 Gitea 账号密码 — 管理员提供
  • -
  • 已获取主密码 — 管理员提供(用于解密 API 凭证)
  • -
  • 能访问 code.letcareme.com — 浏览器打开确认
  • -
  • 代理/VPN 已启动 — 国内必须,脚本自动检测 (Clash/V2Ray/快柠檬等)
  • -
-
- - - - -
-

快速参考

- - - - - - - - -
操作最简方式命令行方式
首次安装git clone + 双击
更新并启动Bookworm.bat
pwsh -ExecutionPolicy Bypass -File install.ps1
快速启动双击 启动Bookworm.batpwsh -File install.ps1 -StartOnly
同步更新双击 更新并启动Bookworm.batpwsh -File install.ps1
基础清理pwsh -ExecutionPolicy Bypass -File stop.ps1
完整恢复pwsh -ExecutionPolicy Bypass -File stop.ps1 -Restore
深度清理pwsh -ExecutionPolicy Bypass -File stop.ps1 -Restore -Deep
-
- - - - -
-

安全须知

- - - - - - -
特性规格
凭证加密AES-256-CBC + PBKDF2 (600,000 迭代)
传输加密HTTPS (TLS 1.2+, Let's Encrypt 证书)
凭证存储进程级环境变量 + 可选本日缓存 (Windows Credential Manager, DPAPI 加密, 当日 23:59 过期)
登录保护fail2ban (5 次失败/小时 → 封禁 24 小时)
-
- 🔒 -
- 主密码无法找回 — 请妥善保管。忘记后需管理员重新生成加密凭证。 -
-
-
- -
- - - - - - - diff --git a/lessons-learned.md b/lessons-learned.md deleted file mode 100644 index aa3330f..0000000 --- a/lessons-learned.md +++ /dev/null @@ -1,206 +0,0 @@ -# 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* diff --git a/启动Bookworm-v3.bat b/启动Bookworm-v3.bat deleted file mode 100644 index 416c2f2..0000000 --- a/启动Bookworm-v3.bat +++ /dev/null @@ -1,17 +0,0 @@ -@echo off -setlocal -chcp 65001 > nul 2>&1 -title Bookworm Smart Assistant - -:: 日常启动入口: 静默更新 + 直接启动 (无需管理员权限) -set "NO_PROXY=bww.letcareme.com,code.letcareme.com,letcareme.com,localhost,127.0.0.1" - -where node >nul 2>nul -if %errorlevel% equ 0 goto :RUN -echo [!!] Node.js 未安装, 请先运行 Bookworm-Install.bat -pause -exit /b 1 - -:RUN -node "%~dp0setup-all.js" --start -endlocal