Back to home@vinokok

dsh-external-access-guide

远程 VPS 上部署 DSH 并通过 HTTPS 安全开放外网访问 | Deploy DSH on a remote VPS and expose it securely over HTTPS

Stars
0
Language
Created
Aug 24, 2026
Updated
Aug 24, 2026

Introduction

DSH 远程 VPS 外网部署

Agent Skill(默认中文)

立即读取并自动部署:SKILL.md

English Skill / 英文版

本 Skill 供本地 Agent 通过 OpenSSH 连接远程 VPS,自动完成 DSH 安装、HTTPS、密码网关、设置兼容、验证、升级和回滚。

中文部署正文 · English guide / 英文部署文档

本指南说明如何在远程 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 文件提交到公开仓库。

架构

浏览器
  |
  | 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 主机指纹,再连接:

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 仓库。每个远程命令都应记录退出码;部署前先执行只读检查:

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 时不会覆盖外网访问层。

# /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

启用并验证:

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 记录:

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

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

临时 HTTP 配置示例:

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 申请证书,并将证书路径配置到反向代理:

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:

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:

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

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

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

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

示例 systemd 服务:

# /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

秘密文件单独存放:

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,并移除浏览器的 OriginRefererSec-Fetch-* 信任标记。
  2. 对外提供的 DSH 浏览器连接 bundle 需要兼容层,使已认证的远程页面被视为受控 Host。

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

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

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

每次升级后验证:

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

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

6. 验证清单

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 升级流程

# 先备份服务和网关配置
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 配置
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

检查网关是否移除了 OriginRefererSec-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