# 40-共享技能层挂载修复(bundled-skills 进实例命名空间)(2026-09-11 落地) ## 背景与动机 档案 38a 的 P0:共享技能层「投了也发现不了」。 - `config.bundledSkillDir` = `/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` **之后**追加: ```ts ...(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 `)= 一次性把全部用户 home + DB + 凭据放进每个实例。改完必须逐字复核 args 并重跑可见面实测。 ## 回滚 / 注意 - **回滚**:`cp src/supervisor/orchestrator.ts.bak- 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 范畴)。