Files

90 lines
6.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 技术蓝图 — DSH 服务端登录插件
> 🧭 [← 返回 README](../README.md) · 部署(模式 A):[deployment](deployment.md) · 部署(模式 B):[k8s-deployment](k8s-deployment.md)
多租户托管平台,让用户通过域名安全访问自己的 DeepSeek Harness(DSH)实例。本文是权威技术设计;实现与本文冲突时以本文为准,并同步回改。
## 1. 运行拓扑
```
用户浏览器
├─(HTTPS)─> dsh.<域名> → 编排服务 Fastify(认证/管理/桌面/域名,/api/*)
└─(HTTPS)─> <用户名>.dsh.<域名> → 编排服务按 Host 头路由到该用户 DSH(HTTP + WebSocket)
```
- **主域 `dsh.<域名>`**:编排服务自己的登录 / 管理台 / 桌面 / API。
- **每用户子域 `<用户名>.dsh.<域名>`**:编排服务按 Host 头解析用户名 → 校验会话 cookie → 反向代理到该用户 DSH 的 `127.0.0.1:<动态端口>`(HTTP + WebSocket 隧道)。
- 每用户 DSH 只绑回环端口、不直接暴露公网;端口表随 spawn/respawn 即时更新,nginx 用通配 `*.dsh.<域名>` 透传即可,无需每次 reload。
- 编排服务是独立 Node 进程,`child_process.spawn('dsh --profile web --host 127.0.0.1 --port <随机>', ...)` 拉起每用户 DSH(主 + 按需守护)。
## 2. 打包与启动
- **主入口**:独立 `dshs` bin(`node lib/cli.js`)直接跑 Fastify + SQLite + 进程编排。
- **市场识别**:根 `package.json` 的 `dsh` 字段(`plugin`/`kind`/`bundle.patch`)+ `cordis.patch.yml`;不 import 任何 `@deepseek-ai/*` 宿主包,peerDependencies 为空,规避宿主包遮蔽。
- **cordis 入口 `apply()` 是带守卫空操作**:默认无副作用,装进任意 profile 都不起服务器。
- **产物型分发**:提交 `lib/`(构建产物),`prepare` = `npm run build` 供 git 安装自构建。
## 3. spawn 每用户 DSH
```ts
spawn(dshBinPath, ['--profile', 'web', '--patch', mainPatchPath, '--host', '127.0.0.1', '--port', String(port)], {
cwd: workspacePath, // 用户在桌面选的文件夹
env: { ...scrubEnv(process.env), HOME: workspacePath, DSH_HOME: homeDir, DEEPSEEK_API_KEY: userApiKey },
stdio: ['ignore', 'pipe', 'pipe'],
detached: false,
})
```
- env 擦除镜像 harness 的 `scrubbedParentEnv`/`SENSITIVE_ENV_PATTERN` 思路:只向子进程显式注入已解析 key。
- 端口是 CLI flag(`--port`),**不是** env、**不是** patch(踩坑记录见 [troubleshooting.md](troubleshooting.md)「端口冲突」)。
- 进程树 teardown 自行实现:SIGTERM → grace → SIGKILL。
## 4. 双 DSH「共享对话 + 崩溃接管」
**共享状态 = 每用户 `$DSH_HOME` 里的持久会话日志**(append-only;`session-persistence` 落盘)。
- **主 DSH** 独占实时会话并持续 append;绑定回环端口对外服务。
- **守护 DSH** 是**按需拉起的一次性 headless DSH**(不常驻):主 DSH 崩溃时、或需执行 post-restart 命令时,编排服务才 spawn 一次;它与主 DSH 同 home/同 workspace,通过 `loadStoredFrom(id, fromSeq)` 读同一日志,修复/resume 后退出。
**崩溃接管闭环**(主 DSH 崩溃时,编排服务拉起一次守护 DSH 并自动重启主 DSH):
1. **诊断**:读退出码 + stderr 尾部 + 会话日志尾部,判定崩溃点。
2. **修复会话日志**:`interruptedTurnClosers`(`packages/core/session/src/repair.ts`)+ `session-persistence.load`/`commitRepair` 把中断 turn 合成 `tool/result`/`step/end`/`turn/end{interrupted}`,产出可恢复的合法转录。
3. **修复根因**:守护 DSH 以 agent 身份(对共享 workspace 有工具权限)修文件/配置、摘坏插件、杀卡死子进程。
4. **接手会话**:`ctx.sessionPersistence.prepare`/`load`(或 `ctx.sessions.create({seed})`)恢复修复后的日志,接续对话成为新主 DSH;随后可选重拉 fresh 主 DSH 并退回守护位。
**计划内重启(装插件)**:主 DSH 退出前把「post-restart 自动命令」写成 JSON 落到 `$DSH_HOME`,守护 DSH 执行重启后命令。
**关键澄清**:「接手」= 顺序 failover(恢复同一持久日志续接对话),非两个活体同时驱动同一 turn——这是 harness 的 resume 语义,无需自建双向活体通道。
## 5. 数据模型(SQLite,migration v1)
- v1 使用:`users`、`sessions`、`workspaces`、`folder_plugins`、`dsh_instances`、`audit_log`。
- 预留:`domains`、`credential_vault`(references-not-secrets,密文引用不落明文)。
字段与约束见 `src/db/schema.ts`。
## 6. API 面
| 组 | 路由 | 脚手架状态 |
|---|---|---|
| Auth | `POST /api/auth/register\|login\|logout`、`GET /api/auth/me` | 已实现(P1) |
| Admin | `GET /api/admin/users`、`POST /api/admin/users/:id/approve\|disable\|enable` | 已实现(P1;disable 会同时删会话 + 停 DSH) |
| Desktop/FS | `GET /api/desktop/tree`、`POST /api/fs/mkdir\|upload\|create` | 已实现(P2;路径经词法 + 符号链接双重围栏) |
| 凭据库 | `GET/POST /api/me/keys`、`POST /api/me/keys/:id/select`、`DELETE /api/me/keys/:id` | 已实现(每用户命名密钥,AES-256-GCM 加密落库) |
| DSH | `POST /api/dsh/launch\|stop\|restart`、`GET /api/dsh/status`、`GET /u/:slug/dsh/*` | 已实现(P3/P5,HTTP 代理;main+watchdog 编排层) |
| Plugin | `GET /api/plugins`、`POST /api/plugins/select` | 已实现(P4) |
| Domain/nginx | `GET/PUT /api/domain`、`POST /api/nginx/regen`、`GET /api/admin/domains`、`POST /api/admin/domains/:id/verify` | 已实现(P6,管理员手动验证;ACME 待接入) |
| 静态 | `GET /*`(占位 SPA) | 已接 |
## 7. 安全模型
默认软隔离(每用户 `$DSH_HOME` + session `cwd` + 沙箱写隔离);Linux 生产建议开启账号级硬隔离(每用户 OS 账号),见 [hard-isolation.md](hard-isolation.md)。两个兜底:
- **端口守卫(portGuard)**:iptables OUTPUT owner-match 规则,阻止同机其他账号直连各用户 DSH 的回环端口、绕过编排服务认证;不支持的环境下开启即拒绝启动。
- **路径围栏**:所有文件面先做词法包含校验(`resolveWithinRoot`),再逐段 `lstat` 拒绝符号链接分量,防止经工作区内的链接越出用户根。
## 8. 分阶段路线
P1–P7 已完成:登录审核、桌面/FS、单 DSH 启动、每文件夹插件、守护/双 DSH、域名/nginx、硬隔离。模式 B(K8s + Postgres + leader election + reconcile,每用户 Pod)已落地:清单见 `deploy/`,部署教程见 [k8s-deployment.md](k8s-deployment.md)。