# dsh-restart-button

[English](README.md) | 中文

侧边栏**一键重启 DSH** 插件:一个图标按钮,固定在"收起侧边栏"折叠按钮的左边。
点击后 `POST /dsh-restart`,host 优雅关闭当前 `dsh --profile web` 进程、用原命令行
在后台重新拉起,服务恢复后页面自动刷新。

## 功能

- 纯图标侧边栏按钮(标准刷新图标),鼠标悬浮时显示文字,与其他侧边栏按钮一致。
- 固定在"收起侧边栏"按钮左边;侧边栏展开/收起(窄条)、拖宽、窗口缩放时自动跟随。
- 优雅重启:`SIGTERM` 优先(dsh 会正常落盘会话),超时升级 `SIGKILL`,等待端口释放后
  用**完全相同的命令行**在后台重新拉起,日志写在 `/tmp/dsh-restart-button.log`。
- **环境变量随重启保留**:重启通过你的登录 shell 执行,先 source shell 的 rc 文件,因此
  你写在 rc 里的**所有**变量(GitHub token、其他任何 MCP server 的 token、代理、PATH 覆盖等)
  都会传给新进程——而不是只继承 dsh 启动时的旧快照。当前进程环境(`DSH_HOME` 等)照常
  继承,最终是两者的并集。
- 自动刷新:kill 之后每秒探测一次服务(最多 60 秒),新服务器应答后强制刷新页面。
- 安全围栏:`/dsh-restart` 只接受环回地址且 `Origin`(若有)与服务器自身 `Host` 匹配的
  请求,否则返回 403。

## 安装

```sh
# 把已发布的 bundle 装进 web profile
dsh plugin --profile web add dsh-restart-button
# 重启一次 dsh 使新 bundle 生效
dsh web
```

本地开发(从 checkout 安装):

```sh
dsh plugin --profile web add link:/绝对路径/dsh-restart-button
dsh web
```

按钮出现在侧边栏顶部、"收起侧边栏"折叠按钮的正左边。

## 工作原理

- **Host 半**(`lib/host.js`):在 harness web server 上注册精确路由 `POST /dsh-restart`。
  收到请求后把一段重启脚本写到临时文件,并以完全分离的方式拉起(`detached: true`、
  `stdio: ignore`、`unref()`),保证脚本在本进程死亡后仍能继续。脚本依次:
  1. sleep 2 秒(先让 HTTP 响应到达浏览器);
  2. 用 `lsof -nP -tiTCP:<port> -sTCP:LISTEN` 找到服务器实际端口(从
     `ctx.webServer` 动态读取,不硬编码)上 **LISTEN** 的 PID
     (只杀服务器,不会误杀浏览器的连接);
  3. 优雅关闭进程并等待端口释放;
  4. 通过用户的登录 shell(`$SHELL`)重新拉起同一命令行,先 source `~/.zshrc` /
     `~/.bash_profile`——见上面"环境变量随重启保留"。
- **Client 半**(`lib/client.js`):`window.__ModuleLoader__.load` 打包格式,注册
  `sidebar.footer.action` 插槽。按钮用 `position: fixed` 固定,并在窗口缩放/侧边栏状态
  变化时重新测量折叠按钮的位置;侧边栏收起成窄条时折叠按钮在最左边,"左边"放不下,
  按钮自动落到它的右边。POST 成功后每秒轮询 `/`,服务恢复即 `window.location.reload()`。

## 注意事项

- 重启后的进程是**后台(detached)运行**的,原来的终端会话已经结束。手动停止:
  `lsof -nP -tiTCP:<port> -sTCP:LISTEN` → `kill <pid>`;或者干脆自己在终端里跑
  `dsh web`。
- host 需要 `lsof`(macOS/Linux 默认自带)。
- 当无法从旧进程恢复命令行时,回退默认用 `dsh --profile web` 启动。

## License

MIT
