用 Wails 写一个跨平台进程管理器

我机器上常年跑着几个自建服务:后端接口、队列消费者,还有几个想起来才用的临时脚本。每次开机都得手动恢复一遍,漏掉哪个就得翻终端找。

现成的工具试过一些。systemd 只认 Linux,supervisor 得先把它自己跑起来,pm2 更偏 Node。我的机器横跨 Windows、macOS 和 Linux,配置语法各不相同,为一个临时脚本写 unit 文件也不划算。

ProcHub 就是这个时候写的:Wails + Go + Vue 3,Apache 2.0 开源,三个系统都有构建产物。

壳子为什么是 Wails

界面只是一张表加几个弹窗,用 Electron 每个窗口塞一个 Chromium 进去,几十 MB 内存起步,不太值。Wails 用系统 WebView,Windows 上是 WebView2,macOS 上是 WKWebView,Go 侧的方法直接暴露给前端,最后打出来是一个可执行文件。

不好的一面也真实存在。WebView2 在 Windows 上不是必装组件,碰到干净的机器得先引导装;调试工具链比 Electron 生涩不少。我还是选了它,原因是容易出问题的地方在子进程那边,那部分代码在 Go 里,图形界面挂了也能用命令行复现。

停一个进程比启一个麻烦

spawn 子进程几行代码就完了,麻烦的是收尾。点停止时先发终止信号,给它一个超时窗口自己释放端口、把缓冲写完,超时还没退再强杀。

Windows 上没有 POSIX 信号,只能走 Job Object 或者 taskkill。这块是三平台里最费劲的,也最容易在测试时漏掉。

重启次数和最近一条错误都跟着记录走,每次拉起覆盖一次,退出时记下时间和退出码,表里那几个数字才有意义。

重启策略给了三个:always、on_failure、never。

always 给常驻服务用,但必须配最大重试次数。我吃过这个亏:端口被占用,进程起来就退,退完又起,日志几秒钟涨几十兆。

on_failure 只在退出码非零时重试,正常退出就停。

一次性任务选 never,跑完记下退出码,不要被自动拉起。

应用启动时还有一个自动拉起的开关,和系统开机自启是两件事。一开始我把它俩做在一起,结果关掉开机自启,进程也不起来了,后来又拆开。

日志写文件,不留内存

最省事的做法是内存里留个环形缓冲,前端拉最近几千行。我一开始也是这么写的,后来改成按进程 id 写到数据目录,滚动保留,份数可配。

改的原因是一次排查。某个服务半夜启动失败,早上打开应用,它已经在重启策略下跑起来了,内存里只剩最近几分钟输出,前面那次退出的堆栈没了。写文件之后至少还能往前翻。

stdout 和 stderr 分开存。混在一起看容易误判。进程反复退出的时候先看 stderr,一般就是端口占用、工作目录写错、环境变量没传进去这几样。

代价是磁盘和 IO。实时日志走文件增量读取,界面关掉也能读,因为读的是文件,不是进程内存。

开机自启,三个系统各写一套

这块我没做抽象。macOS 写 LaunchAgent 的 plist,Linux 用 XDG Autostart 放 .desktop 文件,Windows 改注册表的 Run 项。三者的格式、生效时机、卸载方式都不一样,硬包一层只会让排查更难。

设置页里的主题、语言、开机自启、版本更新都在同一个页面,底下调用的还是这三套系统机制,没有自建守护进程。卸载干净是我想要的,代价是每上一个平台都要重新测。

命令行和界面是同一个二进制

不带参数启动是界面,带子命令走 CLI。

arduino 复制代码
prochub process list
prochub process start api
prochub logs api --tail 200
prochub logs api --follow
prochub config set theme dark
prochub status

logs --follow 相当于 tail -f,写部署脚本的时候省事。config get / set 动的主题、语言、开机自启和界面是同一份配置,不会出现命令行改完界面不认的情况。

有个约束得提前说:CLI 得应用开着才能用,它操作的是进程里的运行状态。要做成常驻守护进程,那就是另一个产品了,这版没往那走。

WSL 是个单独的开关

WSL 里的服务没法当本地进程拉起,只能通过 wsl 命令转发。所以命令行里多了一组:

csharp 复制代码
prochub wsl status
prochub wsl start
prochub wsl restart
prochub autostart-wsl on

autostart-wsl 我没有跟应用的开机自启绑在一起。WSL 拉起来要占内存和时间,不是每次开机都想让它跑。

WSL 里的输出和本地进程走同一条日志管线,界面上看不出区别,只是启动时多一步 wsl 转发。

构建要三台机器

需要 Go 1.23、Node 18 和 Wails CLI。Wails 的 GUI 产物不能交叉编译,三个平台各自构建:Windows 出 amd64,macOS 出 Intel 和 Apple Silicon 两份,Linux 出 amd64 和 arm64。

复制代码
wails build

本地开发用 wails dev,前端热更新,Go 侧改完自动重编译,比每次手动打包省事。

代价在发布流程上。一旦改动碰到平台相关的代码,三个系统都得跑一遍,所以那部分我一直尽量往薄里写。

还没做的

进程之间没有依赖顺序,A 依赖数据库得自己保证;日志只有本地,没有集中收集;也没法管别的机器上的进程。

依赖顺序这件事我犹豫过。真要做得先定一套声明语法,再处理循环依赖和启动失败的降级,工作量比现在整个工具都大。暂时先放着,等我自己被它绌到再说。

相关推荐
我不会起名字3225 小时前
消费回调固定 return nil:错误传播与 Kafka 位点提交的区别
后端·mongodb·kafka·go·im·故障排查
孔令飞9 小时前
把 Agent 搬进飞书:飞书 Agent 机器人演示
云原生·go·ai编程
码上踩坑1 天前
Claude Code 会话攒太多,我用 Go 写了个本地管理台
go
孔令飞1 天前
企业智能化升级:智能体平台设计和实现
云原生·go·ai编程
vipxieliang1 天前
Go GC 性能优化实践:从 GOGC、GOMEMLIMIT 到分配优化
后端·go
小满zs2 天前
Go语言第十八章(架构设计)
后端·go
FungLeo3 天前
成为全栈·Go 后端篇·三轮代码审阅之后:把可读性和证据做成工程规则
golang·go·ast·静态检查·成为全栈·代码审阅
我不会起名字3223 天前
Redis 从 0 到 1(二十八):Redis 抖一下,重试把它打挂了——超时/重试/熔断的 Go 封装
redis·go·超时·熔断·重试
xyzzklk4 天前
变相解决华为Mate 8锁屏后无法建立入站网络连接
linux·运维·服务器·网络·华为·go·安卓