【免费下载链接】onWatch
Track AI API quotas across Synthetic, Z.ai, Anthropic (Claude Code), Codex, GitHub Copilot & Antigravity in real time. Lightweight background daemon (<50MB RAM), SQLite storage, Material Design 3 dashboard. Zero telemetry.
onWatch 是一款轻量级的 AI API 额度监控守护进程(内存占用 <50MB),实时追踪 Claude Code、Codex、GitHub Copilot 等 AI 服务的配额。本文将拆解 onWatch 的自我更新机制:只需运行一条onwatch update命令,程序就能完成版本检查、安全下载、二进制替换,并借由 systemd 或 launchd 的生命周期管理自动重启,全程零手动干预。
一键自我更新:三步完成 Check → Apply → Restart
自我更新的核心逻辑集中在 internal/update/update.go,整个流程像一条流水线:
| 阶段 | 动作 | 关键设计 |
|---|---|---|
| Check | 查询最新版本 | 结果缓存 1 小时,避免频繁请求 |
| Apply | 下载并替换二进制 | 魔数校验 + 原子替换 |
| Restart | 交由服务管理器重启 | 自动识别 systemd / launchd |
版本检查:三级回退策略,告别 403 限流
Check()并不傻乎乎地只打 GitHub API 一个接口。在 update.go 的 fetchLatestTag() 中设计了三道防线:
- 首选:
releases/latest接口,遇 5xx 自动重试 3 次; - 回退一:
github.com/releases/latest网页 302 重定向 —— 它不走 API 域名,天然绕开匿名调用每小时 60 次的限流(这是 NAT 共享出口 IP 场景下 403 的救命方案); - 回退二:releases 列表接口,仅在前一次失败是瞬时性错误时尝试。
另外,若环境中设置了GITHUB_TOKEN,会自动带上 Bearer 令牌,把限流额度从 60 次/小时提升到 5000 次/小时。
安全替换:先验魔数,再原子换二进制
下载环节有两个细节值得新手学习:
- 魔数校验:在 validateBinary() 中,文件前 4 字节必须是 ELF(Linux)、Mach-O(macOS)或 PE(Windows)的执行头,防止把 HTML 错误页当成二进制写入;
- 原子替换:新文件先下到同目录的
onwatch.tmp.*临时文件,chmod 0755后再替换。Unix 上采用「删除旧文件 → rename 新文件」策略——内核会保住正在运行的 inode,直到进程退出;Windows 上退化为「备份-rename」双步策略,失败还能自动回滚。
重启策略:把控制权交还给系统
替换完成后,Restart()并不自己拉起进程,而是按环境三选一:
systemd 环境 → systemctl restart <service> (由 systemd 全权接管) launchd 环境 → launchctl kickstart -k <label> (由 launchd 全权接管) 独立运行 → 直接 spawn 新进程 + PID 文件接管这个「不自作聪明」的设计是整篇的精髓:谁拥有生命周期,谁负责重启,避免更新后产生游离的野进程。
systemd 生命周期管理(Linux):Restart=always 的妙用
一键生成 unit 文件
安装脚本 install.sh 会根据是否使用 root 创建两种 unit:
- 系统级:
/etc/systemd/system/onwatch.service,WantedBy=multi-user.target - 用户级:
~/.config/systemd/user/onwatch.service,并顺手开启loginctl enable-linger,让服务在注销后依然存活
unit 文件的关键参数:
[Service] Type=simple ExecStart=${BIN_DIR}/onwatch --debug Restart=always RestartSec=5自我更新的点睛之笔:MigrateSystemdUnit 自动修复
这是最容易忽略、也最巧妙的部分。旧版本安装的 unit 可能写着Restart=on-failure,而自我更新后旧进程以退出码 0 结束——on-failure策略下 systemd不会拉起新二进制,更新就「静默失效」了。
onWatch 的解法在 MigrateSystemdUnit():
- 每次启动(包括更新后的新进程)都会检查 unit 文件;
- 发现
Restart=on-failure→ 自动改写为Restart=always; - 同步把
RestartSec=10收紧为RestartSec=5,加速重启; - 立即执行
systemctl daemon-reload让新策略生效。
时序上还有个小心机:更新后的子进程先完成 unit 修复和 daemon-reload,再去杀掉父进程(见 main.go 启动流程)。此时 systemd 追踪的还是旧 PID,父进程一死,systemd 立刻以新的Restart=always策略拉起新二进制——即使旧版本的 Restart() 代码有 bug,更新依然能完成闭环。
服务名检测同样优雅:通过INVOCATION_ID环境变量判断是否运行在 systemd 下,再解析/proc/self/cgroup中的0::/system.slice/onwatch.service得到真实 unit 名,失败则回退到onwatch.service(见 DetectServiceName())。
launchd 生命周期管理(macOS):KeepAlive 的取舍
LaunchAgent 的生成与加载
macOS 侧由 internal/service/launchd.go 负责。onwatch service install会在~/Library/LaunchAgents/dev.onllm.onwatch.plist写入任务描述,然后依次执行launchctl bootout → enable → bootstrap → kickstart(见 Install()),其中enable必须先于bootstrap,否则历史上被disable过的 label 会直接加载失败。
plist 核心字段:
<key>RunAtLoad</key><true/> <key>KeepAlive</key> <dict><key>SuccessfulExit</key><false/></dict> <key>ThrottleInterval</key><integer>10</integer>为什么 KeepAlive 不直接写 true?
这是一个很值得学习的取舍(见 RenderPlist() 注释):
- 若
KeepAlive = true,连用户手动onwatch stop都会被 launchd 秒拉回来,「关不掉」; - 设为
{SuccessfulExit: false}后:退出码 0(主动停止)→ 保持停止;异常崩溃 → 自动重启。
配合ThrottleInterval 10防止崩溃风暴刷爆 CPU,这就是「优雅停止」与「崩溃自愈」共存的答案。
更新时的重启走launchctl kickstart -k(强制杀死并重新拉起,见 Restart()),整个过程 launchd 始终是唯一进程属主,agent 不丢失、自启属性不丢失。
用户视角:只需要两条命令
对普通用户来说,以上机制完全透明,日常只需要:
onwatch update # 一键自查 + 下载 + 替换 + 服务管理器重启 onwatch service install # 注册开机自启(macOS/Windows) onwatch service status # 查看自启状态 onwatch service uninstall # 移除自启onwatch update的执行路径在 runUpdate():更新成功后还会自动提示是否开启开机自启(offerAutostart),避免「更新了但重启电脑后服务没起来」的经典翻车场景。服务管理命令实现见 cmd/onwatch/service_cmd.go。
相关文件速查
| 模块 | 路径 | 职责 |
|---|---|---|
| 自我更新器 | internal/update/update.go | 版本检查、下载、替换、重启 |
| launchd 管理 | internal/service/launchd.go | macOS LaunchAgent 全生命周期 |
| 服务 CLI | cmd/onwatch/service_cmd.go | onwatch service子命令、更新后重启编排 |
| 安装脚本 | install.sh | Linux systemd unit 生成与激活 |
| 自更新测试 | internal/update/update_test.go | 各分支路径的单元测试 |
小结
onWatch 的自我更新可以总结为三层保险:
- 检查层:三级端点回退 + 1 小时缓存 + Token 提额,版本查询几乎不会失败;
- 替换层:魔数校验 + 临时文件 + 原子 rename,任何时刻中断都不会留下损坏的二进制;
- 重启层:不自己 fork,而是把重启交给 systemd 或 launchd,并用
MigrateSystemdUnit提前修正Restart=always策略,确保「旧进程一死,新进程必生」。
这套「检查—替换—委托重启」的模式,对任何需要长期驻留的命令行工具都是很好的参考范本。
【免费下载链接】onWatch
Track AI API quotas across Synthetic, Z.ai, Anthropic (Claude Code), Codex, GitHub Copilot & Antigravity in real time. Lightweight background daemon (<50MB RAM), SQLite storage, Material Design 3 dashboard. Zero telemetry.
相关推荐
鸣潮自动化辅助工具 ok-ww 完整指南:把 2 小时日常压缩到 15 分钟
鸣潮自动化辅助工具 ok ww 完整指南:把 2 小时日常压缩到 15 分钟 周五晚上八点,打开游戏,任务面板上是那份熟悉的清单:体力、日常、周本、声骸,一项项
RPA计算机视觉Balena Etcher 完整指南:三步烧录带校验的 SD 卡与 U 盘启动盘
Balena Etcher 完整指南:三步烧录带校验的 SD 卡与 U 盘启动盘 Balena Etcher 是一个跨平台的镜像烧录工具,把 ISO、IMG 等
桌面应用嵌入式开发工具rkt 与 systemd 集成实践指南:用 systemd 管理 rkt 容器全生命周期
rkt 与 systemd 集成实践指南:用 systemd 管理 rkt 容器全生命周期 rkt 从设计之初就是为配合 systemd 等 init 系统而生
容器运行时云原生网络
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考