Files
dsh_shenxian/dsh-server-docs/04-调整方案/40-共享技能层挂载修复.md
T
admin 5ad755116e chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
   保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
   工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
   必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
   + ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
   ⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
   验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

3.8 KiB
Raw Blame History

40-共享技能层挂载修复(bundled-skills 进实例命名空间)(2026-09-11 落地)

背景与动机

档案 38a 的 P0:共享技能层「投了也发现不了」。

  • config.bundledSkillDir = <dataRoot>/bundled-skills(/var/lib/dshs/bundled-skills), orchestrator.ts 已把它注入 env DSH_BUNDLED_SKILL_DIR(ALLOWED_ENV 也放行)。
  • 但 @deepseek-ai/dsh-skill-filesystem 是在实例内读盘: lib/index.js:84 config.bundledSkillDir ?? process.env.DSH_BUNDLED_SKILL_DIR → resolve() → lib/index.js:181-183 roots.push({ path: this.bundledSkillDir, source: "bundled" }) → discoverRoot(root, …)。
  • 而平台 bwrap 只注入 env、从未挂载该目录 → 实例内 ls = No such file or directory → 技能投进去也永远发现不了(档案 10/11 的共享技能层实际失效)。

用户决策

「bundled-skills 的调整确保用户访问权限不扩大就行」

→ 修挂载,但必须证明可见面不变。已按此口径实测(见「验证记录」)。

实现

src/supervisor/orchestrator.ts · spawnAsUser,在 '--bind', root, root 之后追加:

...(this.config.bundledSkillDir !== ''
  ? (['--ro-bind-try', this.config.bundledSkillDir, this.config.bundledSkillDir] as string[])
  : []),
  • 放在 --bind root root 之后:bwrap 后写覆盖前写(与 profile 三文件同理)。
  • 用 --ro-bind-try 而非 --ro-bind:目录若被误删,实例仍能起来(不崩)。
  • 仅当 config.bundledSkillDir !== '' 时追加,保留"未配置即不暴露"的语义。

验证记录

以平台原样 bwrap 参数 + 本行、以 guest 的 uid(100002)在生产机实跑。

检查 结果
mountinfo …/bundled-skills → 同名 ro,nosuid,nodev(精确叶子路径 + 只读)
实例内 ls $DSH_BUNDLED_SKILL_DIR mcn-probe(探针技能可见)
实例内 readFileSync($DSH_BUNDLED_SKILL_DIR + '/mcn-probe/SKILL.md') OK,读到 name: mcn-probe ← 即 skill-filesystem 的读取条件成立
实例内 realpathSync($DSH_BUNDLED_SKILL_DIR) /var/lib/dshs/bundled-skills
挂载点可写 touch = Read-only file system
ls …/users/ 1 项(只列用户自己) ← 可见面未扩大
读 dshs.db NO
/etc 可见项 13(未被本改动影响)
真实启动实例 正常打印 dsh web: http://127.0.0.1:41998/?token=…

探针技能验证后已删除,宿主 bundled-skills/ 恢复为空。

事故 / 踩坑记录

  • 光注入 env 没用:DSH_BUNDLED_SKILL_DIR 一直在 ALLOWED_ENV + baseEnv 里, 看上去"配置齐全",但 skill-filesystem 是实例内读盘,env 只决定"去哪儿找", 目录不挂载就是找不到。判据:插件在实例内 resolve() + 读盘的东西,必须真的出现在 命名空间里(通用化为本 skill 的铁律)。
  • ⛔ 只能挂最内层路径。写成挂父目录(--ro-bind <dataRoot> <dataRoot>)= 一次性把全部用户 home + DB + 凭据放进每个实例。改完必须逐字复核 args 并重跑可见面实测。

回滚 / 注意

  • 回滚:cp src/supervisor/orchestrator.ts.bak-<ts> src/supervisor/orchestrator.ts && npm run build && systemctl restart dshs
  • 副作用:bundled-skills/ 内容对每个登录用户可读(设计意图)→ 该目录 禁止放内部文档 / 私有提示词 / 凭据;投放清单须按"全员可见"审一遍。
  • 无 per-user 关闭能力:dsh 技能层无 disable/deny 机制,rank 600 的共享层内容用户关不掉 → 只适合"全员基线技能";要可启停必须走 rank 400 用户层($DSH_HOME/skills,档案 41 方案)。
  • 现状:机制已通,但尚未投放任何技能(投放属档案 41 范畴)。