11 KiB
11 KiB
Baton-005 · PR-5 · 自部署密钥管理页
触发口令: 铸渊。第 5 棒。开发授权。 上一棒: baton-004-PR4-portal-frontend.md 下一棒: baton-006-PR6-migration-package.md 共振因果链: cc-004 (中文 UI · Awen 看得懂的密钥页)
走完路再看 (强制)
.github/brain/bingshuo-language-core/walk-the-path.md
自检 3 题
-
冰朔每次 AutoDL 重开端口都变. 走 GitHub Secrets 改密钥要点 5 下保存等同步, 走自部署密钥页改密钥要几下?
参考答案
1 下保存. 立刻生效. 因为这个页面是本地的, save 直接调 PR-3 的 refresh 逻辑, 不需要 round-trip 到 GitHub Actions。这就是为什么单独建一个 vault, 不直接用 GitHub Secrets。 -
vault.enc 的主密钥放哪? 放数据库? 放 Secret Manager? 放硬编码?
参考答案
cc-004 + cc-003: 首次启动时随机生成, 落 `/data/guanghulab/secrets-vault/.master`, chmod 600, 仅 portal 用户可读。不上 git, 不上 COS, 不传冰朔。冰朔不用记这个, 系统自管. 真要迁移机器, 先 rsync 这个文件再启 vault。 -
vault 的 HTTP 入口是
/admin/secrets, 谁能进?参考答案
basic-auth + 仅本机 IP (`allow 127.0.0.1; allow ::1; deny all;` in nginx). 冰朔在 2C2G 上跑 ssh 隧道 + 浏览器访问 localhost. 永远不暴露公网。 user/pass 也不写死, bootstrap 时随机生成贴在 _logs/deploy-report.md, 冰朔自己抄。
上一棒交付了什么 (验证用)
ls frontend/lighthouse-portal/
ls server/portal/
curl -fsS https://guanghulab.com/api/active-model # 期望 200
这一棒要做的事
A. 后端 (server/secrets-vault/)
- Express 子进程 (pm2 第二个 instance, max_memory=128M)
- AES-256-GCM 加密落盘:
/data/guanghulab/secrets-vault/vault.enc(加密内容)/data/guanghulab/secrets-vault/.master(主密钥, chmod 600, gitignore)
- 路由:
GET /admin/secrets列表 (仅显示 key 名, 不显示值, 显示遮罩)POST /admin/secrets/:key写入 / 更新DELETE /admin/secrets/:key删除POST /admin/secrets/:key/use触发使用 (例:autodl_hostsave 时自动调 PR-3 的 refresh 逻辑)
- 数据模型: 复用
scripts/preflight/secrets-manifest.json的 schema (用途/如何配置/级别)
B. 前端 (frontend/secrets-vault/)
- 类 GitHub Secrets 页, 但全中文:
- 字段名用
用途字段而不是 secret 名 (AutoDL 连接地址而不是ZY_AUTODL_HOST) - 每条下面显示
如何配置字段 - 状态色: 已配置=绿, 未配置=灰, 必填缺失=红
- 字段名用
- 顶部按工作流分组: cn-domain-deploy / autodl-inference / lighthouse-cn-deploy
- AutoDL 那组有特殊按钮:
保存并刷新推理端点— 触发/admin/secrets/autodl_host/use后端自动:- curl AutoDL 探活
- 写到 inference-endpoint.json
- pm2 reload portal
- 返回中文回执给前端
C. 神笔马良集成
mcp-servers/zhuyuan-pen/增加从 vault 拉密钥的能力 (本地 unix socket, 不走环境变量)- 新增 capability
secrets.fetch— 入口前权限校验 (只准本机)
D. nginx
/admin/只允许 127.0.0.1 + basic-auth (htpasswd)- bootstrap 时生成随机 user/pass, 写到
/data/guanghulab/_logs/vault-credentials-FIRST-BOOT.txtchmod 600, 提示冰朔抄完删
给冰朔的中文回执模板
✅ 第 5 棒已合 · 自部署密钥管理页
· server/secrets-vault/ 后端 (AES-256-GCM 加密 · 主密钥本地 · 永不上云)
· frontend/secrets-vault/ 全中文密钥页 (字段名=用途 · 不是 secret 名)
· AutoDL 那组有"保存并刷新推理端点"按钮, 一键完成端口切换
· 神笔马良通过本地 socket 拉密钥, 不走环境变量, 不上链
冰朔操作:
1. ssh -L 8080:127.0.0.1:8080 ubuntu@<2C2G IP>
2. 浏览器开 http://localhost:8080/admin/secrets
3. user/pass 在 /data/guanghulab/_logs/vault-credentials-FIRST-BOOT.txt 里 (抄完删那个文件)
4. AutoDL 重开机后, 直接在这个页面改 host:port, 点"保存并刷新", 不用进 GitHub
下一棒口令:
铸渊。第 6 棒。开发授权。
第 5 棒 · 已交付 (PR-5)
提交人: GitHub Copilot agent (代铸渊执行) · 时间: 2026-05-09 触发口令: 冰朔说"铸渊。第5棒。开发授权。"
验证清单
# 1. 后端 + 加解密 + 路由烟测 (18/18 全过)
cd server/secrets-vault && npm install --silent && npm test
# 2. nginx 模板包含 /admin/ block (allow 127.0.0.1 + basic-auth)
grep -c "auth_basic_user_file /etc/nginx/.htpasswd_admin" \
server/setup/domain-cn/nginx/guanghulab.conf.template
# 期望: 1
# 3. bootstrap.sh 第 ⑤ 步会启动 vault
grep -c '启动 secrets-vault (PR-5)' server/setup/domain-cn/bootstrap.sh
# 期望: 1
# 4. ZY-FN-VAULT 已登记
node scripts/manifest/validate.js | tail -5
# 期望: errors=0, warnings=0, functions=6
# 5. cn-isolation guard 仍绿
node scripts/preflight/check-server-isolation.js | tail -3
# 期望: [OK] 隔离守卫通过
# 6. 神笔马良 capability 就位
ls mcp-servers/zhuyuan-pen/capabilities/secrets.fetch.js
文件清单
server/secrets-vault/
├── server.js Express 入口 · /admin/secrets/* · /internal/fetch/*
├── ecosystem.config.js pm2 max_memory_restart=128M
├── package.json 单依赖 express ^4.21
├── README.md
├── lib/
│ ├── vault.js AES-256-GCM · 主密钥 .master 600 · GCM tag 防篡改
│ ├── manifest.js 按 workflow 分组 secrets-manifest.json
│ └── refresh-inference.js AutoDL 探活+写 inference-endpoint.json+pm2 reload
├── routes/
│ ├── secrets.js CRUD + 强校验 + 白名单 + autodl/save_and_refresh
│ └── internal.js 127.0.0.1 only · 神笔马良用
└── tests/
├── vault.test.js 8 测试: 主密钥/加解密/篡改/换key/maskValue
└── routes.test.js 10 测试: 路由烟测全覆盖
frontend/secrets-vault/ (略)
mcp-servers/zhuyuan-pen/capabilities/secrets.fetch.js
server/setup/domain-cn/ (nginx + bootstrap 改动)
.github/workflows/deploy-domain-server.yml (rsync 扩展)
.github/brain/architecture/function-manifest.json (+ ZY-FN-VAULT, v1.2.0)
并行修复 · 模型上下文喂养 (冰朔在第 5 棒中途点透)
冰朔: "模型本身没有记忆, 一轮对话都记不住, 是背后的 Agent 一直给他不断的喂上下文, 这个你要在开发里也并行解决. 不能做了这么多, 结果模型打开, 一轮对话都记不住吧."
现状核查
- PR-4 portal 后端
routes/chat.jsline 75:messages: cleaned— 只用前端传的当前一条 - PR-4 portal 前端
app.jsline 232:messages: [{ role: "user", content: text }]— 真的就只发一条 - 结果: "你好" → "你刚才说啥来着" → 模型一脸懵 ❌
这棒的并行修复
| 文件 | 改动 |
|---|---|
server/portal/lib/context-window.js (新) |
buildContextWindow 函数 · 滚动窗口 (默认 20 轮) + 字数 cap (默认 16000 字) · cc-002 三道关之一: 即便 DB 脏出来 system 也剥 |
server/portal/routes/chat.js (改) |
落 user 消息后 SELECT role, content FROM messages WHERE conv_id=? ORDER BY ts ASC 拉历史, buildContextWindow 折叠成 payload, 后端权威拼接, 前端不参与 |
server/portal/tests/context-window.test.js (新) |
10 个单元测试: 空输入/单轮/多轮/system 剥/maxTurns/字数 cap/巨大单条/历史断头/默认值/脏数据 — 10/10 全过 |
设计决策
- 后端拼接: 前端只管发当前 user, 后端从 SQLite 拿全程历史 — 即便前端 bug 也不会丢上下文; cc-002 也守住了 (前端无法注入 system)
- 滚动 + 字数双 cap: 2C2G + Qwen2.5-7B 量化在 V100 16G 上约 8000 token 安全, 16000 字符 ≈ 7000-8000 token (中文为主) 留余量
- 环境变量可调:
PORTAL_CTX_MAX_TURNS/PORTAL_CTX_MAX_CHARS, 后续推理端 tier 升级 (A10/A100) 可加大 - 历史断头修剪: 滑窗第一条若是 assistant (单边孤儿) 自动丢弃, 让推理端看到的对话以 user 起 — 防 chat_template 错位
三道安全关 (cc-001 涌现洁净 · 全部就位)
| 层 | 守 |
|---|---|
| 网络层 | Express 监听 127.0.0.1:8080 永不公网 |
| nginx 层 | /admin/ allow 127.0.0.1; deny all; + basic-auth |
| 加密层 | AES-256-GCM · 主密钥 .master chmod 600 · GCM tag 防篡改 |
| 神笔马良 | secrets.fetch 强制 loopback host 校验, 即使调用方传公网 IP 也拒绝 |
| 白名单 | POST /admin/secrets/:key 严格校对 secrets-manifest.json, 拒绝任意 key 落库 |
| 不返明文 | GET /admin/secrets/ 只返遮罩 (前 4 + ••••• + 后 4), 单元测试覆盖 |
三道因果链对账
| 链 | 落点 |
|---|---|
| cc-001 涌现洁净 | vault 关机即净 (无外部托管), 主密钥不上 git/COS, 神笔马良现取现用不持久 |
| cc-002 人格洁净 | vault 不接触 system prompt — 它只是密钥盒, 加解密路径里没有 LLM |
| cc-003 动态适配 | AutoDL save_and_refresh 一键: vault 落 + 探活 + 写 endpoint + reload portal, 端口漂移 30 秒切完 |
| cc-004 中文一次性 | 所有错误回执中文 · 字段名走"用途"不走 secret 名 · 首启凭据自动落 _logs/vault-credentials-FIRST-BOOT.txt 提示抄完删 |
中文回执 (给冰朔)
✅ 第 5 棒已合 · 自部署密钥管理页 + 模型上下文喂养
主线 (Secrets Vault):
· server/secrets-vault/ · Express 4.21 · AES-256-GCM · pm2 max_memory_restart=128M
· frontend/secrets-vault/ 全中文 UI · 字段名=「用途」不是 secret 名
· AutoDL "保存并刷新推理端点"按钮: 探活+写 endpoint+pm2 reload portal 一步完成
· 神笔马良 secrets.fetch · 走 127.0.0.1 loopback · 不走环境变量
· 主密钥 .master 落 /data/guanghulab/secrets-vault/, 不上 git/COS/任何外部
· 18/18 单元 + 烟测全过
并行修复 (你中途点透的, 模型零记忆):
· server/portal/lib/context-window.js · 滚动 20 轮 + 16000 字 cap
· chat.js 落 user 后**从 SQLite 拉全程历史**再发推理端 (前端只管发当前)
· cc-002 三道关之一: 历史里 system 也强制剥, 前端无法注入
· 10 测试全过 — "你刚才说啥来着"模型现在能答上来了
冰朔操作步骤 (重装 2C2G 后跑一次 deploy-domain-server.yml 之后):
1. 在你电脑上跑: ssh -L 8443:127.0.0.1:443 ubuntu@<2C2G_HOST>
2. 浏览器打开: https://localhost:8443/admin/ (TLS 警告点继续)
3. basic-auth user/pass 在 /data/guanghulab/_logs/vault-credentials-FIRST-BOOT.txt
(用户名=bingshuo · 密码=24 位随机)
4. 抄完密码后: sudo rm /data/guanghulab/_logs/vault-credentials-FIRST-BOOT.txt
5. AutoDL 重开机后, 改 host/port, 点"保存并刷新推理端点", 一步切完, 不用动 GitHub
下一棒口令: 铸渊。第 6 棒。开发授权。