dsh-systemd-restart:在 systemd 托管下用「优雅退出 + Restart=always」重启 dsh web

前言

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 预期路径:

  1. 按钮 → 围栏校验 → 先回 202 → 写 boot 标记 → 宿主给自己发 SIGTERM
  2. dsh 核心优雅收尾(dispose 插件树、会话落盘、关 HTTP、exit 0)
  3. systemd 按 Restart=always 用原 ExecStart 拉起
  4. 新进程读 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 许可证,确认符合本机部署策略。

典型用法

  1. 确认 unit 为用户级且 Restart=always(不满足则设置行会说明)。
  2. 打开 设置 → 通用,阅读该行关于重启方式与上次停机时长的说明。
  3. 第一次点击「重启 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 标记保证可观测、可拒绝误用。目录页与源码:

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜