用 dsh(DeepSeek Harness)本地 Web GUI 调试 Agent 时,最扫兴的莫过于:终端窗口一关,服务跟着退出,页面从此打不开。这篇文章先用一分钟讲清"为什么进程会随终端关闭而死",再给出一个只需配置一次、日常只敲两个 4 字符命令的完整方案——dshd 后台启动、dsht 彻底停止,从此关终端不慌,重启不丢会话。
dsh 是一款在本地运行的 AI Harness 工具(npm 包 @deepseek-ai/dsh),它自带一个 Web GUI,默认地址是:
|
1 |
http://127.0.0.1:3080 |
很多人(包括我)第一次都是这样启动的:
|
1 |
dsh web |
然后打开浏览器开始干活。干到一半想合上电脑或者清理终端窗口,结果终端一关,Web 也打不开了——因为刚才那条命令是"前台"跑在终端里的。
在类 Unix 系统(macOS/Linux)里,终端窗口关闭时,shell 退出并会给自己的子进程发送一个 SIGHUP(挂断)信号:
要让服务不随终端退出,思路只有一个:让进程脱离终端会话。常见手段:
| 手段 | 特点 |
|---|---|
| nohup … & | 忽略 SIGHUP,进程在后台跑,最简单 |
| tmux / screen | 独立会话,之后还能 attach 回去看 |
| launchd / systemd | 做成系统服务,可开机自启(最重) |
判断一个进程是否已经"自由",有一个很直观的办法——看它的父进程(PPID):
|
1 |
ps -o pid=,ppid=,command= -p <PID> |
PPID 是 1(被 init/launchd 收养)说明已脱离终端;PPID 还是某个 shell,说明它仍挂在终端下,关终端会一起死。
下面这套配置只做一次:创建一个启动脚本 + 在 ~/.zshrc 里定义两个命令。日常你只需要记 dshd 和 dsht。
代码语言:bash
AI代码解释
|
1 |
dsh --version || npm i -g @deepseek-ai/dsh |
提示:如果之前用 npx 临时跑过 dsh,全局安装后建议清理一下 npx 缓存里的旧副本(~/.npm/_npx 下对应目录),避免"一份程序两处存放"的困惑。数据不受影响——dsh 的用户数据统一放在 ~/.dsh。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
#!/usr/bin/env bash # ============================================================= # start-dsh.sh —— 以"脱离终端"方式启动 dsh Web GUI # 关闭启动终端后 Web 仍可访问 (http://127.0.0.1:3080) # # 用法: # bash ~/start-dsh.sh 默认模式: nohup 后台运行(推荐) # bash ~/start-dsh.sh --tmux 改用 tmux 会话运行 # DSH_PORT=8080 bash ~/start-dsh.sh 自定义端口(需与 dsh 配置一致) # # 依赖: dsh(全局安装); --tmux 模式另需 tmux # ============================================================= set -u PORT="${DSH_PORT:-3080}" LOG_DIR="${HOME}/.dsh/logs" LOG_FILE="${LOG_DIR}/web.log" MODE="${1:-nohup}" if [ "${MODE}" = "--tmux" ]; then MODE=tmux shift else MODE=nohup fi # ---- 定位 dsh 可执行文件(优先 ~/.local/bin 下的全局安装) ---- DSH_BIN="${DSH_BIN:-}" if [ -z "${DSH_BIN}" ] && [ -x "${HOME}/.local/bin/dsh" ]; then DSH_BIN="${HOME}/.local/bin/dsh" fi if [ -z "${DSH_BIN}" ] && command -v dsh >/dev/null 2>&1; then DSH_BIN="$(command -v dsh)" fi if [ -z "${DSH_BIN}" ]; then echo "? 未找到 dsh,请先全局安装: npm i -g @deepseek-ai/dsh" >&2 exit 1 fi # ---- 已在运行则只提示,避免重复实例 ---- if lsof -nP -iTCP:"${PORT}" -sTCP:LISTEN >/dev/null 2>&1; then echo "? dsh 已在运行: http://127.0.0.1:${PORT}" [ -f "${LOG_FILE}" ] && echo " 日志: tail -f ${LOG_FILE}" exit 0 fi mkdir -p "${LOG_DIR}" echo "→ 启动 dsh (${DSH_BIN}) 端口 ${PORT},模式: ${MODE}" if [ "${MODE}" = "tmux" ]; then if ! command -v tmux >/dev/null 2>&1; then echo "? 未安装 tmux,可先安装: brew install tmux" >&2 exit 1 fi tmux new-session -d -s dsh "exec ${DSH_BIN} web $* >'${LOG_FILE}' 2>&1" echo "? 已在 tmux 会话 [dsh] 中启动" echo " 返回查看: tmux attach -t dsh (脱离: Ctrl+B 然后 D)" else nohup "${DSH_BIN}" web "$@" >"${LOG_FILE}" 2>&1 & echo "? 已在后台启动 (PID $!)" fi echo " 打开地址: http://127.0.0.1:${PORT}" echo " 查看日志: tail -f ${LOG_FILE}" |
|
1 2 |
chmod +x ~/start-dsh.sh bash -n ~/start-dsh.sh && echo "语法 OK" |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# dshd: 后台启动/确认 dsh web(关闭终端不中断),由 ~/start-dsh.sh 实现 alias dshd='bash ~/start-dsh.sh' # dsht: 彻底关闭 dsh web(按 3080 端口找进程) dsht() { local p p=$(lsof -tiTCP:3080 -sTCP:LISTEN 2>/dev/null) if [ -n "$p" ]; then kill $p && echo "? 已停止 dsh (PID: $p)" else echo "dsh 未在运行" fi } |
|
1 |
source ~/.zshrc # 新开的终端不需要,自动加载 |
|
1 |
dshd |
第一次会看到:
|
1 2 3 4 |
→ 启动 dsh (/Users/xxx/.local/bin/dsh) 端口 3080,模式: nohup ? 已在后台启动 (PID 12345) 打开地址: http://127.0.0.1:3080 查看日志: tail -f ~/.dsh/logs/web.log |
如果它已经在运行,则只会提示地址并退出——天然防重复启动。
启动用的终端窗口直接关掉,然后新开一个终端验证:
|
1 2 |
dshd # 输出: ? dsh 已在运行: http://127.0.0.1:3080 |
或者不带任何依赖地验证:
|
1 |
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:3080 # 200 即存活 |
|
1 2 |
dsht # 输出: ? 已停止 dsh (PID: 12345) |
没在运行时执行 dsht,会友好地提示 dsh 未在运行,而不是报一堆 kill 用法错误。
|
1 |
tail -f ~/.dsh/logs/web.log |
这段写给想"知其所以然"的同学。
Q1:我现在的 dsh 是前台跑的,怎么平滑切换到后台模式?
先在前台那个终端按 Ctrl+C 停掉旧进程(页面会短暂断开,正常),再执行 dshd,最后刷新 http://127.0.0.1:3080。历史会话不会丢。
Q2:想换端口怎么办?
DSH_PORT=8080 dshd。注意端口需与 dsh 自身配置保持一致;dsht 默认按 3080 找进程,如换端口需要同步修改。
Q3:电脑重启后呢?
进程会随关机结束,开机后执行一次 dshd 即可。想做到开机自启,可以把它包成 launchd(macOS)或 systemd service(Linux),这是另一篇文章的话题。
Q4:关终端后网页打不开,怎么排查?
依次检查:① dshd 是否提示"已在运行";② lsof -nP -iTCP:3080 -sTCP:LISTEN 是否有输出;③ tail -n 50 ~/.dsh/logs/web.log 看启动报错;④ 浏览器是否走了代理导致访问不到 127.0.0.1。
| 场景 | 命令 |
|---|---|
| 后台启动 / 确认在运行 | dshd |
| 彻底关闭 | dsht |
| 看日志 | tail -f ~/.dsh/logs/web.log |
三个避坑提醒,写代码和运维时都用得上:
配置一次,之后每次只需 dshd 四个字符——把时间留给真正的问题,而不是和终端窗口搏斗。