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.DE​EPSEEK_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。