适用场景:你有一台服务器,想把 DeepSeek Harness 的浏览器界面(Web GUI)跑在上面,让家里的电脑、公司的电脑、甚至手机浏览器都能登录使用,用 AI 帮你写代码、跑任务。本文基于 DSH
0.1.0-rc.6+ Ubuntu 22.04/24.04 + nginx + pm2 的实际部署经验整理。
为什么需要服务器 + 反代
DSH 的 Web 界面(dsh --profile web)默认只监听 127.0.0.1:3080,而且出于安全考虑禁止 --host 0.0.0.0(CLI 直接报错:防止把远程代码执行能力暴露到公网)。所以正确姿势是:
浏览器(手机/电脑)──► nginx(80/443)──► dsh web(127.0.0.1:3080)
nginx 做反向代理 + 登录保护 + 移动端适配,DSH 本身只在本机回环地址提供服务。
架构总览
- dsh web:实际服务,监听
127.0.0.1:3080 - dsh-webui-auth 插件: 登录认证(未认证的浏览器拿不到任何资源)
- pm2: 守护进程 + 开机自启
- nginx: 对外入口,反代 + 移动端缩放 + manifest 直出
第一步:安装 DSH
需要 Node.js ^22.19.0 || >=24.0.0:
npm install -g @deepseek-ai/dsh
dsh --version # 验证安装
第二步:初始化 web profile 并安装认证插件
dsh --profile web 首次运行时自动初始化 profile(目录在 ~/.dsh/profiles/web)。先装登录认证插件(需要 pnpm,Node 自带 corepack):
corepack enable pnpm
npx @deepseek-ai/dsh plugin --profile web add dsh-webui-auth
之后启动 web 服务,首次访问登录页时创建你的账号密码(存在 ~/.dsh/profiles/web/node_modules/dsh-webui-auth/dsh-webui-auth.json,密码是 scrypt 哈希):
dsh --profile web
补充:固定目录选择器为浏览器模式(服务器远程访问必做)
创建 ~/.dsh/patch.yml:
# 固定目录选择器为「浏览器浏览」模式。
# 默认的 directory-picker-auto 会根据服务器环境自动选择:
# - native:系统目录对话框,需要在服务器本机的显示器上操作;
# - browse:浏览器内的目录列表(可浏览/新建子目录)。
# 服务器通过 nginx 反代远程访问时,native 对话框会弹在服务器自己的屏幕上,
# 远程的手机/电脑根本看不到,必须固定为 browse。
# 注意:patch 无法直接改已有行的插件 name,所以先禁用 auto 行,
# 再插入 browse 的主机端 + 客户端插件对。
- id: directory-picker
disabled: true
- insert:
- id: directory-picker-browse
name: '@deepseek-ai/dsh-host-directory-picker-browse'
- id: directory-picker-browse-ui
name: '@deepseek-ai/dsh-client-ui-directory-picker-browse'
启动时用 --patch 挂上(见第四步的启动参数)。如果服务器上确实没有
DISPLAY/chooser 环境,auto 也会选 browse,但显式固定可以保证任何环境下行为一致。
第三步:配置 API Key
在 ~/.dsh/.credentials.yaml 写入:
DEEPSEEK_API_KEY: sk-你的密钥
(或通过环境变量 DEEPSEEK_API_KEY 注入,二选一即可。)
第四步:pm2 守护 + 开机自启
写 pm2 配置 ~/.dsh/pm2.json:
{
"apps": [
{
"name": "dsh-web",
"script": "/usr/local/bin/dsh",
"args": "--profile web --patch /root/.dsh/patch.yml --trusted-host 你的域名",
"autorestart": true,
"restart_delay": 3000
}
]
}
--trusted-host 你的域名必须加:Web 界面调用/api时有浏览器信任围栏,通过域名访问时 Host 不再是127.0.0.1:3080,不加这个参数 API 会被拒。
启动并设置开机自启:
pm2 start ~/.dsh/pm2.json
pm2 save
pm2 startup
第五步:nginx 反向代理
新建 /etc/nginx/sites-available/dsh.apple.me(域名换成你的),软链到 sites-enabled 并 reload:
ln -s /etc/nginx/sites-available/dsh.apple.me /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
下面这份是完整最终配置,包含两个必踩的坑的解法(见注释),gzip/缓存为可选优化:
server {
listen 80;
server_name dsh.apple.me;
# ── 可选:gzip 压缩(纯反代站点,上游不压缩,统一由 nginx 压)──
gzip on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1k;
gzip_vary on; # 必须:与下面的强缓存配合,区分压缩/未压缩变体
gzip_types text/plain text/css application/json application/javascript
application/x-javascript text/javascript application/xml
application/xml+rss image/svg+xml application/manifest+json;
# ── 可选:静态资源缓存(Vite 产物带内容哈希,可放心长缓存)──
location ~* ^/assets/.*\.(js|css|woff2?|ttf|eot|svg|png|jpe?g|gif|ico|webp|json|map)$ {
proxy_pass http://127.0.0.1:3080;
proxy_http_version 1.1;
proxy_set_header Host 127.0.0.1:3080;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# ── 坑 2 解法:manifest 由 nginx 直接输出(见下文详解)──
location = /manifest.webmanifest {
default_type application/manifest+json;
return 200 '{
"id": "/",
"name": "DeepSeek Harness",
"short_name": "DSH",
"start_url": "/",
"scope": "/",
"display": "standalone",
"icons": [
{
"src": "/favicon.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any"
}
]
}';
}
# ── 主入口:反代 + 坑 1 解法(移动端缩放)──
location / {
proxy_pass http://127.0.0.1:3080;
proxy_http_version 1.1;
proxy_set_header Host 127.0.0.1:3080;
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 $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; # WebSocket/SSE 长连接
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
# ── 坑 1 解法:手机端 viewport 缩放 ──
proxy_set_header Accept-Encoding ""; # 必须:让 sub_filter 拿到未压缩 body
sub_filter 'name="viewport" content="width=device-width, initial-scale=1"'
'name="viewport" content="width=device-width, initial-scale=0.7"';
sub_filter_once on;
}
}
坑 1:手机浏览器内容区域太小
现象:手机上打开 Web 界面, 工作区域宽度太窄,设置页面几乎没法用。
原因:页面 HTML 里的 viewport meta 是 initial-scale=1, 文字太大。
解法:在 nginx 上用 sub_filter 把响应里的 viewport 改成 0.7,让页面在手机屏上缩小显示。两个要点:
- 必须先
proxy_set_header Accept-Encoding "";把发给上游的压缩请求头置空,否则上游返回 gzip 压缩的 body,sub_filter的字符串替换会失效; sub_filter默认只处理text/html,HTML 页面正好符合。
改完 nginx -t && systemctl reload nginx,手机硬刷新(清缓存)后就能看到效果。
坑 2:manifest.webmanifest 请求永远是 302,PWA 装不上
现象:浏览器加载 <link rel="manifest"> 时请求 /manifest.webmanifest 一直被重定向到登录页(302),DevTools 里 manifest 解析失败,手机"添加到主屏幕"不可用,display 模式也控制不了。
原因:dsh-webui-auth 插件在 HTTP 层拦截了所有未认证请求(包括静态资源)并 302 到登录页。而浏览器拉取 manifest 的请求不会携带登录 Cookie,于是永远 302,拿不到 manifest 内容。
解法:让 nginx 绕过认证,直接输出这份 manifest 文件——内容照抄 DSH dist 里的 manifest.webmanifest,并把 "display": "fullscreen" 改成 "standalone"(standalone 是"普通应用窗口",手机 PWA 更合适;fullscreen 会全屏隐藏浏览器 UI)。
要点:
- 用
location =精确匹配,只接管这一个路径,不影响其他资源; - 内容硬编码在 nginx 里,以后 DSH 升级如果改了 manifest 结构需要手动同步;
- 不用改 DSH 源码,也不用给认证插件开白名单,改动全部在 nginx 层,升级 DSH 不会丢。
访问与验证
- 电脑:浏览器打开
http://你的域名,登录后正常使用; - 手机:同样地址登录。验证 PWA:Chrome 菜单 →「添加到主屏幕」;DevTools → Application → Manifest,
display应为standalone; - 如果手机或电脑上看到的是旧页面,记得硬刷新清缓存。
常见问题
- 想用 HTTPS?
用 certbot 申请证书,
listen 443 ssl,并把 80 重定向到 443(注意 443 的 location 也要带上同样的 sub_filter 和 manifest 直出配置) - 改完 nginx 不生效?
nginx -t先查语法,再systemctl reload nginx,浏览器硬刷新 - 远程访问时"选择目录"没反应,或对话框弹在服务器本机屏幕上?
检查启动参数里是否带了
--patch /root/.dsh/patch.yml固定 browse 目录选择器;不固定时 auto 可能选中 native(系统对话框弹在服务器屏幕上,远程用户看不到也点不了)
阅读:27 评论: 0 💬