# DeepSeek Harness 多租户容器管理平台 — 设计文档 > 发布架构变更(取代下文旧构建方案):新运行镜像仅由管理员通过 Git 发布检查与精确 commit 确认构建。`release.json`、完整 `image/` 及 runtime 初始化测试仅在 `dockerfile` 发布分支维护,main 不再内置或打包运行镜像资产。下文 npm latest 查询、内置构建上下文、实验构建、构建后创建预约及目录示意中的 `image/` 均为历史设计,不再是操作指南。这是 breaking change:移除 `/api/latest`、`/api/compatibility`、`POST /api/builds` 及 `/api/intents` 的全部查询、创建和取消接口,不提供 410 兼容入口。仅保留 `GET /api/builds` 和 `GET /api/builds/{id}` 构建历史读取。不迁移或恢复旧构建及旧创建意图,不在后台续跑或完成旧预约;这不意味着删除已有实例或数据。当前流程以 README.md、API.md、OPERATIONS.md 为准;以下设计与历史证据保留,不表示新架构已完成相同真实环境验收。 > 状态:本机管理面板核心流程已实现并通过真实 Docker 集成检查;生产隔离、硬配额等仍有差距,以 README.md 的实现边界与验证范围为准。下文是目标设计,不代表所有条目已实现。 > 目标项目:`deepseek-ai/deepseek-harness`(npm CLI `@deepseek-ai/dsh`,命令 `dsh web`) > 控制面技术栈:Go + 前端 SPA > 部署形态:单机 Docker daemon;面板自身亦运行于容器中 > 鉴权/配额:内置账号 + 目录配额 --- ## 0. 调研结论与硬约束 > 实现阶段实测更正:本地 npm `0.2.0-rc.2` 支持 `--trusted-host`,但拒绝 `--public-url`;下文基于 upstream master 的 public-url 描述不能直接用于该发行版。实际启动仅传 web/no-open/port/trusted-host,代理负责外部路径。`scripts/probe-runtime.mjs` 已验证默认 token 交换为 303 + cookie、认证首页 200;完整代理以集成测试为准。 针对本体 `deepseek-ai/deepseek-harness`(npm CLI `@deepseek-ai/dsh`,命令 `dsh web`)确认: | 事实 | 来源 | 对设计的影响 | |---|---|---| | **无官方 Docker 镜像/Dockerfile** | repo 根、`apps/`、`.gitlab-ci.yml` 均无 | 必须自建镜像包装 `dsh web` | | **`dsh web` 默认监听 `127.0.0.1:3080`,且启动时拒绝 `--host 0.0.0.0`**(安全原因) | `packages/bundle/web-app/README.md` | 容器内必须跑反向代理绑 `0.0.0.0` 转发到 loopback | | 端口/主机是 **CLI flag**(`--port/--host/--public-url/--trusted-host/--no-open`),**无 `DSH_PORT/DSH_HOST` 环境变量** | 同上 | 容器启动参数化靠 CMD args,不靠 env | | 数据根 = `$DSH_HOME`(默认 `~/.dsh`) | `docs/user/guide/providers.md`、CLI README | 每实例独立 `DSH_HOME`,bind-mount 即可保留数据 | | **多实例硬约束:每个容器必须独立 `DSH_HOME` + 独立端口**(task-board 单写者) | architecture.md | 一用户两容器 ⇒ 两个 `DSH_HOME`,不能共享 | | 模型/API Key 存储:密钥在 `$DSH_HOME/.credentials.yaml`(YAML,上游配置接口的 write-only 语义不限制本面板所有者文件访问),模型/provider 配置在 `$DSH_HOME/profiles//cordis.patch.yml`;`cordis.patch.yml` 只存 `apiKeyEnv` 引用 | `providers.md` | 备份"模型"= 备份这两个文件 | | **"profile 文件" = `profiles/web/cordis.patch.yml`(YAML,用户可编辑的 patch 层)**;profile 目录还有 `package.json`(含插件依赖 + `dsh.profile` bundle 清单)+ `node_modules`(pnpm 装的插件,可重建) | `apps/cli/README.md` | 编辑 profile = 编辑 `cordis.patch.yml`;插件清单在 `package.json` | | 升级:用户数据全在 `DSH_HOME`(npm 安装树之外),升级 `@deepseek-ai/dsh` 后数据保留;`node_modules` 由 pnpm 重建 | README、CLI README | "保留数据 + 重启新版容器"可行 | | 公网部署:`--public-url ` + `--trusted-host`;代理必须 **剥前缀、保留 Host、把 `Path=/` 的 cookie 改写为 mount + 加 Secure** | `docs/user/guide/public-deployments.md` | 面板反代要处理 cookie/前缀,是最复杂点 | | 启动 URL 带 **per-process token**,首次访问换签名 cookie | 同上 | 面板需从容器日志取 token,服务端代换 cookie | > 结论:DSH 不是为容器多租户设计,但靠"自建镜像 + 容器内反代 + 面板反代桥接 token/cookie + 每实例独立 DSH_HOME"可稳态支撑。 --- ## 1. 目标与范围 ### 已确认的访问与镜像策略(覆盖下文早期假设) - 面向可信团队,实例所有者可通过文件管理读取、下载、修改完整 DSH_HOME,包括自己的凭据。面板不承诺凭据 write-only;跨用户隔离、路径安全、配额与恢复状态保护保持不变。下载文件/打包目录会提示泄密风险,提示属于交互防误操作,不是阻止所有者访问的安全边界。 - 专用 Profile 编辑器提供 YAML 校验与受控停启流程,但通用文件操作、DSH 终端和 agent 不强制经过该流程;修改运行中配置存在不一致风险,建议先停机并备份。 - 镜像准入分为推荐、仍兼容、仅存量运行、禁止使用。兼容依据绑定精确版本、模板修订、平台与镜像身份;不再仅因历史模板不是当前模板就一律判为不可用,也不能仅因历史构建成功就认定兼容。 - 未验证 DSH 新版仅管理员可显式实验构建;保留既有完整性、依赖与启动检查。实验构建成功不是兼容验收,未明确准入的镜像不可新建实例或作为升级目标。管理员正向准入须填写验收依据并记录审计,不绕过实际镜像身份与验证记录检查。 - 通过 docker.sock 在单机上**创建/启停/删除**多个 `dsh web` 容器,**每用户最多 2 个**。 - 每用户一个**独立工作区**,支持文件**上传/下载/删除/重命名**,并有**容量配额**。 - 工作区**映射进每个 dsh 容器**(agent 在工作区内干活)。 - **备份/还原 dsh 配置**(含模型与插件清单)。 - **编辑 profile 文件**(`cordis.patch.yml`)。 - **升级**:保留数据,用新版本镜像重启容器。 - **本地镜像管理**:面板查询 DSH 最新版本,用户手动触发本机构建;创建实例时选择本地已有版本,或手动构建最新版后创建。不需要私有镜像仓库,不推送生成的镜像。 - 控制面:Go 后端 + SPA 前端;内置账号 + 目录配额;**面板自身容器化运行**。 --- ## 2. 总体架构 面板不是宿主进程,而是挂了 `docker.sock` 的**兄弟容器**(sibling),通过 sock 编排 dsh 兄弟容器,并通过同一 bridge 网络反代它们。 ``` ┌────────────── 宿主 ──────────────────┐ │ docker.sock ──┐ │ │ /var/lib/dsh-mgr ──┐ (bind) │ │ │ │ │ ┌─────────────────▼─────────────▼───┐ │ dsh-net (bridge) │ │ 面板容器 dsh-mgr │ │ :80/:443 发布 │ │ - /data ← /var/lib/dsh-mgr │ │ │ │ - /var/run/docker.sock │ │ │ │ - SPA + API + 反代 + 编排引擎 │ │ │ └─────────┬───────────────────────────┘ │ │ │ sock │ │ ┌─────────▼───────────┐ ┌───────────────┐ │ │ │ dsh-web: 容器1 │ │ 容器2 ... │ │ 不发布端口 │ │ caddy:8080 → 3080 │ │ │ │ /data/dsh-home、/workspace ← 宿主 bind │ └─────────────────────┘ └───────────────┘ │ └─────────────────────────────────────────────┘ ``` 组件: 1. **面板服务**(Go 容器):REST API + 静态 SPA + 反向代理子系统 + docker client + 配额/备份/升级引擎。 2. **dsh-web 镜像**(自建):Node + `@deepseek-ai/dsh` + caddy + 入口脚本。 3. **dsh-net**:内部 bridge 网络,容器不发布主机端口,仅面板可达。 4. **(可选)socket-proxy**:面板与 sock 之间的白名单代理。 --- ## 3. 宿主路径 ↔ 面板内路径映射(关键) Docker bind 的 `Source` 必须是**守护进程所在宿主路径**,不是面板容器内路径。面板容器把宿主 `/var/lib/dsh-mgr` 挂到 `/data`,于是同一文件树有两个视角: | 用途 | 面板直接读写(容器视角) | 创建 dsh 容器 bind.Source(宿主视角) | |---|---|---| | 用户工作区 | `/data/workspaces//files/...` | `/var/lib/dsh-mgr/workspaces//files` | | 实例 DSH_HOME | `/data/workspaces//instances//home/...` | `/var/lib/dsh-mgr/workspaces//instances//home` | | 备份包 | `/data/backups///...` | (不入容器,仅面板读写) | 面板配置: ``` HOST_DATA_ROOT=/var/lib/dsh-mgr # 传给 docker API 的 bind 源 CONTAINER_DATA_ROOT=/data # 面板自己 fs 操作的根 ``` - 所有"面板直接动文件"(文件 API、备份 tar、profile 读写)用 `CONTAINER_DATA_ROOT`。 - 所有"喂给 docker API 的 bind.Source / build context"用 `HOST_DATA_ROOT` 拼接。 - 封装 `toHostPath(containerPath)` 翻译函数集中调用,避免散落拼接出错——这是 sibling 容器最常见的 bug。 --- ## 4. 数据与目录布局(宿主机) ``` /var/lib/dsh-mgr/ db.sqlite # 用户、实例、配额、备份索引 workspaces// files/ # 用户工作区(面板文件 API + 各 dsh 容器共享挂载) └─ <用户自由目录结构> instances// home/ # DSH_HOME(bind → 容器 /data/dsh-home) .credentials.yaml # API Key(机密) cordis.patch.yml # home 级 patch profiles/web/ package.json # 插件依赖 + dsh.profile bundle 清单 cordis.patch.yml # profile 级 patch(= "profile 文件") node_modules/ # 插件实体(可重建,不入备份) skins/ pets/ agent-presets/ .agent-presets/ dsh-ssh.json # 0600 sessions/ worktrees/ # 运行态(不入配置备份) backups/// .tar.gz # 配置备份包 # 镜像层由本机 Docker daemon 管理,不作为此目录下的文件保存;DB 记录镜像索引 ``` **配额作用域**:`workspaces//` 整棵树(files + 所有 instances 的 home)。每用户一个配额单位。 --- ## 5. 自建 DSH 镜像设计 ``` FROM node:22-slim # 1) 装 dsh CLI(版本参数化) ARG DSH_VERSION=0.2.0-rc.2 RUN npm i -g @deepseek-ai/dsh@${DSH_VERSION} # 2) 装 caddy(容器内反代,绕过 dsh 拒绝 0.0.0.0) COPY caddy /usr/bin/caddy COPY Caddyfile /etc/caddy/Caddyfile # :8080 { reverse_proxy 127.0.0.1:3080 } COPY entrypoint.sh /entrypoint.sh ENV DSH_HOME=/data/dsh-home WORKDIR /workspace ENTRYPOINT ["/entrypoint.sh"] # CMD 由面板按实例注入:dsh web --no-open --port 3080 --public-url --trusted-host ``` - `entrypoint.sh`:`tini` 接管信号 → 起 caddy(后台)→ `exec dsh "$@"`。dsh 绑 loopback:3080,caddy 绑 0.0.0.0:8080。 - **存储方式**:生成的镜像仅保存在本机 Docker daemon,不推送 registry,也不从 registry 拉取成品 `dsh-web` 镜像。所有用户共享面板管理的本地镜像目录。 - **版本查询**:面板从 npm 查询 `@deepseek-ai/dsh` 的 `dist-tags.latest`,展示精确版本和查询时间;查询不触发构建或升级。不通过版本字符串排序猜测最新版。查询失败时保留本地版本列表,缓存结果需标明过期。 - **版本固定**:用户确认构建时锁定页面展示的精确版本,后端校验其确实存在于 npm 元数据中;构建过程中不再次解析 `latest`,Dockerfile 不安装浮动的 `@latest`。 - **本机构建**:Docker API `POST /build` 接受 tar 流;面板将内置 `/image/` 的 Dockerfile+caddy+entrypoint+Caddyfile 打包后提交给 daemon,并通过 Docker API 的 buildargs 传入 `DSH_VERSION`。用户不能提交任意 Dockerfile、构建上下文、镜像地址或其他构建参数。 - **镜像身份**:tag 使用 `dsh-web:-`,其中 `recipeRev` 标识镜像模板修订;记录精确 DSH 版本、模板修订、架构、本地 image ID 和构建时间。创建实例使用已确认的 image ID,不依赖可能被覆盖的 tag。 - **构建任务**:手动触发后异步执行,展示排队、构建中、验证中、成功、失败状态及脱敏日志;任务持久化。同版本、模板修订和架构的并发请求复用同一任务,已有可用镜像直接复用。默认全局串行构建,并限制用户触发频率、任务时长及日志大小,构建前检查磁盘空间。 - **可用性判定**:Docker build 成功后,用临时隔离容器验证版本和 DSH 启动就绪;验证通过才加入可选择列表。失败保留错误记录,不自动替换已有可用镜像。面板重启后对账未完成任务及本地镜像,不盲目重复构建。 - **本地清单**:以 Docker inspect/list 和面板管理标签对账,展示精确版本、构建时间、可用状态及使用情况。外部删除导致 image ID 不存在时标记缺失,不再允许新建;管理员清理时保护被实例、待执行任务或回滚流程引用的镜像,不自动运行全局 prune。 - **网络要求**:仅本地保存不代表离线构建;首次构建仍可能需要拉取基础镜像及下载 npm 包。网络故障不影响选择已存在的本地可用镜像。 **创建实例交互**: 1. **选择本地版本**:从本地可用列表选择版本,校验 image ID 仍存在后创建。 2. **构建最新版并创建**:查询并展示精确版本 → 用户确认 → 事务预留实例名额 → 构建或复用任务 → 验证成功 → 用该 image ID 创建实例。等待构建的实例也计入每用户 2 个名额;构建/创建失败或取消时清理未完成资源并释放名额,不回退到其他版本。取消某个用户的等待不取消其他用户共享的构建任务。 3. 面板另提供独立的“构建最新版”按钮,仅生成本地镜像,不创建或升级实例,也不占实例名额。 --- ## 6. 容器生命周期(docker.sock + socket-proxy) Go 端用 `github.com/docker/docker/client`: - **创建**: - Image = 本地可用镜像的固定 image ID(对应精确 DSH 版本及模板修订,见 §5) - Env:`DSH_HOME=/data/dsh-home`(密钥不入 env;用户在 dsh UI 里设 key,落到 `.credentials.yaml`) - Binds(Source 用 **HOST 路径**): - `workspaces//instances//home` → `/data/dsh-home` - `workspaces//files` → `/workspace` - Network = `dsh-net`(不发布端口) - Cmd = `web --no-open --port 3080 --public-url https:///dsh// --trusted-host ` - Labels:`dsh-mgr.uid`、`dsh-mgr.instId`、`dsh-mgr.version`、`dsh-mgr.role=dsh` - 资源:`--memory=2g --cpus=2`(可配);`--tmpfs /tmp`;只读 rootfs + 必要写入挂载 - **操作**:start/stop/restart/remove/logs/inspect/exec,全走 SDK。 - **2 实例上限**:按逻辑实例在 DB 事务中预留名额,等待构建/创建中的实例也占名额;失败清理后释放。Docker labels 用于对账,不作为并发名额控制。 - **对账**:面板启动时按 label 枚举容器与 DB 对账(孤儿容器回收或纳管)。 - **sock 安全**:面板挂裸 sock = 等效宿主 root。缓解: 1. **socket-proxy**(推荐):只放行白名单 API(containers/exec/images/networks/volumes/logs/build),关 plugins/daemon/swarm。面板连 proxy 而非裸 sock。 2. 面板自身非 root、只读 rootfs + tmpfs /tmp、资源限制。dsh 容器不 `--privileged`、去 cap。 --- ## 7. 网络与访问方式 - 面板**启动时 ensure** `dsh-net`(bridge)存在,并把自身加入(compose 声明或运行时 `network connect`)。 - dsh 容器挂 `dsh-net`,**不发布主机端口**。 - 面板反代 `/dsh//` → `http://.dsh-net:8080`,容器名 = instId。 - **访问方式(待定,见 §18)**:默认推荐"路径前缀反代 + token 桥接"(§8),面板天然为 ingress。备选:子域名(需泛 DNS+TLS)/ 直发端口(MVP,隔离弱)。 --- ## 8. 访问与认证(token 桥接 + cookie 改写) > 此为最复杂点。推荐方案:路径前缀反代 + token 桥接。 - 面板路由 `/dsh//*` → 反代到 `http://.dsh-net:8080`,**剥掉 `/dsh/` 前缀**,**保留浏览器 Host**,加 `X-Forwarded-*`。 - **token 桥接**:容器启动后,面板从 `docker logs` 流式抓 dsh 打印的 `http://127.0.0.1:3080/?token=xxx` 里的 token;用户首次打开 `/dsh//` 时,面板服务端用该 token 打一次容器换 cookie,再把 `Set-Cookie` 的 `Path=/` 改写为 `Path=/dsh/`、加 `Secure`/`HttpOnly`/`SameSite=Lax` 下发给浏览器。后续请求带该 cookie 透传。 - **鉴权**:面板先校验用户登录 + 该 instId 归属当前 uid,再放行反代。dsh 自身 token cookie 为第二层。 - **WebSocket/SSE**:反代需支持 WS 升级;cookie 改写后需实测 WS 握手是否正常(关键风险)。 ## 9. 工作区文件管理 + 容量配额 **文件 API**(path 校验:解析后必须在 `files/` 内,拒 `..`/绝对路径): - `GET /api/ws/tree?path=` 列目录(带 size/mtime) - `POST /api/ws/mkdir`、`POST /api/ws/rename`、`DELETE /api/ws/delete` - `POST /api/ws/upload`(分片/流式,**写前查配额**) - `GET /api/ws/download?path=`(单文件流;目录自动 zip) - `GET /api/ws/usage`(当前用量/配额) **配额实现**(容器化面板的约束): - **默认:软配额**(无特权,随处可跑)。面板写前 `du` 校验 + 周期巡检 + 写入并发限流。并发大文件可能短暂超限。 - **可选硬配额**(二选一,需运维确认): - **XFS prjquota**:面板 `--privileged` + `cap_add:[SYS_ADMIN,CHOWN]` + 把宿主 XFS 挂载点 bind 进来,面板内对 `/data` 跑 `xfs_quota`/`chattr -p`。每用户唯一 project ID(uid+1000)。 - **ZFS refquota**(若宿主用 ZFS):面板 `--privileged` + zfsutils,每用户 `zfs create pool/users/ && zfs set refquota=10G`,再 bind 该 dataset 到 `/data/workspaces/`。 - 或在宿主跑**极小特权配额 sidecar**(gRPC/HTTP),面板远程调它做 quota 操作——面板自身保持非特权。 - 配额值存 DB,按用户可配(默认 10GiB)。面板展示用量。 > 具体选型见 §18 待拍板。 --- ## 10. 配置备份与还原(含模型与插件) **备份内容**(tar.gz,**排除可重建/运行态**): | 包含 | 说明 | |---|---| | `.credentials.yaml` | 模型 API Key(机密)→ 备份包加密 | | `cordis.patch.yml`(home 级) | home patch | | `profiles/web/package.json` | **插件清单**(含 dsh.profile bundle 列表) | | `profiles/web/cordis.patch.yml` | **模型/provider 配置** + apiKeyEnv 引用 | | `profiles/web/pnpm-lock.yaml`(若有) | 插件版本锁 | | `skins/ pets/ agent-presets/ .agent-presets/` | 用户资源 | | `dsh-ssh.json` | SSH 凭据(0600,加密入包) | | `manifest.json` | dsh 版本、备份 schema 版、时间戳 | **排除**:`node_modules/`(pnpm 重建)、`worktrees/`、`sessions/`、日志。 **备份包加密**:用用户密码派生密钥(Argon2id → AES-GCM)加密整包;密钥不落库。下载/上传均加密包。 **备份流程**:停容器(或仅停写)→ tar 指定文件 → 加密 → 落 `backups///` + 索引入库 → 重启容器。支持立即下载。 **还原流程**:校验+解密包 → 停容器 → 备份当前 home 为 `.pre-restore.bak` → 覆盖配置文件(不动 node_modules)→ **按 `package.json` 重建插件**(`dsh plugin --profile web install` 或 entrypoint 首启时 pnpm reconcile)→ 启容器 → 健康检查 → 失败则回滚 `.pre-restore.bak`。 --- ## 11. profile 文件编辑 - 编辑对象:`profiles/web/cordis.patch.yml`(YAML)。 - 前端用 Monaco 编辑器;后端 `GET` 返回原文、`POST` 校验(YAML 合法 + 可选 `--dump-config-schema` 比对)后原子写(临时文件 + rename,`dsh-atomic-write` 风格)。 - 保存后**重启容器**使生效(dsh 有 `dsh-hmr`,但配置类变更稳妥起见重启;后续可探热更)。 - 只读展示 `dsh.profile`(bundle 清单)与 `--dump-config` 预览(面板 `docker exec` 跑 `dsh --profile web --dump-config` 取回渲染)。 - 专用配置预览保持脱敏;完整 home 文件管理允许实例所有者读取 `.credentials.yaml` 原文,不承诺 write-only。 --- ## 12. 升级流程(保留数据 + 重启新版容器) ``` 1. 用户选择本地可用目标版本;若需最新版,先手动触发 §5 构建并等待验证成功。锁定本地 image ID 后才进入备份/停机流程,不自动 pull 成品镜像 2. 自动备份当前配置(§10)→ 标记 pre-upgrade 备份 3. docker stop (不 remove,保留作回滚) 4. docker create :同名 instId、同 binds(home + files)、同 labels、新 image、 Cmd 按新版本要求重算 --public-url/--trusted-host 5. entrypoint 首启:若 profiles/web/node_modules 缺失/与 package.json 不一致 → pnpm install 重建插件 6. docker start → 面板抓 token、健康检查(轮询 caddy :8080) 7. 成功:docker rm ;失败:stop 、start 、标记回滚、提示用户 ``` 关键:**bind 不变 ⇒ 数据零丢失**;`node_modules` 在新镜像里按 `package.json` 重建(跨版本兼容性由 pnpm/dsh-plugin-manager 保证)。 --- ## 13. 安全与资源 - **docker.sock**:面板容器能访问 sock 即等效宿主 root;socket-proxy 白名单 + 面板非 root + 只读 rootfs 缓解。 - dsh 容器:**不 `--privileged`**、去 CAP_NET_RAW 等、只读 rootfs、tmpfs /tmp、mem/cpu 限制、不挂 sock。 - 面板账号:bcrypt + JWT(短期 access + refresh),失败锁定。 - 配置备份包加密;实例所有者通过文件 API 下载的凭据或目录 ZIP 不属于加密配置备份,应按机密数据保管。 - 路径穿越校验、上传大小/类型白名单(可配)。 - 审计日志:容器操作/备份/还原/升级/profile 改动入库。 --- ## 14. 技术栈与项目结构 - **后端**:Go 1.22+,`net/http`(或 chi/gin),`github.com/docker/docker/client`,`github.com/docker/go-connections`,sqlite(`modernc.org/sqlite` 纯 Go 驱动,免 CGO),Argon2id、AES-GCM。 - **前端**:Vite + React + TypeScript,Monaco(profile 编辑),shadcn/ui。 ``` dsh-mgr/ cmd/server/ # Go 入口 internal/ auth/ # 账号/JWT docker/ # 容器编排(镜像、生命周期、对账、路径翻译) workspace/ # 文件 API、配额(软 du + 可选 xfs/zfs) proxy/ # /dsh/ 反代 + token 桥接 + cookie 改写 backup/ # 备份/还原/加密 upgrade/ # 升级编排 profile/ # cordis.patch.yml 编辑 store/ # sqlite schema/查询 api/ # HTTP handlers web/ # SPA image/ # 自建 dsh-web 镜像(Dockerfile + Caddyfile + entrypoint.sh + caddy 二进制) deploy/ # 面板自身 docker-compose / socket-proxy / systemd ``` --- ## 15. 数据模型(sqlite 概要) - `users(id, username, pass_hash, quota_bytes, created_at)` - `instances(id, uid, name, dsh_version, image_id, build_id, status, container_id, created_at, ...)` - `backups(id, uid, inst_id, path, size, schema_ver, created_at, kind=auto|manual|pre-upgrade|pre-restore)` - `images(image_id, version, recipe_rev, platform, tag, status, built_at)`(本地镜像索引,status=validating|available|failed|missing;与 Docker 实际状态对账) - `image_builds(id, requested_by, version, recipe_rev, platform, status, image_id, error, created_at, finished_at)`(持久化构建任务;同版本+模板修订+架构的活动任务去重) - `audit(id, uid, action, target, detail, at)` --- ## 16. 面板自身部署(compose 示例) ```yaml services: dsh-mgr: image: dsh-mgr:latest restart: unless-stopped ports: - "80:80" # 前置 TLS 反代或直接 443 user: "65532:65532" # 非 root environment: HOST_DATA_ROOT: /var/lib/dsh-mgr CONTAINER_DATA_ROOT: /data PANEL_PUBLIC_URL: https://dsh.example.com DSH_NET_NAME: dsh-net DEFAULT_QUOTA_BYTES: 10737418240 DOCKER_HOST: unix:///var/run/docker.sock # 或指向 socket-proxy volumes: - /var/lib/dsh-mgr:/data - /var/run/docker.sock:/var/run/docker.sock:ro # 或 socket-proxy networks: [dsh-net] read_only: true tmpfs: [/tmp] deploy: { resources: { limits: { memory: 1g } } } # 硬配额(XFS)可选: privileged: true; cap_add: [SYS_ADMIN,CHOWN]; volumes: [xfs mount] # 可选 socket-proxy socket-proxy: image: tecnativa/docker-socket-proxy environment: CONTAINERS: 1; EXEC: 1; IMAGES: 1; NETWORKS: 1; VOLUMES: 1; LOGS: 1; BUILD: 1 POST: 1 volumes: [/var/run/docker.sock:/var/run/docker.sock:ro] networks: [dsh-net] restart: unless-stopped networks: dsh-net: { driver: bridge } ``` --- ## 17. 关键风险与待验证 1. **token/cookie 桥接**(§8)最脆——需实测 dsh token 换 cookie 在反代+前缀下的行为,确认 cookie 改写后 **WS/SSE 正常**(dsh web 可能有 WebSocket)。 2. **插件首启重建**:跨 dsh 版本 `pnpm install` 在 entrypoint 无网时失败——可能需升级时由面板 `docker exec` 联网重建后再切流量。 3. **XFS/ZFS 硬配额**依赖宿主 FS 与特权/agent;否则退化为软配额。 4. **dsh agent 工作目录**:`dsh-fs-local`/`dsh-fs-sandbox` 的根是否等于 cwd——需确认 `/workspace` 作为 cwd 即可让 agent 在工作区操作,或需额外配置 fs root。 5. dsh 版本仍为 `0.2.0-rc.*`(RC),API/flag 可能变动——需维护版本兼容矩阵。 6. **sibling bind 路径翻译**:`toHostPath` 出错会导致 bind 挂错目录或挂空目录,需单测覆盖。 7. **本地镜像构建**:验证 npm 查询失败仍可选本地版本、并发构建去重、失败释放实例名额、面板重启对账、镜像被外部删除,以及构建磁盘不足等路径;构建最新版不保证兼容,必须通过启动验证。 --- ## 18. 待拍板决策点(open) **已确认:镜像仅保存在本机;面板支持查询并手动构建最新版;创建实例可选本地已有版本,或构建最新版后创建。不需要 registry/CI 镜像分发。** > 以下访问方式和配额选型仍待确认;此前审查发现的隔离、文件安全和升级回滚问题仍需另行修订。 1. **访问方式**:路径前缀反代 + token 桥接(推荐,面板天然 ingress)/子域名(需泛 DNS+TLS)/直发端口(MVP,隔离弱)。 - **推荐默认**:路径前缀反代。 2. **配额**:宿主 FS 是 XFS / ZFS / 其他?是否接受"特权面板"或"宿主配额 sidecar"做硬配额?还是软配额默认即可? - **推荐默认**:软配额(du)起步,硬配额作为后续增强;若宿主为 XFS/ZFS 且可接受特权,则上硬配额。 --- ## 19. 里程碑 1. **M1 镜像 + 单容器手动跑通**:自建本地 `dsh-web` 镜像,验证精确版本、caddy+dsh 启动与就绪。 2. **M2 面板骨架**:账号/鉴权 + npm 最新版查询 + 本地镜像列表/手动构建任务 + 创建时选本地版本或构建最新版 + docker 编排(创建/启停/删除/2 上限)+ 对账 + 路径翻译。 3. **M3 反代 + token 桥接**:`/dsh//` 可用(含 WS)。 4. **M4 工作区文件 API + 软配额**。 5. **M5 备份/还原 + profile 编辑**。 6. **M6 升级流程 + 回滚**。 7. **M7 SPA 完善 + 审计 + 资源限制 + 可选硬配额**。 --- ## 附:调研依据(关键 URL) - 仓库:https://github.com/deepseek-ai/deepseek-harness - README:https://raw.githubusercontent.com/deepseek-ai/deepseek-harness/master/README.md - CLI README:https://raw.githubusercontent.com/deepseek-ai/deepseek-harness/master/apps/cli/README.md - web-app README:https://raw.githubusercontent.com/deepseek-ai/deepseek-harness/master/packages/bundle/web-app/README.md - providers 指南:https://raw.githubusercontent.com/deepseek-ai/deepseek-harness/master/docs/user/guide/providers.md - 公网部署指南:https://raw.githubusercontent.com/deepseek-ai/deepseek-harness/master/docs/user/guide/public-deployments.md - npm 包:https://registry.npmjs.org/@deepseek-ai/dsh/latest