DeepSeek Harness 部署实战:Ubuntu 云服务器安装、微信插件排错与 ClawBot 接入
如果你准备把 DeepSeek Harness 部署到 Ubuntu 云服务器,并通过 Web 页面使用它,实际操作中最容易卡住的地方通常集中在 Node.js、DSH Profile、插件依赖和 credentials 文件格式。
本文记录一套实际排查过程。从服务器上的 DSH 安装,到 Nginx 反向代理,再到微信插件冲突、package.json 损坏、credentials 版本错误,以及最终切换到 dsh-clawbot 插件。文中的服务器 IP、API Key、session secret 等敏感信息均已隐藏。
一、Ubuntu 云服务器上安装 DSH 需要准备什么
如果你的服务器执行 node -v 时出现 Command 'node' not found,第一步需要先安装 Node.js。
本次环境最终使用的是 Node.js 22 系列,并通过 npm 安装 DeepSeek Harness。服务器上的实际运行环境后来确认如下。
node -v
得到:
v22.23.2
同时使用 pnpm 管理 DSH Profile 依赖。
pnpm -v
本次环境使用的是 pnpm 10 系列。
这里有一个实际踩坑点。DSH 的主程序、Profile 和插件依赖并不完全等价于普通 Node.js 项目。DSH 会在自己的 profile 目录下组织插件,并通过 pnpm 管理这些依赖。
因此,后续排错时不能只看:
npm list
还需要检查:
pnpm list --depth 1
以及:
cat /root/.dsh/profiles/web/package.json
这两个文件分别告诉你当前 profile 实际安装了什么,以及 DSH 启动时准备加载什么。
二、DSH Web Profile 实际存放在哪里
如果使用默认的 web profile,核心目录位于:
/root/.dsh/profiles/web
进入目录后可以看到 profile 的 package.json。
一个最基本的 Web Profile 可以保持为下面这种结构。
{
"name": "dsh-profile-web",
"private": true,
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app"
],
"patchReload": "live"
}
}
}
这里的 bundles 很重要。
DSH 启动时会读取 Profile Manifest,然后按照这里定义的 bundle 加载组件。
如果你已经卸载了某个插件,却忘记从 bundles 中删除它,DSH 仍然可能尝试加载这个插件。
本次微信插件排查就是因为这个问题产生了额外的启动错误。
三、为什么卸载微信插件以后还要检查 package.json
最开始使用的是:
deepseek-harness-weixin
后来发现这个插件与当前 DSH 内部的 dsh-agent-presets API 存在兼容问题,因此决定删除微信插件。
删除依赖以后,pnpm list --depth 1 已经没有输出相关依赖。
但是 package.json 中仍然存在:
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"deepseek-harness-weixin"
]
这时候单纯执行:
pnpm list --depth 1
并不能说明 DSH 启动时不会加载微信插件。
真正影响 Profile 启动的是 bundles 配置。
所以最终需要变成:
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app"
]
修改完成以后建议立即验证 JSON。
node -e "JSON.parse(require('fs').readFileSync('package.json','utf8')); console.log('package.json OK')"
如果输出:
package.json OK
再继续启动 DSH。
四、修改 package.json 时,一个逗号就可能让整个 DSH 无法启动
实际排查中曾经出现这样的错误。
SyntaxError: Unexpected token ']'
错误来自 DSH 的:
readProfileManifest
这说明 DSH 在加载 Profile Manifest 时,连 JSON 都没有成功解析。
当时的错误类似于:
Unexpected token ']', ..."p",
],
""... is not valid JSON
这种情况下,不需要继续排查插件代码。
直接把 Profile 的 package.json 恢复成一个合法 JSON 文件即可。
例如:
cat > /root/.dsh/profiles/web/package.json <<'EOF'
{
"name": "dsh-profile-web",
"private": true,
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app"
],
"patchReload": "live"
}
}
}
EOF
然后:
node -e "JSON.parse(require('fs').readFileSync('package.json','utf8')); console.log('package.json OK')"
这个检查非常值得保留。
DSH 的报错栈很长,但如果最前面的错误是:
SyntaxError
Unexpected token
JSON.parse
readProfileManifest
优先检查 Profile Manifest。
五、DSH 启动后出现 credentials 错误怎么办
当 package.json 修复以后,DSH 就进入了下一阶段。
这次错误变成了:
credentials-local: /root/.dsh/.credentials.yaml declares version "1"; this build reads version 1
乍看容易理解成版本 1 没问题,但这里有一个非常关键的 JavaScript 类型区别。
当前 dsh-credentials-local 源码使用的是:
const DOCUMENT_VERSION = 1;
并且判断方式是:
if (fields["version"] !== 1)
这里使用了严格相等。
因此下面两种写法完全不同。
version: 1
这是数字。
version: "1"
这是字符串。
当前 DSH 版本要求的是:
version: 1
所以如果看到:
version: "1"
应该改回:
version: 1
可以使用:
sed -i '1s/version: "1"/version: 1/' /root/.dsh/.credentials.yaml
六、DSH credentials 文件应该保持什么结构
本次排查还发现,credentials 文件中原本包含 DeepSeek API Key 和浏览器 session。
典型结构类似下面这样。
version: 1
refs:
DEEPSEEK_API_KEY: "YOUR_DEEPSEEK_API_KEY"
records:
client-connection/browser-session:
kind: grant
payload:
version: 1
secret: "YOUR_BROWSER_SESSION_SECRET"
其中:
version: 1
是 credentials 文档版本。
而:
payload:
version: 1
属于具体 record 的 payload。
这两个字段都应该保持数字类型。
七、为什么重新配置 API Key 时找不到 DEEPSEEK_API_KEY
后来执行:
sed -n '1,12p' /root/.dsh/.credentials.yaml
发现当前文件已经变成:
version: 1
records:
client-connection/browser-session:
kind: grant
payload:
version: 1
secret: ...
这里已经没有:
refs:
DEEPSEEK_API_KEY: ...
所以之前用于替换 API Key 的脚本自然会提示:
没有找到 DEEPSEEK_API_KEY
这个错误本身没有说明 API Key 输入失败,只说明当前 YAML 中已经没有这个 reference。
如果需要重新加入 DeepSeek API Key,需要恢复 refs 段。
例如:
version: 1
refs:
DEEPSEEK_API_KEY: "YOUR_NEW_DEEPSEEK_API_KEY"
records:
client-connection/browser-session:
kind: grant
payload:
version: 1
secret: "YOUR_BROWSER_SESSION_SECRET"
操作之前建议备份。
cp /root/.dsh/.credentials.yaml \
/root/.dsh/.credentials.yaml.bak-$(date +%Y%m%d-%H%M%S)
API Key 不建议直接写进 shell 命令历史,可以使用隐藏输入。
read -rsp "请输入新的 DeepSeek API Key: " NEW_KEY
echo
read -rsp 不会回显输入内容,所以粘贴 Key 后终端看起来没有任何变化,这是正常行为。
可以用下面的方式确认变量存在。
[ -n "$NEW_KEY" ] && echo "API Key 已输入" || echo "API Key 为空"
不要使用:
echo "$NEW_KEY"
避免把密钥直接显示到终端。
八、为什么原来的 deepseek-harness-weixin 插件无法继续使用
本次最关键的插件兼容性问题来自:
@deepseek-ai/dsh-agent-presets
微信插件启动时尝试调用:
resolveSessionPreset
但服务器当前安装的:
@deepseek-ai/dsh-agent-presets
版本为:
0.1.5-rc.2
检查实际导出内容后发现:
resolveSessionPreset: undefined
导出的 preset 相关 API 中没有这个函数。
也就是说,微信插件需要的 API 和服务器当前 DSH 内部包提供的 API 对不上。
当插件继续加载时,就出现:
The requested module '@deepseek-ai/dsh-agent-presets'
does not provide an export named 'resolveSessionPreset'
这类错误应该从依赖 API 兼容性方向处理。
单纯重新安装 Node.js、清空浏览器缓存或者重新配置 API Key 都无法解决这个问题。
九、为什么不建议为了微信插件盲目升级 DSH
当发现插件需要某个 API 时,一个直觉做法是升级 DSH。
本次环境中这个思路并不可靠。
当前 DSH 主程序与内部包存在独立的版本组合,dsh-agent-presets 的 API 本身也经历过变化。
实际检查:
node -e "import('@deepseek-ai/dsh-agent-presets').then(m => console.log(Object.keys(m)))"
可以直接看到当前版本实际提供哪些导出。
如果插件依赖:
resolveSessionPreset
而当前安装版本没有这个导出,那么继续强行安装同一条版本线的其他 rc 版本,可能引入新的 session 或 profile 兼容问题。
因此本次最终选择删除:
deepseek-harness-weixin
然后改用另一个 ClawBot 插件。
十、使用 dsh-clawbot 接入微信
当前准备采用的插件是:
github:tangwenhao616-netizen/dsh-clawbot
安装命令如下。
dsh plugin --profile web add github:tangwenhao616-netizen/dsh-clawbot
这个命令由 DSH 的 plugin 管理入口转发给 profile 下的 pnpm。
安装完成以后先检查:
cat /root/.dsh/profiles/web/package.json
再检查:
pnpm list --depth 1
重点确认新插件已经进入当前 Profile,同时原来的:
deepseek-harness-weixin
没有重新出现。
如果需要更加谨慎,可以在安装之前备份 Profile。
cd /root/.dsh/profiles/web
cp package.json package.json.bak
cp pnpm-lock.yaml pnpm-lock.yaml.bak 2>/dev/null || true
这样插件安装出现问题时,可以快速恢复 Profile 配置。
十一、DSH Web 为什么不能直接监听 0.0.0.0
部署到云服务器以后,还有一个容易误判的问题。
DSH Web 默认运行在:
127.0.0.1:3080
直接执行:
dsh web --no-open --trusted-host YOUR_SERVER_IP
可以让 DSH 在服务器本机运行。
如果尝试让 DSH 自己直接监听公网地址,当前安全机制可能拒绝这种方式。
因此更合适的部署方式是:
Internet
↓
Nginx
↓
127.0.0.1:3080
↓
DSH Web
DSH 本身继续只监听本机,Nginx 负责对外提供访问入口。
十二、Nginx 反向代理 DSH Web
本次环境采用 Nginx 转发到:
127.0.0.1:3080
WebSocket 相关 header 需要保留,否则 Web 页面中的实时通信可能出现问题。
典型配置结构如下。
server {
listen 80;
server_name YOUR_DOMAIN;
location / {
proxy_pass http://127.0.0.1:3080;
proxy_http_version 1.1;
proxy_set_header Host $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 $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
如果 DSH 发送较大的请求,还需要考虑 Nginx 的请求体限制。
之前实际排查过程中出现过:
HTTP 413
这种情况下可以增加:
client_max_body_size 100m;
proxy_request_buffering off;
然后检查配置:
nginx -t
确认没有语法错误后:
systemctl reload nginx
十三、DSH Web 出现 401 时先别判断 API Key 有问题
通过 Nginx 访问 DSH Web 后,如果直接请求内部 API,例如:
/api/llm/listProviders
可能得到:
401
这种结果需要结合 DSH Web 的浏览器 session 判断。
如果浏览器页面本身可以打开,而直接使用 curl 请求内部 API 得到 401,优先检查 Web session 和认证状态。
这和 DeepSeek API Key 是否有效是两个不同层面的事情。
十四、systemd 让 DSH 在服务器后台运行
手动执行:
dsh web --no-open --trusted-host YOUR_SERVER_IP
确认没有错误后,再交给 systemd。
例如:
[Unit]
Description=DeepSeek Harness
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/dsh web --no-open --trusted-host YOUR_SERVER_IP
Restart=always
RestartSec=5
User=root
WorkingDirectory=/root/.dsh
[Install]
WantedBy=multi-user.target
保存后:
systemctl daemon-reload
systemctl enable dsh
systemctl restart dsh
检查:
systemctl status dsh --no-pager
查看最近日志:
journalctl -u dsh -n 50 --no-pager
如果 systemd 不断重启,先不要继续修改 systemd。
直接看日志中第一次出现的错误。
例如:
plugin tree failed to load
通常应该继续往下看具体是哪个 loader 或 plugin。
如果出现:
readProfileManifest
JSON.parse
检查 package.json。
如果出现:
credentials-local
检查 .credentials.yaml。
如果出现:
does not provide an export
检查插件与 DSH 内部 API 的兼容性。
十五、这次排错可以归纳成一条实际排查路径
遇到 DSH 无法启动时,可以按照启动阶段逐层判断。
dsh web
↓
读取 Profile
↓
package.json
↓
加载 bundles
↓
加载插件
↓
credentials
↓
Web App
↓
Nginx / 浏览器
每一层对应的检查命令不同。
Profile JSON
node -e "JSON.parse(require('fs').readFileSync('/root/.dsh/profiles/web/package.json','utf8')); console.log('OK')"
插件列表
cd /root/.dsh/profiles/web
pnpm list --depth 1
Profile 配置
cat /root/.dsh/profiles/web/package.json
Credentials
sed -E 's/(DEEPSEEK_API_KEY:).*/\1 "***REDACTED***"/; s/(secret:).*/\1 "***REDACTED***"/' \
/root/.dsh/.credentials.yaml
DSH 运行状态
systemctl status dsh --no-pager
DSH 日志
journalctl -u dsh -n 50 --no-pager
Nginx 配置
nginx -t
这种顺序可以避免在 Profile JSON 已经损坏的情况下继续排查 API Key,也可以避免插件 API 报错时反复修改 Nginx。
部署与排错操作清单
基础环境
node -v
pnpm -v
dsh --version
DSH Profile
cd /root/.dsh/profiles/web
cat package.json
pnpm list --depth 1
删除旧微信插件
确认 package.json 中只保留需要的 bundle。
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app"
]
验证 JSON
node -e "JSON.parse(require('fs').readFileSync('package.json','utf8')); console.log('package.json OK')"
Credentials
version: 1
refs:
DEEPSEEK_API_KEY: "YOUR_DEEPSEEK_API_KEY"
records:
client-connection/browser-session:
kind: grant
payload:
version: 1
secret: "YOUR_BROWSER_SESSION_SECRET"
测试 DSH
dsh web --no-open --trusted-host YOUR_SERVER_IP
安装新的 ClawBot 插件
dsh plugin --profile web add github:tangwenhao616-netizen/dsh-clawbot
检查插件
pnpm list --depth 1
systemd
systemctl restart dsh
systemctl status dsh --no-pager
journalctl -u dsh -n 50 --no-pager
Nginx
nginx -t
systemctl reload nginx
FAQ
DSH 报 Unexpected token ']' 怎么处理?
优先检查 /root/.dsh/profiles/web/package.json。这是 Profile Manifest 的 JSON 解析失败,通常意味着删除插件时留下了多余逗号或其他非法 JSON。
version: "1" 和 version: 1 有什么区别?
当前 dsh-credentials-local 使用数字 1 作为文档版本。"1" 是字符串,在严格比较下不会被当成同一个值。
删除微信插件以后为什么还要修改 bundles?
pnpm list 反映安装依赖,bundles 决定 DSH Profile 启动时加载哪些组件。两者需要同时保持一致。
为什么微信插件会出现 resolveSessionPreset 错误?
当前服务器上的 dsh-agent-presets 没有导出这个函数,而原微信插件尝试导入它,因此插件初始化阶段失败。
修改 credentials 时应该删除 browser-session 吗?
当前这套部署中没有必要。client-connection/browser-session 属于已有 session credential,配置 DeepSeek API Key 时只处理 refs.DEEPSEEK_API_KEY,保留现有 record。
read -rsp 输入 API Key 为什么看不到字符?
这是 read 的隐藏输入行为。粘贴 Key 后直接按 Enter 即可。可以使用 [ -n "$NEW_KEY" ] 检查变量是否为空。
DSH Web 为什么放在 Nginx 后面?
当前部署中 DSH Web 使用本机地址监听,Nginx 对外提供访问入口。这样可以避免直接让 DSH Web 暴露公网监听地址,同时可以由 Nginx 处理域名、WebSocket 和请求体大小等配置。
安装 dsh-clawbot 前应该做什么?
先确保 DSH 本身可以正常启动,然后备份 Profile 的 package.json 和 lock 文件,再执行:
dsh plugin --profile web add github:tangwenhao616-netizen/dsh-clawbot
安装完成后检查 package.json 和 pnpm list --depth 1,确认旧的 deepseek-harness-weixin 没有重新进入 Profile。
