70 lines
3.8 KiB
Markdown
70 lines
3.8 KiB
Markdown
# 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` **之后**追加:
|
|||
|
|
|
|||
|
|
```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 <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 范畴)。
|