DeepSeek Harness systemd 部署:配置 + 7 个坑
dsh 是 DeepSeek 开源的编码 Agent,给它一句话任务,它自己读文件、跑命令、改代码。平时 npx @deepseek-ai/dsh web 一行就能跑起来,但这么跑有个毛病:服务活在你的终端里,SSH 一断就没了,服务器重启还得手动再敲一遍。
这篇把它挂成 Ubuntu 的 systemd 服务:开机自启、崩了自动拉起、关掉终端照常跑。官方文档没写部署这一章,下面的配置是我照着它的 CLI behavior reference 和 systemd 的常规用法整理的,命令都能直接抄。
下文全按 Ubuntu 说。Windows 用户别硬套,WSL2 里照做,或者直接用桌面版(文末有链接)。
一、为什么是 systemd
挂后台的办法不止一种。nohup dsh web & 也能用,但那是放养:进程崩了没人管,服务器重启不会自己起来,找日志得去翻 nohup.out。tmux 类似,本质是把终端藏起来,会话断了照样歇菜。
systemd 是 Ubuntu 自带的进程管家,开机自动起、崩了自动拉,日志还统一收进 journalctl。装都不用装。
二、先装好 dsh,别用 npx 起服务
放进服务里的命令要稳,先全局装:
npm install -g @deepseek-ai/dsh
dsh -V
npx 那套留给临时试用。每次启动它都要先解析一遍包,还可能顺手拉个新版,服务哪天自己变了个样都不奇怪。全局装完是固定版本,systemd 起它就是起一个普通命令。
装完把 which dsh 的输出记下来,马上要用。用 nvm 管 Node 的注意:dsh 会落在 nvm 的版本目录里,路径跟着 Node 版本走,而 systemd 不读你的 .bashrc,PATH 里没有这些东西,ExecStart 必须写绝对路径。这是这篇的第一个坑。
三、unit 文件,重点就三行
systemd 的服务定义就是一个 .service 文件。建一个:
sudo nano /etc/systemd/system/dsh.service
内容如下。假设你建了个叫 dsh 的普通用户专门跑它,没有就用自己账号,路径照着改:
[Unit]
Description=DeepSeek Harness Web UI
After=network-online.target
Wants=network-online.target
[Service]
User=dsh
Environment=DSH_HOME=/home/dsh/.dsh
WorkingDirectory=/home/dsh/projects/demo
ExecStart=/usr/local/bin/dsh web --no-open
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
逐行讲重点。
-
WorkingDirectory 是全文件最要命的一行。 dsh 把启动时所在的目录当默认工作区根目录,systemd 的默认工作目录却是 /。不设这行,它的文件操作就撒在根目录上;你给项目写的 AGENTS.md 它也读不到——dsh 启动时只加载工作目录下这份文件。把它指到你的项目目录。
-
User 别用 root。dsh 是个能跑 shell 的 agent,官方默认权限预设是 workspace-write:写文件、跑命令被限制在会话工作区和系统临时目录里,但读文件、访问网络、看进程不设防。用普通用户跑,就算它越界,能碰的也只有这个用户的家目录。
-
Environment=DSH_HOME 这行管配置放哪。dsh 找凭据的顺序是:环境变量、
$DSH_HOME/.credentials.yaml、启动目录的 .env、$DSH_HOME/.env。你平时手动跑,Key 存在~/.dsh里;一换服务账号,$DSH_HOME 跟着新家目录走,之前配的 Key 就都不认了。最省事的解法:拿要跑服务的那个账号手动配一次。sudo -u dsh dsh web起一遍,浏览器里填 Key、选工作区,确认能跑任务,Ctrl+C 停掉,再交给 systemd。服务器上没浏览器的话,先看下一节开条隧道再配。 -
Restart=on-failure 配 RestartSec=5,崩了隔 5 秒拉起来。正常 stop 不会触发重启,因为 dsh 对 SIGTERM 做了优雅退出,收到信号会把手里的事收尾(最多等 5 秒)再退,systemctl stop 走的正是这条路,不用你特殊照顾。
末尾的 --no-open 别忘了。dsh web 起完喜欢帮你开浏览器,它只认 SSH 环境变量这两种情况会跳过;systemd 环境里它照样尝试,开不出来就往日志里吐一条诊断,服务本身不受影响。加了这参数,启动日志干净点。
四、起服务、开机自启、看日志
sudo systemctl daemon-reload
sudo systemctl enable --now dsh
systemctl status dsh
journalctl -u dsh -f
enable --now 一条顶两条:设开机自启,顺便立刻启动。status 里看到 active (running) 就成了。
日志不落在哪个文件里,全在 journalctl,-f 是跟踪。dsh 崩没崩、为什么崩,先看这里。
想换端口,在 ExecStart 末尾追加 --port 8080,改完 daemon-reload 再 restart。
五、远程访问:官方故意不给 0.0.0.0
服务跑起来了,你在自己电脑上开 http://服务器IP:3080,不通的。dsh web 默认只听 127.0.0.1:3080,想用 --host 0.0.0.0 放开,CLI 直接报参数错误。这不是功能没做完,文档原话写着 "intentionally does not support",故意的。
别绕过它。这个界面背后是一个能跑 shell 的 agent,挂上公网等于把服务器的终端敞开门。要用,走 SSH 隧道:
ssh -L 3080:127.0.0.1:3080 youruser@服务器IP
隧道开着,本地浏览器访问 http://127.0.0.1:3080,看到的就是服务器上那个 dsh。用完断开,入口就没了。
想走域名加 HTTPS 的正路(Nginx/Caddy 反代),官方也留了口:--trusted-host 参数能把你的域名加进 /api 的浏览器信任校验白名单。但 dsh 默认只拦写不拦读,一个 shell agent 直接挂公网我不建议,这篇也不展开。
六、避坑清单:7 个坑对号入座
- WorkingDirectory 不设,默认工作区就是 systemd 给的 /,文件操作撒在根目录,AGENTS.md 也读不到。
- 换了运行账号 Key 就"消失":凭据按 环境变量 →
$DSH_HOME/.credentials.yaml→ 启动目录 .env →$DSH_HOME/.env的顺序找,$DSH_HOME 默认跟着账号家目录走。 - nvm 装的 Node,ExecStart 必须写绝对路径,systemd 不读你的 shell 配置;nvm 一换 Node 版本路径就变,服务机上要么用系统包管理器装 Node,要么认了这个理。
- 别用 npx 起服务,每次启动重新解析包,还可能拉新版。
--host 0.0.0.0直接报参数错误,官方故意的,远程访问走 SSH 隧道。- 忘了加
--no-open,启动日志里会多一条浏览器打开失败的诊断,无害但碍眼。 - dsh 还在 developer preview,升级可能带破坏性变更。升级前
dsh -V记下版本,升完systemctl restart dsh,不对劲就npm install -g @deepseek-ai/dsh@旧版本号降回去。
最后
到这一步,dsh 就是个正经的系统服务了:开机自己起,崩了自己拉,日志在 journalctl 里,终端关不关无所谓。服务器重启完你什么都不用做,开条隧道就能接着用。
不想要常驻界面的还有另一条路:dsh --profile headless "任务" 跑完就退、不占端口,配个 systemd timer 定时触发,就是一台无人值守的任务机。这个玩法值得单独写一篇。这个号一直在写 AI 编程工具的实战用法,dsh 系列后面每出一篇都会推到你的订阅里,想接着用的关注一下。

浙公网安备 33010602011771号