Files
dsh_shenxian/README.md
T
admin cf8b7b1f5c chore(k8s): 下线 K8s 后端形态,移除依赖 @kubernetes/client-node
生产形态是单机 local(DEFAULT_DEPLOY_MODE=local,env 未覆盖)⇒ K8s 分支
在 local 下本来不可达;且该形态与官方 dsh 基座、插件体系均无关
=> 整体下线,并移除该形态唯一的第三方依赖。

移除(备份在 D:/github/_dsh_shenxian_K8s后端备份_20260915/,含还原命令与
「集群化方案要复用的模板清单」):
  src/supervisor/{k8s-spawner,leader,reconcile}.ts
  src/fs/k8s-user-fs.ts · src/tcp-bridge.ts · src/web/file-service.ts
  test/{k8s-spawner,leader}.test.mjs · scripts/smoke-file-service.mjs

改写调用方:cli.ts(5 条 import / 选主块 / file-service 与 tcp-bridge 两个
子命令 / dispatch / HELP)、web/server.ts(改为 fail-loud 守卫 + 恒用
LocalSpawner)、fs/provider.ts(只留 LocalUserFs)、package.json(测试与
smoke 入口),外加 3 处指向已删类型的悬空 JSDoc。

保留(集群化方案列为未来可选):deploy/ · poc/01-04 · Dockerfile.dsh ·
docs/k8s*.md(已加「代码已下线」状态横幅)· config.ts 的 K8s 配置字段与
DeployMode 联合类型。

验证:tsc --noEmit exit 0;npm test 36 测试 / 35 通过 / 0 失败 / 1 跳过;
npm run verify exit 0;依赖与被删符号全仓 0 命中。
2026-09-15 06:36:20 +08:00

160 lines
12 KiB
Markdown
Raw 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.
# dshs
> 📌 **本部署的两处文档别混(档案 19 §C10)**
> - `docs/`(本仓库内)= **仓库自带 7 篇**(blueprint / deployment / k8s-deploy …)——**不要往这里写我们的改造记录**;
> - **本项目的改造文档库**在另一仓库:`[email protected]:maogeigei/dsh_shenxian_doc.git`(服务器镜像 `/opt/dsh/docs`,root 600),
> 含档案 01–26、`DEPLOY-本部署.md`(构建/部署/回滚)、`ops/`(nginx 与切换脚本)、`scripts/`(运维工具)。
> 运维排障先看 `DEPLOY-本部署.md` 与档案 02 附录 C。
**面向公网的多租户 DSH 托管平台** —— 部署到一台公网服务器后,多个用户注册并经管理员审核,各自获得一套**相互隔离**的 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)环境,随时通过域名安全访问(已适配手机端)。
> 以 [DSH 插件市场](https://github.com/bradeGithub/DSH-Plugins-Marketplace) 的 cordis-plugin 形态分发,遵守 [STANDARD.md](STANDARD.md)。
## 它解决什么
DSH 本身是单用户本地工具:没有认证、没有多租户隔离、Web 远程访问缺认证层。`dshs` 在其之上补一层**服务端登录 + 多租户编排**:
```
用户浏览器
│ HTTPS
▼
nginx(TLS 终结,主域 + *.子域 通配)
▼
编排服务 dshs(Fastify,单进程)
├─ 认证 / 审核 / 管理台 / 网页桌面 / 域名 API
└─ 按 Host 或 /u/<userId>/dsh/* 路由
└─ 反向代理 → 各用户的 DSH 子进程(只绑 127.0.0.1 动态端口,不出公网)
```
流程:管理员审核注册用户 → 每个用户落到自己的文件桌面 → 按文件夹启动 DSH → 通过域名访问,彼此文件隔离。
## 核心能力
| 能力 | 说明 |
|---|---|
| **登录与审核** | `bootstrap-admin` 创建首个管理员;注册后需审核通过才能登录;禁用用户会同时删除其会话并停止运行中的 DSH |
| **每用户隔离 DSH** | 主 DSH 常驻对外服务;崩溃时**按需拉起一次守护 DSH** 修复并自动重启主实例 |
| **网页桌面** | 文件浏览 / 新建 / 上传;按文件夹启动 DSH;所有路径经「词法包含 + 符号链接分量」双重围栏校验 |
| **每文件夹插件** | 自动检测该用户 profile 已安装的插件,按文件夹勾选启用,持久化并注入 cordis patch |
| **多形态访问** | 默认子路径 `/u/<userId>/dsh/`;每用户子域名 `<用户名>.<baseDomain>`(HTTP + WebSocket);自定义域名 + nginx `server {}` 生成接口 |
| **凭据隔离** | 每用户命名 API 密钥库,AES-256-GCM 加密落库(references-not-secrets);换 key 自动重建实例;spawn 只注入当前启用的 key |
## 部署形态(二选一)
同一套代码,靠 `DSHS_DEPLOY_MODE` 切换:
| | 模式 A:直接部署(单服务器) | 模式 B:K8s + 容器化 |
|---|---|---|
| 形态 | 单机裸机,`child_process` + setuid/iptables | 多机 ACK,每用户独立 Pod |
| 数据 | SQLite(本机文件) | PostgreSQL(CloudNativePG) |
| 隔离 | 软隔离 / OS 账号硬隔离 | Pod 网络 + SecurityContext + NetworkPolicy |
| 弹性/HA | 无(单点) | 控制面 3 副本 + leader election,DSH Pod 自动重建 |
| 交付 | `git clone` + 脚本 | `kubectl apply -f deploy/` |
模式 B(K8s)**已于 2026-09-15 下线**:K8s 后端代码从本仓移除(备份在 `D:\github\_dsh_shenxian_K8s后端备份_20260915\`),`deploy/` 清单与设计文档**保留**作为未来可选路线;当前生产形态为**模式 A 单机部署**。历史实现(每用户 DSH Pod + tcp-bridge sidecar + file sidecar(8082)+ Headless Service + NetworkPolicy + 控制面 3 副本 + Lease 选主 + reconcile)见 [K8s 部署教程](docs/k8s-deployment.md) 与 [踩坑记录](docs/k8s-deploy.md)。
## 快速开始(模式 A)
前置:Linux 服务器、Node **^22.19 或 ≥24**、已安装 DSH CLI(`npm i -g @deepseek-ai/dsh`)。完整的生产流程(systemd / DNS / 通配证书 / nginx)见[部署教程](docs/deployment.md)。
```sh
git clone https://work.alotbuy.com/maogeigei/dsh_shenxian.git
cd dshs
npm install && npm run build # tsc → lib/
# 1) 创建首个管理员(用它登录管理台)
node lib/cli.js bootstrap-admin --username admin --password '<强密码>' --db ./dev.local.db
# 2) 启动编排服务
node lib/cli.js --port 3080 --db ./dev.local.db
```
打开 `http://127.0.0.1:3080/`:
1. 用 admin 登录进入管理台;
2. 另开浏览器注册一个普通用户 → 回管理台点「通过」;
3. 该用户重新登录进入桌面 → 上传文件 / 建文件夹 → 点「在此文件夹启动 DSH」;
4. 首次使用在桌面的「管理密钥」里填入自己的 DeepSeek API Key(加密存储;未设置时 DSH 能启动但无法调用模型)。
> ⚠️ 本地试跑可以验证登录 / 审核 / 桌面 / 启动全链路;但要**打开 DSH 聊天界面**,需要配置域名与每用户子域名(`BASE_DOMAIN` + `COOKIE_DOMAIN` + nginx 通配,见[部署教程](docs/deployment.md)第 6–9 步)——DSH 的 SPA 使用绝对路径,纯子路径方式加载不了静态资源。
## 配置
环境变量均可省略;同名 CLI flag(`--port` / `--db` / `--isolation-mode` 等)优先级更高,全部见 `src/config.ts` 与 `node lib/cli.js --help`。
| 环境变量 | 默认 | 说明 |
|---|---|---|
| `DSHS_PORT` | `3080` | 编排服务绑定端口(nginx 上游) |
| `DSHS_DATA_ROOT` | `~/.dshs` | 每用户 home/workspace 根(生产 `/var/lib/dshs`) |
| `DSHS_DSH_BIN` | `dsh` | 子 DSH 可执行文件(建议绝对路径,systemd 下 PATH 精简) |
| `DSHS_ISOLATION_MODE` | `soft` | `soft` 软隔离 / `account` 账号级硬隔离(Linux,需 root) |
| `DSHS_BASE_UID` | `100000` | 账号级隔离的 uid 基数 |
| `DSHS_PORT_GUARD` | `false` | iptables owner-match 端口守卫(Linux+root):阻止同机其他账号直连各用户 DSH 的回环端口;不支持的环境下开启会拒绝启动(fail loud) |
| `DSHS_SECURE_COOKIES` | `false` | HTTPS 部署设为 `true`(cookie 加 `Secure`,SameSite=None 以跨子域共享) |
| `DSHS_BASE_DOMAIN` | 空 | 每用户子域名的基域(如 `dsh.example.com`);空 = 仅子路径访问 |
| `DSHS_COOKIE_DOMAIN` | 空 | 会话 cookie 的 `Domain`(如 `.dsh.example.com`,注意前导点);空 = host-only |
| `DSHS_SESSION_TTL` | `604800` | 会话有效期(秒,默认 7 天) |
| `DSHS_MAX_UPLOAD` | `25MB` | 上传请求体上限(base64 JSON) |
| `DSHS_PACKAGE_DIR` | (自动探测) | **平台内置 dsh 的包根**。默认**按序探测**:`DSHS_DSH_BIN` 解软链反推 → `/usr/local/lib/node_modules`、`/usr/lib/node_modules` → `npm root -g`。⚠️ `npm i -g` 的落点随发行版变(发行版包管理器装的 Node 常落 `/usr/lib/node_modules`),**写死会让「模型设置」的厂家目录与「插件兼容性预检」静默失效**(无报错、只剩降级行为)⇒ 探测失败时才需要显式设它。 |
| `DSHS_COMPAT_ROOT` | (= `DSHS_PACKAGE_DIR`) | 同上的旧名(兼容);同时接受 `DSH_PACKAGE_DIR` / `DSH_COMPAT_ROOT`(导出侧命名) |
| `DSHS_PI_AI_DATA_DIR` | (自动探测) | `@earendil-works/pi-ai` 的**厂家目录**数据位置(`<包根>/node_modules/@earendil-works/pi-ai/dist/providers/data`);一般无需设置 |
| `DSHS_RESTART_BACKOFF` | `1000` | 子 DSH 崩溃后的自动重启延迟(毫秒) |
| `DSHS_ENABLE_PATCH` | `false` | 是否向子 DSH 注入 `--patch`(运行时插件 + 每文件夹插件;旧版 dsh 不支持时可关) |
| `DSHS_SECRET` | 自动生成 | 每用户密钥库的加密主密钥;未设置时生成并持久化到 `<dataRoot>/secret.key`(0600) |
| `DSHS_DEPLOY_MODE` | `local` | `local` 单机 / `k8s` 每用户 Pod(模式 B) |
| `DSHS_DB_URL` | 空 | Postgres DSN;设置即启用 Postgres(k8s 必填) |
| `DSHS_NAMESPACE` | `dsh` | (k8s)每用户资源所在命名空间 |
| `DSHS_DSH_IMAGE` | 空 | (k8s 必填)每用户 DSH Pod 镜像 |
| `DSHS_CONTROL_PLANE_IMAGE` | 空 | (k8s 必填)file sidecar / tcp-bridge / init 容器镜像 |
| `DSHS_IMAGE_PULL_SECRET` | `dsh-acr-pull` | (k8s)生成 Pod 的拉镜像 secret |
| `DSHS_EGRESS_CIDRS` | 空 | (k8s)每用户 Pod 443 出站白名单 CIDR;空 = 全网(始终排除私网段与 169.254.169.254) |
| `DSHS_K8S_SERVICE_ACCOUNT` | `dsh-orchestrator` | (k8s)控制面 ServiceAccount |
## 文档
| 文档 | 内容 | 什么时候读 |
|---|---|---|
| [部署教程(模式 A)](docs/deployment.md) | 从零到公网可用:Node / DSH / systemd / DNS / HTTPS / nginx | 第一次部署单机版,照着做即可 |
| [硬隔离教程](docs/hard-isolation.md) | 每用户独立 OS 账号(`setpriv` 降权),生产环境建议开启 | 模式 A 跑通后的进阶加固 |
| [域名配置示例](docs/domain-config.md) | 主域 + 每用户子域 + 自定义域名的 nginx 配置 | 配置域名 / 排查路由问题 |
| [常见问题排查](docs/troubleshooting.md) | 502 / 404 / 401 / 403 / SSL / 端口冲突,按现象索引 | 出错了先来这里对现象 |
| [K8s 部署教程(模式 B)](docs/k8s-deployment.md) | ACK + CNFS + CNPG 分步部署(多机 HA) | 要部署容器化多机形态 |
| [K8s 踩坑记录](docs/k8s-deploy.md) | 模式 B 实机部署的坑与根因(镜像源 / 存储 / 网络 / 备案) | 模式 B 部署卡住时查 |
| [技术蓝图](docs/blueprint.md) | 权威技术设计:拓扑 / 双 DSH / 数据模型 / API / 安全模型 | 想了解原理或参与开发 |
| [PoC 实验记录](poc/README.md) | k8s 关键假设的四项实测(RWX / socat / NetworkPolicy / CNPG) | 评估模式 B 可行性时 |
> 学习路线:**模式 A** 先读「部署教程」跑通 → 再看「硬隔离」加固,出问题查「常见问题排查」;**模式 B** 直接从「K8s 部署教程」开始,卡住查「K8s 踩坑记录」。
## 开发
```sh
npm install # pnpm/npm 均可;Node ^22.19 || >=24
npm run typecheck # tsc --noEmit
npm test # 构建 + node:test 单测(DB 双后端 / k8s spawner / leader / fs 围栏)
npm run smoke # 及 smoke:* 系列:对运行中的服务做端到端冒烟(scripts/smoke*.mjs)
```
## 安全
- **会话**:不透明随机 token,仅 SHA-256 哈希落库;cookie `HttpOnly` + `SameSite`,HTTPS 下加 `Secure`。
- **密码**:scrypt 加盐哈希,常数时间比较。
- **密钥**:每用户 API key 以 AES-256-GCM 加密落库,主密钥来自 env 或 `<dataRoot>/secret.key`(0600)。
- **路径**:所有文件操作先做词法包含校验,再逐段拒绝符号链接分量,防止越出用户根目录。
- **隔离分层**:软隔离(默认)→ 账号级硬隔离(OS 账号 + `setpriv` 降权,`0700` 真正生效)→ 端口守卫(iptables owner-match)→ k8s Pod 边界(non-root / drop ALL / 只读 rootfs / NetworkPolicy)。
- **审计**:注册 / 登录 / 审核 / 改密钥等动作写入 `audit_log`。
细节与威胁模型见[技术蓝图 §7](docs/blueprint.md)、[硬隔离教程](docs/hard-isolation.md)。
## 问题反馈与支持
这是我开发的第一个正式项目,功能可能还不够完善,非常欢迎大家提出意见和反馈!
如果你在使用过程中遇到问题,或者有改进建议,欢迎提交 [Issue](../../issues):
- 🐛 **遇到 Bug**:请描述你的复现步骤、报错信息、以及运行环境(系统/DSH 版本等)
- 💡 **功能建议**:请说明你的使用场景和你期望的效果
- 📖 **文档疑问**:指出 README 中看不懂或描述不清的地方
我会**尽量在 1~2 天内回复**,确认有效的问题会尽快修复并发布更新。
感谢你的支持!🌟