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 一律写「远程服务器」。
71 lines
3.8 KiB
Markdown
71 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 范畴)。
|