# DeepSeek Harness 部署方案

已确认项目身份与全部部署参数（官方仓库、npm 版本、Node 要求、启动参数、凭据解析）。以下是 Ubuntu 24.04 的部署方案：

---

## 项目与最新版本

- **DeepSeek Harness（`dsh`）**：DeepSeek 官方开源 agent 框架，采用“一切皆插件”架构（基于 Cordis）。仓库：[deepseek-ai/deepseek-harness](https://github.com/deepseek-ai/deepseek-harness)，文档：[官方文档站](https://deepseek-harness.github.io/deepseek-harness/)。
- **最新版本**：npm `latest` = `0.1.5-rc.1`（2026-09-10 发布）；`next` 通道 = `0.1.5-rc.2`（最新）。处于开发者预览阶段，**会有破坏兼容性的变更**，部署时建议钉住版本号。
- **运行要求**：Node.js `^22.19.0 || >=24.0.0`（package.json `engines`），Ubuntu 24.04 上推荐 **Node 24 LTS**。
- **默认端口**：`http://127.0.0.1:3080`。

## 推荐部署形态

- **npm 全局安装 + systemd 守护**：`dsh web` 本身只监听本机，适合再套一层 nginx 反代 + HTTPS。
- **不建议用 Docker**：官方维护者指出容器路线有已知坑（pnpm 符号链接、`node-pty` 等原生模块与 musl/glibc、profile patch 被卷挂载覆盖），npm 直装最稳。
- 生产建议：`127.0.0.1` 回环绑定（webserver 无内置 TLS/鉴权），远程访问走 SSH 隧道或 nginx+HTTPS。

## 部署步骤

**1. 前置准备（Ubuntu 24.04）**

```
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl ca-certificates git ufw
# 全程用非 root 的 sudo 用户执行
```

**2. 安装 Node.js 24 LTS（NodeSource）**

```
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs
node -v   # 必须 >= v22.19，推荐 v24.x
npm -v
```

**3. 创建运行用户与工作区目录**

```
sudo useradd --system --create-home --shell /usr/sbin/nologin dsh
sudo mkdir -p /srv/dsh/workspace
sudo chown -R dsh:dsh /srv/dsh
# 启动时所在目录 = 默认 workspace 根目录（Web UI 里还需手动“选择工作区”）
```

**4. 全局安装 DeepSeek Harness（钉住版本）**

```
sudo npm install -g @deepseek-ai/dsh@0.1.5-rc.1
dsh --version        # 预期 0.1.5-rc.1
dsh --help
```

> 想用最新 `rc.2` 就装 `@deepseek-ai/dsh@0.1.5-rc.2`。CLI 参数：`dsh --profile web`（`web` 是别名），web 应用参数需放在其后：`--host`（仅 `127.0.0.1`/`0.0.0.0`）、`--port`、可重复的 `--trusted-host`、`--no-open`。

**5. 配置 DeepSeek API Key（无交互方式）**

凭据解析顺序：进程环境变量 → `$DSH_HOME/.credentials.yaml` → 调用目录 `.env` → `$DSH_HOME/.env`（$DSH\_HOME 默认 `~/.dsh`）。Web 搜索使用 `DEEPSEEK_API_KEY`。推荐直接在 systemd 里注入环境变量（第 6 步会用）。

手动方式也可以：启动后浏览器打开 `http://127.0.0.1:3080` → **设置 → 模型** → DeepSeek 卡片填 Key（密钥只写，存于 `$DSH_HOME/.credentials.yaml`）。模型变更立即生效，无需重启。

**6. systemd 服务**

```
# /etc/systemd/system/dsh.service
[Unit]
Description=DeepSeek Harness Web UI
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=dsh
Group=dsh
WorkingDirectory=/srv/dsh/workspace
Environment=DSH_HOME=/home/dsh/.dsh
Environment=DEEPSEEK_API_KEY=sk-你的密钥
ExecStart=/usr/bin/dsh web --no-open --host 127.0.0.1 --port 3080
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
```

```
sudo systemctl daemon-reload
sudo systemctl enable --now dsh
journalctl -u dsh -f          # 观察 "dsh web:" URL 行（内含进程 token，属敏感信息）
curl -sI http://127.0.0.1:3080/ | head -1   # 预期 HTTP/1.1 200
```

> 若 `dsh` 不在 `/usr/bin`，用 `which dsh` 修正 `ExecStart`。

**7.（可选）nginx 反代 + HTTPS（远程访问必做）**

```
# /etc/nginx/sites-available/dsh
server {
    listen 80;
    server_name dsh.example.com;
    location / {
        proxy_pass http://127.0.0.1:3080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;      # WebSocket 必需
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

```
sudo ln -s /etc/nginx/sites-available/dsh /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d dsh.example.com
```

浏览器同源策略要求：反代后需把域名加入可信主机，否则 WebSocket 连不上 → 在服务参数追加 `--trusted-host dsh.example.com`（重启生效）。需要登录密码可选装社区插件 `dsh-webui-auth`；自己只想内网用的也可以不加反代，直接 `ssh -L 3080:127.0.0.1:3080 user@server` 隧道访问。

**8. 防火墙与验收**

```
sudo ufw allow OpenSSH && sudo ufw allow 80/tcp && sudo ufw allow 443/tcp && sudo ufw enable
```

验收：浏览器打开 `https://dsh.example.com` → 设置→模型配 Key → 选择工作区 → 发一条任务（例如“Summarize this repository”）→ 确认能读文件、跑命令、委派子任务。

## 运维与注意事项

- **升级**：开发者预览迭代快，升级前备份整个 $DSH\_HOME（`settings.yaml`、`.credentials.yaml`、`profiles/`、会话库），再 `sudo npm install -g @deepseek-ai/dsh@<新版本>`；先看 npm 发布说明和 [SAFETY.md](https://github.com/deepseek-ai/deepseek-harness/blob/master/SAFETY.md)。
- **安全**：保持回环绑定；启动 URL 里的进程 token 是 API/WebSocket 的认证凭据，注意别把完整 URL 打进日志/截图；密钥只写入 `$DSH_HOME/.credentials.yaml`，建议 `chmod 600`；`0.0.0.0` 直绑无 TLS 无鉴权，谨慎。
- **扩展**：一次性任务用 `dsh --profile headless "任务文本"`；插件管理 `dsh plugin --profile web add <包>`（需 pnpm，`sudo corepack enable && corepack prepare pnpm@latest --activate`）；国内网络需代理时见文档 [network-proxy](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/guide/network-proxy.zh.md)。

现状确认：服务器没有 pnpm（需要用 corepack/npm 启用），`dsh plugin` 必须带 `--profile`。给你一套可直接照抄的完整示例——以官方插件 **Codex 子代理（`@deepseek-ai/dsh-subagent-codex`）** 为例：

## 第一步：装 pnpm（dsh 插件管理依赖它）

```
sudo npm install -g pnpm@11.7.0     # 项目本身用的 pnpm 11.7.0
pnpm --version                       # 验证
```

## 第二步：安装插件到 web 配置

```
sudo -u dsh /usr/bin/dsh plugin --profile web add @deepseek-ai/dsh-subagent-codex
```

要点：

- 必须用 `sudo -u dsh` 跑（profile 在 `/home/dsh/.dsh/profiles/web` 下，以 dsh 用户运行才能读到正确的 `DSH_HOME`）
- `--profile web` 指定装到哪个配置（还有 `headless`、`sdk`、`acp` 或自定义 profile）
- 该命令等价于在该 profile 目录里执行 `pnpm add`，装完会自动把包写进 `dsh.profile.bundles`（因为包的 `package.json` 声明了 `"dsh": {"bundle": {"patch": "./cordis.patch.yml"}}`）

## 第三步：重启服务让新 bundle 挂载

```
sudo systemctl restart dsh
dsh-url        # 重启后 token 变了，取新访问地址
```

## 第四步：验证

```
sudo -u dsh /usr/bin/dsh plugin --profile web list        # 已安装列表
sudo -u dsh env DSH_HOME=/home/dsh/.dsh /usr/bin/dsh --profile web --dump-config | grep -i subagent
```

Web UI 里新建会话后，Agent 面板会出现 Codex 子代理提供方（实际可用还要求服务器上装了 Codex CLI 并完成登录）。

## 其他“装插件”的形态（顺带说明）

- **移除**：`sudo -u dsh /usr/bin/dsh plugin --profile web remove @deepseek-ai/dsh-subagent-codex`，然后重启
- **本地/社区插件**：`... plugin --profile web add <npm包名>`，或本地目录 `... add /path/to/plugin`（GitHub 上带 `dsh-plugin` topic 的都是生态插件）
- **可选覆盖层（非 npm 包）**：官方 `config/examples/` 里的 github-review、schedule、mcp-memory 是 cordis patch 文件，用 `dsh --profile web --patch ./xx.yml` 或写入 `cordis.patch.yml`，不需要 pnpm

需要我直接在服务器上把 pnpm 和这个 Codex 子代理插件装上吗？装完我会重启并给你新的访问地址。

核实完毕，关键事实已确认：**会话库在 `$DSH_HOME/sessions/<工作区>/` 下，一个进程只有一个 token、一份会话库、一份设置**——这就是"所有人会话互相可见"的根本原因（当前版本 Web 版是单用户设计，没有内置账号体系）。

## 隔离方案：每人一个独立实例（推荐）

核心思路：**每用户 = 独立 DSH\_HOME + 独立端口 + 独立 token + 独立工作区**，互不共享任何状态。以给 `alice` 开一个为例：

**1. 建独立系统用户与工作区（文件系统这一层也隔离）**

```
sudo useradd -m -s /usr/sbin/nologin alice
sudo mkdir -p /srv/dsh-alice/workspace
sudo chown -R alice:alice /srv/dsh-alice
```

**2. 新建 systemd 实例（独立 DSH\_HOME、独立端口 3081、独立密钥）**

```
# /etc/systemd/system/dsh-alice.service
[Unit]
Description=DeepSeek Harness Web UI (alice)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=alice
Group=alice
WorkingDirectory=/srv/dsh-alice/workspace
Environment=DSH_HOME=/home/alice/.dsh
Environment=DEEPSEEK_API_KEY=sk-alice自己的密钥
ExecStart=/usr/bin/dsh --profile web --no-open --host 127.0.0.1 --port 3081
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
```

```
sudo systemctl daemon-reload && sudo systemctl enable --now dsh-alice
```

**3. nginx 给它单独开一个端口（dsh 不让直接绑 0.0.0.0）**

```
# /etc/nginx/sites-available/dsh-alice
server {
    listen 8081;                 # alice 专属入口
    location / {
        proxy_pass http://127.0.0.1:3081;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
    }
}
```

启用后 `sudo nginx -t && sudo systemctl reload nginx`。

**4. 给 alice 发登录地址（每个实例 token 独立）**

```
sudo -u alice env DSH_HOME=/home/alice/.dsh /usr/bin/dsh-url   # 或从 journalctl 取
```

Alice 打开 `http://192.168.4.17:8081/?token=<她的token>`，她只会看到自己的会话、设置、密钥；Bob 用 8082/3082 同理。各自实例记得加 `--trusted-host 192.168.4.17`（如果和共享实例一样用 IP 访问）。

## 为什么不建议其他做法

- **单实例 + 密码插件（`dsh-webui-auth`）**：只能挡陌生人，登录后仍是同一个 token/同一份会话库，**做不到会话隔离**
- **单实例内分工作区**：会话虽按工作区目录存放（`$DSH_HOME/sessions/...`），但任何人拿到 token 都能切到任意工作区查看/操作，代理也能读其他目录，隔离形同虚设
- **共享 `DSH_HOME` 跑多个 profile**：`sessions`、设置、凭据都在同一个 $DSH\_HOME 下，仍互相可见

## 安全边界提醒

这个方案保证的是**会话/设置/入口隔离**。如果用户之间不完全互信，还需要：

- 各自独立工作区目录 + 独立 OS 用户（agent 以该 OS 用户执行命令，读不到别人的目录）
- 每个实例的 token 只发给对应的人；局域网里 token 即权限

需要我直接在服务器上把 `alice` 这个实例（用户、目录、unit、nginx 端口、token）完整建好吗？建好后把她的访问地址单独给你。