前言¶
DeepSeek Harness(dsh)的 web 宿主常由 systemd 用户级 unit 拉起。改插件、换配置后,需要在设置里一键重启 web,而不是手敲 systemctl 或杀进程后无人拉起。
不少重启类插件会派 detached helper 在宿主退出后再启动新进程。宿主与 helper 同属一个 cgroup 时,systemd 默认 KillMode=control-group 会在停服时清掉 cgroup 内所有进程,helper 一并结束,重启变成「进程没了、没人拉起来」。部分同类插件在检测到 systemd 监管时会直接拒绝(实测返回 403)。
下面介绍维护者 534056668 的客户端插件 dsh-systemd-restart:在 设置 → 通用 增加一行「重启 dsh web」,不派分离子进程,由宿主自己优雅退出,交给 unit 的 Restart=always 按原 ExecStart 重新拉起。
这是什么¶
- 名称:dsh-systemd-restart(GitHub:
534056668/dsh-systemd-restart) - 分类:客户端(DeepSeek Harness 插件目录归类为客户端)
- 许可:MIT
- 版本(package.json):0.1.0
- 引擎要求:Node.js >= 20;dsh >= 0.2.0-rc.1;需 dsh 0.2.x 的 web profile
一句话:面向 由 systemd 用户级 unit 托管、且配置了 Restart=always 的 dsh web 宿主,提供设置页重启入口,并配套 loopback 围栏与 boot 标记,避免 detached helper 与 cgroup 冲突。
重启机制与常见做法的差异¶
进程无法「替换自己」,重启一定是:结束旧进程、再启动新进程。本插件走 systemd 预期路径:
- 按钮 → 围栏校验 → 先回 202 → 写 boot 标记 → 宿主给自己发 SIGTERM
- dsh 核心优雅收尾(dispose 插件树、会话落盘、关 HTTP、
exit 0) - systemd 按
Restart=always用原ExecStart拉起 - 新进程读 boot 标记,算出停机时长写回设置行
因此:零 systemctl 调用(不需要 polkit / DBus)、零端口竞态(没有第二个进程抢 3080)。与 dsh-simple-restart、dshmarket 等依赖 detached helper 且在 KillMode=control-group 下易失败的做法形成对比;README 亦提到生态里 @goodandready/dsh-smart-restart(默认 restartMode: kill)、dsh-restart-handoff、@gehennawu/dsh-service 等同类「退出由服务管理器拉起」思路。
核心功能¶
设置页交互¶
路径:设置 → 通用 / General → 重启 dsh web。
- 第一次点击为武装(
确认重启?,5 秒后自动取消),第二次点击执行。 - 提示行说明本机将如何重启(例如 unit 名、
Restart=always说明),以及上次重启停机时长。 - 页面断开后后台轮询 boot id;新进程就绪后自动刷新;120 秒内未起来则停止轮询并给出日志位置。
不满足前置条件时,按钮所在行会写清拒绝原因,而不是假装可用。
HTTP 接口¶
| 路由 | 方法 | 作用 |
|---|---|---|
/dsh-systemd-restart/api/status |
GET | 只读:boot id、pid、探测到的 unit 与 Restart=、将采用的方式及理由、上次重启记录、循环守卫计数 |
/dsh-systemd-restart/api/restart |
POST | 执行重启;先过围栏 |
POST /api/restart 围栏(摘要):非 POST → 405;非 loopback → 403;带 forwarded / x-forwarded-for / x-real-ip → 403;缺 Origin 或 Origin 的 host ≠ Host → 403;mode 为 off → 403;已有重启排程 → 409;超出循环守卫 → 429。通过后返回 202 并附 boot.id。
配置(cordis.patch.yml)¶
在 profile 的 cordis.patch.yml 中用 - id: systemd-restart + config: 覆盖;注意为整体替换,非逐键合并。
| 键 | 类型 | 默认 | 含义 |
|---|---|---|---|
mode |
auto / kill / systemctl / off |
auto |
重启方式;auto 读 unit 的 Restart= |
delayMs |
number | 1500 |
回 202 后延迟再退出,给浏览器收包时间 |
unit |
string | '' |
显式 systemd unit;空则从 /proc/self/cgroup 探测 |
stateFile |
string | '' |
状态文件;默认 $DSH_HOME/storages/dsh-systemd-restart/state.json |
maxRestarts |
number | 3 |
窗口内允许重启次数,超出拒绝 |
windowMs |
number | 600000 |
上述窗口(毫秒) |
auto 判定:Restart=always → kill;否则若有 systemd-run 且为用户级 unit → systemctl(独立 cgroup 里跑 systemctl --user restart);否则 → off 并在 UI 说明原因。
安装与启用¶
前提:dsh 0.2.x web profile;宿主由 systemd 用户级 unit 托管,且该 unit Restart=always。
官方 README 安装步骤:
git clone https://github.com/534056668/dsh-systemd-restart.git
cd dsh-systemd-restart
bash install.sh
install.sh 会先备份 profile 的 package.json / pnpm-lock.yaml,再执行 dsh plugin --profile web add "$(pwd)",并打印装配状态。等价手动安装:
cd ~/.dsh/profiles/web && dsh plugin --profile web add /path/to/dsh-systemd-restart
装完后刷新页面:宿主侧可随 patchReload: live 生效,客户端行在页面加载时组装。
插件以当前 dsh 进程权限运行;安装前应阅读仓库源码与 MIT 许可证,确认符合本机部署策略。
典型用法¶
- 确认 unit 为用户级且
Restart=always(不满足则设置行会说明)。 - 打开 设置 → 通用,阅读该行关于重启方式与上次停机时长的说明。
- 第一次点击「重启 dsh web」进入确认态,5 秒内再次点击执行;等待 202 与页面断连后的自动刷新。
自检状态(需登录,未登录 GET status 为 401):
curl -s http://127.0.0.1:3080/dsh-systemd-restart/api/status
离线测试(不真重启宿主):
npm test
README 注明 46 项离线断言;合法 POST 且非 off 的真实退出路径被测试刻意排除。
装到真实环境后可按 dsh 插件 SOP「闸⑤」验收(示例 unit 名 dsh-web,日志路径以本机为准):
OFF=$(wc -c < ~/.dsh/web-stdout.log)
systemctl --user restart dsh-web
sleep 20
tail -c +$((OFF+1)) ~/.dsh/web-stdout.log | grep -nE "did not activate|failed to import"
curl -s http://127.0.0.1:3080/dsh-systemd-restart/api/status
适用场景与限制¶
适合:Linux 上、dsh web 由 systemd 用户级 unit 托管、Restart=always,希望在设置里安全重启且避免 detached helper 与 cgroup 连坐的场景。
不适合或会直接拒绝:
- 手敲
dsh web起的进程:退出后无人拉起 → 拒绝(避免「杀了回不来」)。 - 系统级 unit(
/etc/systemd/system/...):需要 root/polkit,插件会拒绝。 - 非 Linux:端口与 unit 探测不可用,
mode判为off。 - 点击重启会立即掐断正在进行的回合,不等待会话空闲。
DeepSeek Harness 生态理念是「一切皆插件」;社区目录站点与 DeepSeek / 幻方无官方从属关系,本文仅作插件能力说明。
结尾¶
dsh-systemd-restart 把「设置里一键重启 web」做成与 systemd Restart=always 同一条路:宿主优雅退出,由 unit 拉起新进程,并用 loopback 围栏与 boot 标记保证可观测、可拒绝误用。目录页与源码: