Skip to content
dsh.fish
Skill

dsh-vps-https-deployer

本地 Agent 通过 OpenSSH 连接远程 Ubuntu VPS,安装 DSH Web,并使用 HTTPS 和仅密码网关开放外网访问。

Source
vinokok
Updated
Updated 2 days ago

Readme

# DSH 远程 VPS 外网部署

> ## Agent Skill(默认中文)
>
> **[立即读取并自动部署:SKILL.md](SKILL.md)**
>
> [English Skill / 英文版](SKILL.en.md)
>
> 本 Skill 供本地 Agent 通过 OpenSSH 连接远程 VPS,自动完成 DSH 安装、HTTPS、密码网关、设置兼容、验证、升级和回滚。

[中文部署正文](#架构) · [English guide / 英文部署文档](README.en.md)

本指南说明如何在**远程 Ubuntu VPS** 上部署 DeepSeek Harness(DSH)Web,并通过公网 HTTPS 域名、Nginx/反向代理 和仅密码登录网关安全访问。适用于通过 SSH 管理 VPS 的管理员,不会修改本地电脑上的 DSH Desktop 客户端。

参考部署使用 `<VPS_IP>`、`/opt/dsh-headless-test`、Nginx,以及 `<dsh.example.com>`。请替换所有尖括号占位符。

> **安全提醒:** 本文已脱敏。不要把密码、API Key、SSH 私钥、TLS 私钥、Session secret、密码哈希、Cookie 或 `.env` 文件提交到公开仓库。

## 架构

```text
浏览器
  |
  | HTTPS + 仅密码登录 Cookie
  v
Nginx :443
  |
  v
独立登录网关 127.0.0.1:3090
  |
  | 认证后的反向代理
  v
DSH Web 127.0.0.1:3081
```

DSH 和登录网关都只监听 VPS 本机回环地址,公网唯一入口是 Nginx 的 HTTPS 端口。

## 前提条件

- Ubuntu VPS,已安装 Node.js 和 systemd
- DNS 名称 `<dsh.example.com>` 指向 VPS
- Nginx 或标准 Nginx
- DSH 独立安装在例如 `/opt/dsh-headless-test`
- 建议使用专用非 root 服务账号;只有现有部署确实需要时才使用 root

## 1. 从本地 Agent 连接远程 VPS

部署从本地 Agent 开始。使用系统 OpenSSH 和一次性远程命令执行,连接和部署逻辑由本 Skill 独立完成。

先在本地确认 SSH 主机指纹,再连接:

```bash
ssh-keyscan -t ed25519 <VPS_HOST> > /tmp/<VPS_HOST>.known_hosts
ssh-keygen -lf /tmp/<VPS_HOST>.known_hosts
ssh -o UserKnownHostsFile=/tmp/<VPS_HOST>.known_hosts -o StrictHostKeyChecking=yes <VPS_USER>@<VPS_HOST>
```

自动化 Agent 应使用本地安全存储中的 SSH 私钥,不应把私钥、密码或 token 写进 skill、脚本、日志或 Git 仓库。每个远程命令都应记录退出码;部署前先执行只读检查:

```bash
ssh -o UserKnownHostsFile=/tmp/<VPS_HOST>.known_hosts -o StrictHostKeyChecking=yes <VPS_USER>@<VPS_HOST> \
  'uname -a; command -v systemctl; command -v node || true; command -v nginx || true'
```

确认目标主机、操作系统、磁盘空间、Node.js、systemd 和反向代理状态后,Agent 才能继续执行远程安装。

## 2. 在远程 VPS 上运行 DSH 服务

将 DSH 安装在独立目录中,并让它只绑定 `127.0.0.1`。网关文件不要放进 DSH 的 `node_modules`,这样升级 DSH 时不会覆盖外网访问层。

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

[Service]
Type=simple
User=<dsh-user>
WorkingDirectory=/opt/dsh-headless-test
Environment=DSH_HOME=/opt/dsh-headless-test/home
ExecStart=/opt/dsh-headless-test/node_modules/.bin/dsh web --host 127.0.0.1 --port 3081 --no-open --trusted-host <dsh.example.com>
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true

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

启用并验证:

```bash
systemctl daemon-reload
systemctl enable --now dsh-web.service
systemctl status dsh-web.service
ss -ltnp | grep 3081
```

监听地址应为 `127.0.0.1:3081`,不能是 `0.0.0.0:3081`。

## 3. 配置 DNS 和 HTTPS 证书

添加 A 记录:

```text
<dsh.example.com> -> <VPS_PUBLIC_IP>
```

申请 Let's Encrypt HTTP-01 证书时,DNS 必须能把验证请求送到 VPS 上的 Nginx。如果使用 Cloudflare,可以临时关闭代理完成验证;证书签发后再开启代理,并使用 `Full (strict)`。

临时 HTTP 配置示例:

```nginx
server {
    listen 80;
    server_name <dsh.example.com>;
    root /www/wwwroot/<dsh.example.com>;
    location ^~ /.well-known/acme-challenge/ { try_files $uri =404; }
    location / { return 301 https://$host$request_uri; }
}
```

通过 certbot webroot 申请证书,并将证书路径配置到反向代理:

```bash
certbot certonly --webroot -w /www/wwwroot/<dsh.example.com> -d <dsh.example.com>
install -d -m 750 /www/server/panel/vhost/cert/<dsh.example.com>
install -m 640 /etc/letsencrypt/live/<dsh.example.com>/fullchain.pem /www/server/panel/vhost/cert/<dsh.example.com>/fullchain.pem
install -m 640 /etc/letsencrypt/live/<dsh.example.com>/privkey.pem /www/server/panel/vhost/cert/<dsh.example.com>/privkey.pem
```

配置证书续期 deploy hook:续期后复制证书,并且仅在 `nginx -t` 成功后 reload Nginx。

## 4. 配置 Nginx 反代 HTTPS 和 WebSocket

HTTPS 虚拟主机应反代到登录网关,而不是直接反代到 DSH:

```nginx
map $http_upgrade $dsh_connection_upgrade {
    default upgrade;
    '' close;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name <dsh.example.com>;

    ssl_certificate /www/server/panel/vhost/cert/<dsh.example.com>/fullchain.pem;
    ssl_certificate_key /www/server/panel/vhost/cert/<dsh.example.com>/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    location / {
        proxy_pass http://127.0.0.1:3090;
        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $dsh_connection_upgrade;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
        proxy_buffering off;
    }
}
```

检查并 reload:

```bash
/www/server/nginx/sbin/nginx -t
/www/server/nginx/sbin/nginx -s reload
```

## 4. 部署独立的仅密码登录网关

网关是独立的 Node.js 服务,应具备:

- 不暴露受保护产品名称的通用登录页
- 服务端 PBKDF2、bcrypt 或 Argon2 密码校验
- 前端 HTML/JavaScript 中不包含明文密码
- 签名的 `HttpOnly`、`Secure`、`SameSite=Strict` Cookie
- Cookie 过期和退出登录
- 登录失败限流
- HTTP 反代和 WebSocket Upgrade 转发
- 只监听 `127.0.0.1`

示例 systemd 服务:

```ini
# /etc/systemd/system/dsh-login-gateway.service
[Unit]
Description=DSH password gateway
After=network-online.target dsh-web.service
Requires=dsh-web.service

[Service]
Type=simple
User=<gateway-user>
WorkingDirectory=/opt/dsh-login-gateway
Environment=NODE_ENV=production
Environment=GATEWAY_HOST=127.0.0.1
Environment=GATEWAY_PORT=3090
ExecStart=/usr/bin/node /opt/dsh-login-gateway/server.js
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/dsh-gateway

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

秘密文件单独存放:

```bash
install -d -m 750 /var/lib/dsh-gateway
chmod 600 /var/lib/dsh-gateway/password.hash /var/lib/dsh-gateway/session.secret
```

这些文件绝不能上传到公开仓库。

## 5. 解决远程 HTTPS 下的 DSH 设置限制

DSH 会将设置、凭据和模型发现操作限制为 loopback 同源请求。密码网关虽然完成了认证,但浏览器地址仍然是公网域名,因此需要两层兼容:

1. 网关认证后,向 DSH 内部转发时使用 `Host: 127.0.0.1:3081`,并移除浏览器的 `Origin`、`Referer` 和 `Sec-Fetch-*` 信任标记。
2. 对外提供的 DSH 浏览器连接 bundle 需要兼容层,使已认证的远程页面被视为受控 Host。

兼容层必须放在 DSH 安装目录之外,例如:

```text
/opt/dsh-login-gateway/plugins/remote-host-compat.js
```

网关只应处理精确匹配的 `@deepseek-ai/dsh-client-connection/client.js`,先验证已知源码标记;如果 DSH 升级后结构变化,则拒绝改写并记录错误。不要直接修改 `node_modules`,这样升级 DSH 不会重置兼容层。

每次升级后验证:

```bash
curl -k -b <authenticated-cookie-file> \
  https://<dsh.example.com>/plugins/@deepseek-ai/dsh-client-connection/client.js \
  | grep 'isLoopback: true'
```

然后用无痕窗口打开页面,进入模型设置,确认提供方目录和设置读写正常。

## 6. 验证清单

```bash
systemctl is-active dsh-web.service
systemctl is-active dsh-login-gateway.service
ss -ltnp | grep -E '127.0.0.1:(3081|3090)'
/www/server/nginx/sbin/nginx -t
curl -k -I https://<dsh.example.com>/
```

功能验证:

- 未登录请求只返回通用登录页
- 错误密码返回 `401`
- 连续失败后触发 `429`
- 正确密码返回 `HttpOnly; Secure; SameSite=Strict` Cookie
- 登录后可以打开 DSH 首页
- 模型/提供方设置正常加载
- WebSocket 保持可用
- 公网无法直接访问 3081 和 3090
- 证书续期 hook 在 reload 前执行 `nginx -t`

## 7. DSH 升级流程

```bash
# 先备份服务和网关配置
systemctl cat dsh-web.service > /var/backups/dsh-web.service.txt
systemctl cat dsh-login-gateway.service > /var/backups/dsh-login-gateway.service.txt
cp /www/server/panel/vhost/nginx/<dsh.example.com>.conf /var/backups/

# 只在 DSH 自己的安装目录升级
cd /opt/dsh-headless-test
pnpm update @deepseek-ai/dsh

# 重启并验证
systemctl restart dsh-web.service
systemctl restart dsh-login-gateway.service
systemctl is-active dsh-web.service dsh-login-gateway.service
```

网关源码、秘密文件、systemd 服务、Nginx 配置和兼容插件都在 DSH npm 包之外,正常情况下不会被 DSH 升级覆盖。

## 8. 回滚

建议保存以下带日期的备份:

- Nginx 虚拟主机
- DSH systemd 服务
- 网关 systemd 服务
- 网关源码
- DSH profile 配置

```bash
systemctl stop dsh-login-gateway.service
# 检查后恢复指定日期的备份
/www/server/nginx/sbin/nginx -t
/www/server/nginx/sbin/nginx -s reload
systemctl start dsh-web.service
```

如果兼容层拒绝新的 DSH bundle,应先停用兼容层并审查新版本结构,不要盲目对任意 JavaScript 做替换。

## 常见问题

### `settings are unavailable in this browser`

浏览器加载的 bundle 仍把公网域名视为非 loopback。检查兼容层是否生效,并用无痕窗口或清除站点数据重新加载。

### `transport failure for /api/llm.providers: HTTP 403`

检查网关是否移除了 `Origin`、`Referer`、`Sec-Fetch-*`,是否设置了内部 loopback Host,以及 WebSocket 是否正确转发。

### 出现浏览器 Basic Auth 弹窗

Nginx 仍启用了旧的 `auth_basic`。删除 HTTPS location 中的 Basic Auth,改为反代到登录网关。

### 修改 bundle 后出现 502

改写响应时,删除 `transfer-encoding: chunked` 再设置新的 `content-length`;保留 JavaScript Content-Type,并禁止该改写资源使用旧缓存。

## 安全建议

- 使用高强度唯一密码,怀疑泄露时立即轮换。
- 使用权限最小化且有效期较短的 GitHub 部署凭据。
- 开启 Cloudflare 代理时使用 `Full (strict)`。
- 优先使用专用非 root Linux 服务账号。
- 不要通过防火墙规则或端口转发暴露 DSH 的回环端口。
- 每次 DSH 版本变化后重新审查兼容层。

---

**语言切换:** 中文为默认入口;英文请查看 [README.en.md](README.en.md)。

Install

# Skills are files: copy them into $DSH_HOME/skills/dsh-vps-https-deployer (defaults to ~/.dsh/skills/dsh-vps-https-deployer)

Profile: web

Source