技术蓝图

September 7, 2026 · View on GitHub

多租户托管平台,让用户在局域网内(Docker 部署 + 内网 IP 直连)安全访问自己的 DeepSeek Harness(DSH)实例。本文是权威技术设计;实现与本文冲突时以本文为准,并同步回改。

1. 运行拓扑

用户浏览器
   ├─(HTTP)─> http://<内网IP>:3080        → 编排服务 Fastify(认证/管理/桌面/共享配置,/api/*)
   └─(HTTP)─> http://<内网IP>:<子端口>    → per-instance forwarder → 该用户 DSH 的 127.0.0.1 回环端口
  • 编排服务端口:编排服务自己的登录 / 管理台 / 桌面 / API。
  • 每用户子端口:DSH CLI 拒绝绑定 0.0.0.0,子 DSH 只绑回环端口;编排服务为每个运行中的实例在容器 eth0 上起一个内置 HTTP/WS forwarder(剥 Origin、注入 crypto.randomUUID polyfill、改写 loopback 门、交接 dsh ≥0.1.2-alpha.5 web 首页的 launchToken 认证门——见 deployment-docker.md),端口固定在 DSH_PORT_MIN/MAX 段内供 Docker 映射。
  • 编排服务是独立 Node 进程,child_process.spawn('dsh --profile web --host 127.0.0.1 --port <段内端口>', ...) 拉起每用户 DSH(主 + 按需守护)。

2. 打包与启动

  • 主入口:独立 dsh-admin bin(node lib/cli.js)直接跑 Fastify + SQLite + 进程编排。
  • 市场识别:根 package.jsondsh 字段(plugin/kind/bundle.patch)+ cordis.patch.yml;不 import 任何 @deepseek-ai/* 宿主包,peerDependencies 为空,规避宿主包遮蔽。
  • cordis 入口 apply() 是带守卫空操作:默认无副作用,装进任意 profile 都不起服务器。
  • 源码型分发:仓库不含 lib/ 构建产物(.gitignore 排除);prepare = npm run build,git 安装时自构建(市场侧按源码型弹构建确认,见 STANDARD.md §2.2)。

3. spawn 每用户 DSH

// 主实例(端口取自 DSH_PORT_MIN/MAX 段;--patch 仅在 enablePatch 时传入):
spawn(dshCommand, ['--profile', 'web', '--host', '127.0.0.1', '--port', String(port), ...patchArgs], {
  cwd: workspacePath,
  env: { ...scrubEnv(process.env), HOME: workspacePath, DSH_HOME: homeDir },
  stdio: ['ignore', 'pipe', 'pipe'],
})
// 看门狗:一次性 headless 实例,任务以位置参数传入。
spawn(dshCommand, ['--profile', 'headless', WATCHDOG_TASK], { /* 同上 */ })
  • env 擦除镜像 harness 的 scrubbedParentEnv/SENSITIVE_ENV_PATTERN 思路:从允许列表重建子进程环境,再注入解析好的每用户取值(HOME 指向工作区、DSH_HOME 指向状态目录)。
  • 进程树 teardown 自行实现:SIGTERM → 5s 宽限 → SIGKILL(Windows 下信号均映射为进程终止)。
  • dsh CLI 版本探测Supervisor.dshVersion() 运行 <dshCommand> --version 取首个非空行(stdout/stderr 合并,退出码不敏感;4s 超时;失败为 null,不影响状态接口)。dsh 二进制支持免重建热更新(bind mount 替换),因此结果按 60s TTL 缓存 + 并发去重,而非进程启动时探测一次;/api/dsh/status/api/admin/instances 均携带 dshVersion 字段供前端展示。

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–2 步的 会话日志修复依赖 harness 内部机制(interruptedTurnClosers / session-persistence), 随与真实 harness 集成接入。

  1. 诊断:读退出码 + stderr 尾部 + 会话日志尾部,判定崩溃点。
  2. 修复会话日志interruptedTurnCloserspackages/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 落到 users/<id>/handoff.json(刻意放在 $DSH_HOME 之外——修复流程可能清空 home),守护 DSH 在重启后读取并执行。

关键澄清:「接手」= 顺序 failover(恢复同一持久日志续接对话),非两个活体同时驱动同一 turn——这是 harness 的 resume 语义,无需自建双向活体通道。

5. 数据模型(SQLite,migration v1)

  • v1 使用:userssessionsworkspacesfolder_pluginsaudit_log。v3 增:shared_config / shared_config_state(共享模型配置)。v4 删:credential_vault(每用户密钥库)。v5 删:domains(域名/nginx 部署已移除)。v6 删:dsh_instances 幽灵表与 users.api_key_ref 残留列(实例状态按设计仅存内存)。v7 增:sessions.last_used_at(设备管理)、app_settings(注册开关/邀请码)、market_items / user_plugins(离线插件市场),删 folder_plugins.description 死列。v8 删:users.home_dir / users.approved_by 死列。v9 增:market_items.validation / disclosure / pack_meta(导入期校验结论、披露声明、打包元数据)。v10 增:market_items.shared(推送全员)与 user_plugins.source(user/shared 安装来源)。v11 增:scheduled_tasks / scheduled_task_runs(定时 agent 任务与运行历史)。v12 增:inbound_webhooks / webhook_fire_log(token 触发器与触发日志,token 只存 SHA-256)。

字段与约束见 src/db/schema.ts

6. API 面

路由脚手架状态
AuthPOST /api/auth/register|login|logoutGET /api/auth/meGET /api/meta(注册门禁状态)、POST /api/me/passwordGET /api/me/sessionsDELETE /api/me/sessions/:id已实现(P1/P8)
AdminGET /api/admin/usersPOST /api/admin/users/:id/approve|disable|enable|reset-password|deleteGET/PUT /api/admin/settingsGET /api/admin/audit已实现(P1/P8)
OpsGET /healthzGET /api/admin/instances(含 dsh CLI 版本行)、POST /api/admin/instances/:userId/stopPOST /api/admin/instances/stop-allGET /api/admin/storageGET /api/admin/dsh-cli(CLI 目录状态)、POST /api/admin/dsh-cli/update(上传 dsh-cli.tgz 就地更新,需 DSH_ADMIN_DSH_CLI_DIR已实现(P8/P9)
Desktop/FSGET /api/desktop/treePOST /api/fs/mkdir|create|upload(multipart)|delete|rename|move|writeGET /api/fs/read(文本预览)、GET /api/fs/raw(下载/内联流,支持 Range)、GET /api/fs/zip(目录打包)、GET /api/fs/search(全工作区搜索)已实现(P2/P8;上传为 multipart 流式,文件夹上传保留相对路径)
DSHPOST /api/dsh/launch|stop|restartGET /api/dsh/status(含连续重启计数/熔断态 + dsh CLI 版本行)已实现(P3/P5/P8,内网直连 + forwarder;main+watchdog 编排层)
PluginGET /api/pluginsPOST /api/plugins/select已实现(P4)
MarketGET/POST/DELETE /api/admin/market*POST /api/admin/market/:id/validatePOST /api/admin/market/:id/sharedGET/PUT /api/admin/shared-patchGET /api/me/marketPOST /api/me/market/:id/installPOST /api/me/market/uninstall已实现(P8,见 plugins-market.md;P9 增多根导入/披露/启动探测/hot reload 语义;P10 增推送全员与共享 patch 层)
TasksGET/POST /api/me/tasksPUT/DELETE /api/me/tasks/:idPOST /api/me/tasks/:id/runGET /api/me/tasks/:id/runs已实现(P11,src/scheduler/scheduler.ts:30s 到期轮询 → Supervisor.runHeadless 一次性执行;单用户并发 1;超时 DSH_ADMIN_TASK_TIMEOUT_MS 默认 30 分钟)
WebhooksPOST /hooks/:token(公开触发,限流 30/min)、GET/POST /api/me/webhooksPOST /api/me/webhooks/:id/enabledDELETE /api/me/webhooks/:idGET /api/me/webhooks/:id/fires已实现(P11,token 只存 SHA-256;触发即异步 headless 执行并记日志)
SharedConfigGET/PUT /api/admin/shared-configGET /api/me/shared-configPOST /api/me/shared-config/accept已实现(shared-config.md
静态GET /*web/ 下的桌面 SPA:desktop.html + window-manager.js + file-explorer.js + shared-config-editor.js + account.js + market.js + admin-extras.js已接

7. 安全模型

默认软隔离(每用户 $DSH_HOME + session cwd + 沙箱写隔离);Linux 部署建议开启账号级硬隔离(每用户 OS 账号)。详见 hard-isolation.md

8. 分阶段路线

P1–P7 已完成:登录审核、桌面/FS、单 DSH 启动、每文件夹插件、守护/双 DSH、硬隔离、共享模型配置(域名/nginx 与每用户密钥库已随内网-only 瘦身移除)。

P8(v0.2.0)已完成:账号与会话安全(自助改密/设备管理/审计日志/删用户/注册门禁)、运维面板(全局实例视图与单停、磁盘统计、/healthz、崩溃熔断 + 指数退避)、文件管理器增强(在线文本编辑、目录 zip 下载、排序与全工作区搜索)、插件/技能离线市场(管理员 tgz 收录 → 用户安装/更新/卸载,见 plugins-market.md)。

P9 已完成(依托 dsh 0.1.2-rc.1 插件化能力):

  • 插件免重启生效:rc.1 的 web profile 模板默认 patchReload: "live"(监视 profile 级与 home 级 patch 文件),市场装/卸 cordis 插件写完 cordis.patch.yml 即被运行中实例热重载;安装响应携带 reload: hot|restart|none,按 dsh --version 判定(≥0.1.2-rc.1 为 hot),版本未知保守回退重启语义。
  • 市场导入增强:多包仓库/技能合集子目录扫描(STANDARD §1 顺序 5/8/9,深度 3,逐条目独立存储);披露徽章(STANDARD §9 最小子集:cloud/network/apiKeys/offlineMode/jurisdiction/retention);offline-packager *.meta.json 附带导入;bundledDependencies 覆盖全部运行时依赖时标记「自包含」。
  • 沙箱启动探测:导入期在临时 home 里先空 profile 启动建基线、再装插件重启一次,以 launchToken 行为就绪信号——模块加载失败/非法插件形态/双注册类启动崩溃在收录时即拦截(dsh 不可用 → skipped 降级;实测 --dump-config 不加载第三方插件模块,故不做 dump 级校验)。
  • dsh CLI 平台内热更新:管理台上传 pack-dsh.ps1 产出的 dsh-cli.tgz → 挂载目录内解包校验 → 原子替换 node_modulesDSH_ADMIN_DSH_CLI_DIR,见 deployment-docker.md)+「停止全部运行实例」。

P10 已完成(共享资源面,见 plugins-market.md):

  • 推送全员:技能/agent 预设可标记 shared,launch 前自动同步进每个用户 home(src/fs/shared-sync.ts);用户不可卸载 shared 来源安装(409),取消推送时降级回自装。cordis 插件刻意不推送(§6.4 双通道冲突)。
  • 管理员共享 patch 层:维护 home 级 $DSH_HOME/cordis.patch.yml(dsh 配置叠加的独立层,与市场 profile 级正交),保存前经形状校验 + 沙箱启动探测(坏 patch 会 fail loud 打挂全员启动),保存即全员原子同步,live 重载即时生效。

P11 已完成(自动化面,复用 Supervisor.runHeadless):

  • 定时 agent 任务:每用户以 interval / daily 两种排程定义自然语言 prompt,调度器 30s 轮询到期任务 → 一次性 headless DSH 在该用户工作区执行;单用户并发 1、fire 先重排(服务器崩溃不重复触发、错过的启动后补跑)、输出尾部落运行历史、孤儿运行启动时清扫(src/scheduler/scheduler.ts)。
  • 入站 webhookPOST /hooks/:token 公开触发(限流 30/min,token 128-bit、库里只存 SHA-256、失败一律 404),命中即异步 headless 执行(body 的 message 拼进 prompt),202 立即返回;触发日志独立落表(src/web/routes/webhooks.ts)。

后续候选:会话用量报表(token-meter)、TOTP/LDAP、通知中心。