# 技术蓝图 — DSH 服务端登录插件 多租户托管平台,让用户通过域名安全访问自己的 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. 打包与启动 - **主入口**:独立 `dsh-server-login` 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, '--cwd', workspacePath], { cwd: workspacePath, env: { ...scrubEnv(process.env), DSH_HOME: homeDir, DEEPSEEK_API_KEY: userApiKey }, stdio: ['ignore', 'pipe', 'pipe'], detached: false, }) ``` - env 擦除镜像 harness 的 `scrubbedParentEnv`/`SENSITIVE_ENV_PATTERN` 思路:只向子进程显式注入已解析 key。 - 进程树 teardown 自行实现:SIGTERM → grace → SIGKILL(Windows `taskkill /T /F`)。 ## 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` | 已实现(P1) | | Desktop/FS | `GET /api/desktop/tree`、`POST /api/fs/mkdir\|upload` | 已实现(P2) | | 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/POST /api/admin/domains` | 已实现(P6,管理员手动验证;ACME 待接入) | | 静态 | `GET /*`(占位 SPA) | 已接 | ## 7. 安全模型 默认软隔离(每用户 `$DSH_HOME` + session `cwd` + 沙箱写隔离);Linux 生产建议开启账号级硬隔离(每用户 OS 账号)。详见 [deployment.md](deployment.md)。 ## 8. 分阶段路线 P1–P7 已完成:登录审核、桌面/FS、单 DSH 启动、每文件夹插件、守护/双 DSH、域名/nginx、硬隔离。