dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
远程 VPS 上部署 DSH 并通过 HTTPS 安全开放外网访问 | Deploy DSH on a remote VPS and expose it securely over HTTPS
Agent Skill
Read the automation skill: SKILL.en.md
This skill lets a local Agent connect to a remote VPS over OpenSSH and automate DSH installation, HTTPS, password gateway, settings compatibility, verification, upgrades, and rollback.
This guide documents how to expose a remote DeepSeek Harness (DSH) Web installation on an Ubuntu VPS through a public HTTPS domain, Nginx/reverse proxy, and a password-only login gateway. It is intended for an administrator connecting to the VPS over SSH; it does not modify the DSH desktop client running on the administrator's computer.
The reference deployment used a VPS at <VPS_IP>, DSH under /opt/dsh-headless-test, Nginx, and a domain such as <dsh.example.com>. Substitute your own values.
Security: this document is intentionally sanitized. Replace every value in angle brackets. Never commit passwords, API keys, SSH private keys, TLS private keys, session secrets, password hashes, cookies, or
.envfiles.
Browser
|
| HTTPS + password-only session cookie
v
Nginx :443
|
v
Independent login gateway 127.0.0.1:3090
|
| authenticated reverse proxy
v
DSH Web 127.0.0.1:3081
The DSH process and gateway bind only to loopback. The only public entry point is Nginx on HTTPS.
<dsh.example.com> pointing to the VPS/opt/dsh-headless-testThe deployment starts on the local Agent. Use the system OpenSSH client and one-shot remote commands; the connection and deployment logic is implemented by this standalone skill.
Verify the SSH host key before connecting:
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>
The automation Agent must use an SSH private key from local secure storage. Never write private keys, passwords, or tokens into the skill, scripts, logs, or Git repository. Run a read-only preflight first:
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'
Continue only after the host identity, OS, disk space, Node.js, systemd, and reverse-proxy state are understood.
Create a private application directory and install DSH there. Keep the DSH package separate from all gateway files so upgrades do not overwrite the access layer.
Example service:
# /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
Apply and verify:
systemctl daemon-reload
systemctl enable --now dsh-web.service
systemctl status dsh-web.service
ss -ltnp | grep 3081
The listener should be 127.0.0.1:3081, never 0.0.0.0:3081.
Create an A record:
<dsh.example.com> -> <VPS_PUBLIC_IP>
For initial Let's Encrypt HTTP-01 validation, DNS must resolve directly to the VPS or Cloudflare must be configured so the challenge reaches Nginx. If Cloudflare proxying is temporarily disabled, re-enable it only after issuance and use Full (strict) with a valid origin certificate.
Use a temporary HTTP-only Nginx server for ACME validation:
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; }
}
Issue the certificate with certbot webroot, then configure the certificate and key paths in the reverse proxy. Protect the private key with restrictive permissions.
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
Create a renewal deploy hook that copies renewed files and reloads Nginx only after nginx -t succeeds.
The HTTPS virtual host should forward to the gateway, not directly to 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;
}
}
Validate before reload:
/www/server/nginx/sbin/nginx -t
/www/server/nginx/sbin/nginx -s reload
The gateway is an independent Node.js service. It should provide:
HttpOnly, Secure, SameSite=Strict session cookieExample systemd unit:
# /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
Store secrets separately:
install -d -m 750 /var/lib/dsh-gateway
chmod 600 /var/lib/dsh-gateway/password.hash /var/lib/dsh-gateway/session.secret
Never put secrets in the public repository.
DSH restricts settings, credential, and model-discovery operations to loopback same-origin requests. A password gateway is an authentication boundary, but the browser address remains a public domain. Two parts are needed:
Host: 127.0.0.1:3081 and removes browser-origin trust markers before forwarding.Keep this compatibility layer outside node_modules, for example:
/opt/dsh-login-gateway/plugins/remote-host-compat.js
The gateway should patch only the exact @deepseek-ai/dsh-client-connection/client.js response, verify known source markers, and fail closed if the DSH bundle structure changes. Do not edit DSH package files directly. This keeps the change independent from DSH package upgrades.
After every DSH upgrade, verify:
curl -k -b <authenticated-cookie-file> \
https://<dsh.example.com>/plugins/@deepseek-ai/dsh-client-connection/client.js \
| grep 'isLoopback: true'
Then open the model settings page in a private window and confirm that the provider catalog loads and settings are writable.
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>/
Functional checks:
401429 after repeated failuresHttpOnly; Secure; SameSite=Strict cookienginx -t before reload# Back up service and gateway configuration first
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/
# Upgrade DSH in its own installation directory
cd /opt/dsh-headless-test
pnpm update @deepseek-ai/dsh
# Restart and verify
systemctl restart dsh-web.service
systemctl restart dsh-login-gateway.service
systemctl is-active dsh-web.service dsh-login-gateway.service
The gateway source, secrets, systemd unit, Nginx site, and compatibility plugin live outside DSH's npm package and should survive upgrades.
Keep a dated backup of:
Rollback steps:
systemctl stop dsh-login-gateway.service
# Restore the dated files after reviewing their contents
/www/server/nginx/sbin/nginx -t
/www/server/nginx/sbin/nginx -s reload
systemctl start dsh-web.service
If the compatibility plugin rejects a new DSH bundle, leave it disabled until the marker logic is reviewed. Do not bypass the fail-closed behavior by blindly patching arbitrary JavaScript.
settings are unavailable in this browserThe browser bundle still considers the public hostname a non-loopback surface. Confirm the compatibility layer is active and that the browser loaded a fresh bundle. Use a private window or clear site data.
transport failure for /api/llm.providers: HTTP 403Check that the gateway strips Origin, Referer, and Sec-Fetch-* before the internal request, sets the internal loopback Host, and preserves WebSocket forwarding. Check Nginx and gateway logs.
An old Nginx auth_basic directive is still active. Remove it from the HTTPS location and proxy to the gateway instead.
When rewriting a response, remove transfer-encoding: chunked before setting a new content-length. Preserve the JavaScript content type and disable stale caching for the transformed asset.
Full (strict) mode when proxying is enabled.