运维自动化

开机即运行 -- LaunchAgent 自启、一键切换、巡检脚本、NeoWatch 监控


你将学到什么

  • 用 macOS LaunchAgent 实现所有 AI 服务开机自启和崩溃重启
  • 用统一脚本管理 10 个服务的启停和日志
  • 用 ai-switch 一键切换在线/离线/4bit/8bit 模式
  • 用巡检脚本定时检查全部端口、内存、磁盘、日志异常
  • 用 NeoWatch 仪表盘实时监控整个基础设施

前置条件

  • Mac Studio 已部署 MLX 推理引擎(参见 Mac Setup
  • 已安装 Claude Code、OpenClaw 等服务
  • 了解基本的终端操作

预计时间:20 分钟


一、为什么需要自动化

当你的 Mac Studio 上运行着 10 个 AI 服务时,手动管理就是灾难:

code
手动管理的一天:

06:00  机器意外重启(macOS 自动更新)
06:01  所有 AI 服务全部停止
06:30  你醒来发现 Telegram Bot 没回复
06:35  手动启动 mlx_lm.server... 等 2 分钟加载模型
06:37  手动启动 OpenClaw... 发现端口冲突
06:40  手动启动 NeoWatch... 忘记先启动 backend
06:43  手动启动 5 个 OpenClaw 实例... 顺序搞错
06:50  终于全部跑起来了
06:52  mlx_lm.server 崩溃了(Metal OOM)
06:52  KeepAlive 1 秒后重启 → 内存没释放 → 再次崩溃
06:52  崩溃循环开始...

自动化之后:

code
自动化管理的一天:

06:00  机器意外重启
06:01  LaunchAgent 自动按序启动所有服务(ThrottleInterval 防崩溃循环)
06:03  全部就绪
06:10  巡检脚本定时跑,确认 10 个端口全部健康
       你还在睡觉

三个支柱:开机自启 + 崩溃重启 + 定时巡检。


二、LaunchAgent 基础

macOS 的 LaunchAgent 是原生服务管理器,类似 Linux 的 systemd。每个服务对应一个 .plist 文件,放在 ~/Library/LaunchAgents/ 目录下。

plist 文件结构

以 AI 推理引擎为例:

code
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <!-- 服务唯一标识符 -->
  <key>Label</key>
  <string>ai.mlx-lm-server</string>
 
  <!-- 开机自动启动 -->
  <key>RunAtLoad</key>
  <true/>
 
  <!-- 崩溃后自动重启 -->
  <key>KeepAlive</key>
  <true/>
 
  <!-- 崩溃后最少等待 60 秒再重启 -->
  <key>ThrottleInterval</key>
  <integer>60</integer>
 
  <!-- 启动命令 -->
  <key>ProgramArguments</key>
  <array>
    <string>/opt/homebrew/bin/mlx_lm.server</string>
    <string>--model</string>
    <string>/Users/neo/models/MiniMax-M2.5-MLX-4bit</string>
    <string>--host</string>
    <string>0.0.0.0</string>
    <string>--port</string>
    <string>8000</string>
  </array>
 
  <!-- 环境变量 -->
  <key>EnvironmentVariables</key>
  <dict>
    <key>HOME</key>
    <string>/Users/neo</string>
    <key>PATH</key>
    <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/usr/sbin:/bin</string>
    <key>MLX_METAL_FAST_SYNCH</key>
    <string>1</string>
  </dict>
 
  <!-- 日志输出 -->
  <key>StandardOutPath</key>
  <string>/Users/neo/logs/mlx-lm-server.log</string>
  <key>StandardErrorPath</key>
  <string>/Users/neo/logs/mlx-lm-server.err.log</string>
</dict>
</plist>

关键参数解释

参数作用推荐值
Label服务唯一 ID,用于 launchctl 操作ai.xxx 命名空间
RunAtLoad开机(或 load 时)自动启动true
KeepAlive进程退出后自动重启true
ThrottleInterval崩溃后最少等待秒数60 秒以上(大模型必须)
EnvironmentVariables进程环境变量PATH 必须包含 /usr/sbin
WorkingDirectory进程工作目录按需设置
StandardOutPathstdout 日志文件~/logs/xxx.log
StandardErrorPathstderr 日志文件~/logs/xxx.err.log

ThrottleInterval 为什么必须 >= 60 秒? 大模型崩溃时,Metal GPU buffer 释放需要 10-30 秒。如果 ThrottleInterval 太短(比如默认 10 秒),新进程在内存还没释放完时就启动,立刻 OOM 再次崩溃,形成崩溃循环。60 秒给了足够的内存回收时间。

管理命令

code
# 注册并启动服务
launchctl load ~/Library/LaunchAgents/ai.mlx-lm-server.plist
 
# 停止并注销服务
launchctl unload ~/Library/LaunchAgents/ai.mlx-lm-server.plist
 
# 查看服务状态
launchctl list | grep ai.
 
# 强制重启(先停再启)
launchctl kickstart -k gui/$(id -u)/ai.mlx-lm-server
 
# 启用/禁用自启(不影响当前运行)
launchctl enable gui/$(id -u)/ai.mlx-lm-server
launchctl disable gui/$(id -u)/ai.mlx-lm-server

三、ai-services.sh 统一管理

10 个服务用 10 条 launchctl 命令太痛苦。ai-services.sh 脚本将所有操作统一到一个入口。

支持的命令

code
ai-status              # 查看所有服务状态(PID、端口、运行时长)
ai-start               # 启动所有服务
ai-start mlx           # 只启动 MLX 推理引擎
ai-stop                # 停止所有服务
ai-restart             # 重启所有服务(带安全等待逻辑)
ai-logs                # 查看最近日志
ai-logs openclaw       # 查看 OpenClaw 日志
ai-patrol              # 运行巡检脚本

Shell 别名配置

添加到 ~/.zshrc

code
alias ai-status='bash ~/scripts/ai-services.sh status'
alias ai-start='bash ~/scripts/ai-services.sh start'
alias ai-stop='bash ~/scripts/ai-services.sh stop'
alias ai-restart='bash ~/scripts/ai-services.sh restart'
alias ai-logs='bash ~/scripts/ai-services.sh logs'
alias ai-patrol='bash ~/scripts/neo-patrol.sh'
alias ai-switch='bash ~/scripts/ai-switch.sh'

ai-restart 的安全等待逻辑

重启大模型服务不是简单的"停了再开"。脚本内置了安全等待:

code
ai-restart mlx:

1. launchctl unload (停止服务 + 禁用 KeepAlive)
2. 轮询 pgrep 等待进程完全退出 (最长 120 秒)
3. 检查内存释放情况 (vm_stat)
4. launchctl load (重新启动)
5. 等待端口可达 (curl 健康检查)
6. 输出状态确认

为什么要等进程完全退出? Metal GPU buffer 的释放是异步的。kill 信号发出后,Python 进程开始清理,Metal buffer 逐步释放(10-30 秒)。如果不等完全释放就启动新进程,两份模型权重同时存在 = OOM。


四、ai-switch 模式切换

ai-switch 是最核心的运维工具。它不只是切换模型,而是联动整个基础设施。

四种模式

code
ai-switch status          # 查看当前模式
ai-switch online          # 切换到云端模式(停止本地推理引擎)
ai-switch local           # 切换到本地模式(默认 4-bit)
ai-switch local-4bit      # 本地 4-bit(速度优先)
ai-switch local-8bit      # 本地 8-bit(质量优先,需二次确认)

切换时的联动操作

一次 ai-switch local-4bit 会触发以下操作:

code
1. 安全检查
   ├─ 检查近 30 分钟 DiagnosticReports(GPU Hang 历史)
   └─ 检查内存余量(模型大小 + 30GB 安全边际)

2. 停止旧服务
   ├─ launchctl unload(停止 + 禁用 KeepAlive 重启)
   └─ 轮询等待进程完全退出

3. 更新配置
   ├─ plist 中的模型路径
   ├─ ~/.ai-mode 状态文件
   └─ OpenClaw primary model 配置

4. 启动新服务
   ├─ launchctl load
   └─ 等待端口健康

5. 验证
   └─ curl 测试推理引擎响应

安全检查机制

GPU Hang 历史检测:扫描 /Library/Logs/DiagnosticReports/ 中最近 30 分钟的报告。如果发现 GPU Hang 记录,拒绝切换到本地模式,提示先重启机器。

内存余量检查:计算目标模型所需内存 + 30GB 安全边际,与当前可用内存对比。不够就拒绝切换。

8-bit 二次确认:8-bit 模型运行时占 ~270GB(512GB 的 53%),留给系统的余量不多,GPU Hang 风险更高。切换前要求用户手动确认。


五、巡检脚本 (neo-patrol.sh)

巡检脚本是定时运行的"体检程序",覆盖全部 10 个端口和关键指标。

检查项目

code
neo-patrol.sh 巡检清单:

[端口检查] -- 10 个服务全部 curl 健康检查
  :8000   mlx_lm.server(推理引擎)
  :18789  OpenClaw 二宝(管家)
  :18790  OpenClaw 三宝(交易员)
  :18795  OpenClaw 六宝大脑
  :18796  OpenClaw 四宝大脑
  :3940   NeoWatch Backend
  :3939   NeoWatch Frontend
  :3000   Open WebUI (Docker)
  :3721   四宝引擎(量化策略)
  :6789   六宝引擎(预测市场)

[内存检查]
  - Wired 内存(模型占用)
  - Free + Inactive(可用余量)
  - Swap 使用量(应为 0)

[磁盘检查]
  - 可用空间百分比
  - ~/logs/ 目录大小

[日志检查]
  - 最近 24 小时错误日志扫描
  - 智能过滤:排除定时任务错误、可选服务白名单、非关键模式
  - 覆盖:mlx_lm、4 个 OpenClaw 实例、quant-agent、liubao

智能过滤

巡检脚本不会对每个 ERROR 都报警。内置了智能过滤逻辑:

  • 定时/可选服务白名单:某些服务不是 24x7 必须运行的,不在线不算异常
  • 24 小时错误窗口:只关注最近一天的错误
  • 非关键模式排除:网关超时(gateway timeout)等瞬态错误不计入

定时调度

通过 LaunchAgent 自动调度,每 2 小时运行一次:

code
<!-- ai.lobster-patrol.plist -->
<key>StartCalendarInterval</key>
<array>
  <!-- 偶数小时的 :10 分运行,一天 12 次 -->
  <dict>
    <key>Hour</key><integer>0</integer>
    <key>Minute</key><integer>10</integer>
  </dict>
  <dict>
    <key>Hour</key><integer>2</integer>
    <key>Minute</key><integer>10</integer>
  </dict>
  <!-- ... 4, 6, 8, 10, 12, 14, 16, 18, 20, 22 -->
</array>

巡检结果输出到日志文件,异常时通过 Telegram 告警推送。


六、NeoWatch 监控仪表盘

NeoWatch 是整个基础设施的"中控室",提供实时可视化监控。

仪表盘布局

code
┌─────────────────────────────────────────────────────────┐
│                     NeoWatch Dashboard                   │
│  ┌──── StatusBar(MLX 推理状态 + 系统资源) ─────────┐   │
│  └──────────────────────────────────────────────────┘   │
│                                                         │
│  Row 1:                                                 │
│  ┌─ Patrol ──┐  ┌─ Quant ───┐  ┌─ Liubao ──┐         │
│  │  (cyan)   │  │ (purple)  │  │ (orange)  │         │
│  │  巡检状态  │  │ 量化策略   │  │ 预测市场   │         │
│  └───────────┘  └───────────┘  └───────────┘         │
│                                                         │
│  Row 2:                                                 │
│  ┌─ OpenClaw ─┐  ┌─ Treasure ──────────────┐          │
│  │  (green)   │  │       (yellow)          │          │
│  │  AI 网关    │  │   宝藏洞察 (col-span-2)  │          │
│  └────────────┘  └─────────────────────────┘          │
│                                                         │
│  5 面板 5 色,运维优先设计                                 │
└─────────────────────────────────────────────────────────┘

核心功能

WebSocket 实时推送:所有数据通过 WebSocket 推送,不用刷新页面。推理速度、内存状态、服务健康状态实时更新。

GPU Watchdog(4 态有限状态机)

code
HEALTHY ──(tok/s 下降 >40%)──→ WARN
   ↑                              │
   └──(恢复)──────────────────────┘
                                  │
WARN ────(tok/s < 0.3)────→ CRITICAL
                                  │
CRITICAL ─(反复触发)────→ TAINTED
                                  │
TAINTED ──(手动 reset)────→ HEALTHY
状态含义颜色自动动作
HEALTHY推理正常绿色
WARN速度下降,可能是瞬态波动黄色观察
CRITICAL推理接近停滞红色Telegram 告警
TAINTED反复异常,可能 GPU Hang紫色需人工介入

当前阶段是 Alert-only。Watchdog 只观察和告警,不会自动重启任何服务。自动恢复功能(L1/L2)已实现但默认关闭,因为自动重启 MLX 服务有 GPU Hang 风险。

告警系统

  • SQLite 持久化告警记录
  • REST API 支持确认/批量确认
  • 告警去重:同一告警连续 3 次以上后抑制,恢复后重置

七、完整服务架构

部署完成后,你的 Mac Studio 上运行着 10 个服务:

服务端口LaunchAgent职责
mlx_lm.server8000ai.mlx-lm-serverMLX 推理引擎(核心)
OpenClaw 二宝18789ai.openclaw.gatewayAI 管家 + Telegram Bot
OpenClaw 三宝18790ai.openclaw.trader交易员 + Crypto 分析
OpenClaw 四宝大脑18796ai.openclaw.quant量化策略 AI 大脑
OpenClaw 六宝大脑18795ai.openclaw.liubao预测市场 AI 大脑
四宝引擎3721ai.quant-agent量化策略执行引擎
六宝引擎6789ai.liubao预测市场交易引擎
NeoWatch Backend3940ai.neowatch.backend系统监控 API
NeoWatch Frontend3939ai.neowatch.frontend监控仪表盘
Open WebUI3000Docker DesktopWeb 聊天界面

服务依赖关系

code
mlx_lm.server (:8000) ← 核心,其他服务依赖它做推理
    ↑
    ├── OpenClaw 二宝 (:18789)  ← 管家,心跳检查含三宝
    ├── OpenClaw 三宝 (:18790)  ← 交易员,15 分钟 crypto cron
    ├── OpenClaw 四宝 (:18796)  ← 量化大脑
    ├── OpenClaw 六宝 (:18795)  ← 预测市场大脑
    └── Open WebUI (:3000)      ← Web 聊天

四宝引擎 (:3721) ← 独立运行,push 通知到 Telegram
六宝引擎 (:6789) ← 独立运行,push 通知到 Telegram

NeoWatch Backend (:3940) ← 监控所有上述服务
    ↑
    └── NeoWatch Frontend (:3939) ← 等 Backend 就绪后启动

启动顺序

LaunchAgent 没有原生的启动顺序控制。我们通过 plist 中的启动脚本实现等待依赖:

code
<!-- NeoWatch Frontend 等待 Backend 就绪 -->
<key>ProgramArguments</key>
<array>
  <string>/bin/bash</string>
  <string>-c</string>
  <string>
    for i in $(seq 1 30); do
      curl -s http://localhost:3940/api/v1/health > /dev/null 2>&amp;1 &amp;&amp; break
      sleep 1
    done
    exec /opt/homebrew/bin/bun run dev
  </string>
</array>

循环 30 秒等 Backend 健康检查通过,然后 exec 替换当前进程启动前端。


八、运维铁律

从无数次事故中总结的三条铁律。违反任何一条都可能导致 GPU Hang 或数据丢失。

铁律一:绝不自行重启 MLX 推理引擎

code
错误做法:
  pkill mlx_lm.server && mlx_lm.server --model ...

正确做法:
  ai-restart mlx          # 通过脚本重启(带安全等待逻辑)
  ai-switch local-4bit    # 通过 ai-switch 切换(带内存检查)

手动重启极易导致:

  • 旧进程 Metal buffer 未释放 + 新进程加载模型 = 双倍内存 = OOM Panic
  • GPU Hang(Metal 驱动死锁,唯一修复方式是重启机器)
  • 崩溃循环(KeepAlive 不断重启崩溃的进程)

遇到推理异常,优先从配置、日志、代码层面排查。 确认必须重启时,使用 ai-restartai-switch,并确保你在机器旁边(万一需要物理重启)。

铁律二:所有变更通过运维 Agent 执行

架构变更涉及的文件远比你想象的多。一次"简单的"端口修改可能影响:

  1. LaunchAgent plist(服务配置)
  2. ai-switch.sh(模式切换逻辑)
  3. ai-services.sh(服务管理数组)
  4. OpenClaw config(模型路由)
  5. NeoWatch 采集器(监控目标)
  6. 巡检脚本(端口列表)
  7. 运维 Agent 配置(服务清单)
  8. 运维记忆(架构描述)
  9. 桌面笔记(运维记录)

遗漏任何一个 = 下次巡检时认知错误 = 误操作。

铁律三:架构变更须同步所有运维组件

这是铁律二的推论。每次变更后必须检查以下清单:

code
变更同步清单:

[ ] LaunchAgent plist 已更新
[ ] ai-switch.sh 已更新
[ ] ai-services.sh 已更新
[ ] OpenClaw 相关 config 已更新
[ ] NeoWatch 采集器已更新
[ ] 巡检脚本端口列表已更新
[ ] 运维 Agent 配置已更新
[ ] 运维记忆已更新
[ ] MEMORY.md 已记录
[ ] 桌面笔记已写入

九、常见问题

Q: 服务启动失败怎么排查?

code
# 1. 查看 launchd 状态(PID 和退出码)
launchctl list | grep ai.
 
# 2. 查看错误日志
tail -30 ~/logs/mlx-lm-server.err.log
 
# 3. 手动启动调试(绕过 LaunchAgent)
/opt/homebrew/bin/mlx_lm.server \
  --model ~/models/MiniMax-M2.5-MLX-4bit \
  --host 0.0.0.0 --port 8000

Q: ai-switch 和 KeepAlive 冲突吗?

不冲突。ai-switch 在停止服务时用 launchctl unload(而不是 kill),这会同时停止进程并禁用 KeepAlive 重启。切换完成后再 launchctl load 重新启用。

Q: 如何查看某个服务的实时日志?

code
# MLX 推理引擎
tail -f ~/logs/mlx-lm-server.err.log
 
# OpenClaw 二宝
tail -f ~/.openclaw/logs/gateway.err.log
 
# OpenClaw 三宝(trader profile)
tail -f ~/.openclaw-trader/logs/gateway.err.log
 
# NeoWatch
tail -f ~/logs/neowatch-backend.err.log

Q: 巡检报告在哪看?

code
# 最近一次巡检结果
cat ~/logs/neo-patrol.log
 
# 或通过 NeoWatch 仪表盘查看(Patrol 面板)
open http://localhost:3939

Q: 如何临时禁用某个服务的自启?

code
# 禁用自启(不影响当前运行状态)
launchctl disable gui/$(id -u)/ai.openclaw.trader
 
# 停止当前运行的实例
launchctl unload ~/Library/LaunchAgents/ai.openclaw.trader.plist
 
# 重新启用
launchctl enable gui/$(id -u)/ai.openclaw.trader
launchctl load ~/Library/LaunchAgents/ai.openclaw.trader.plist

Q: 旧的 plist 文件怎么处理?

架构迁移后废弃的 plist 必须重命名为 .disabled 后缀,而不是删除:

code
# 正确做法 -- 保留记录,防止 launchd 加载
mv ~/Library/LaunchAgents/ai.mlx-server.plist \
   ~/Library/LaunchAgents/ai.mlx-server.plist.disabled
 
# 错误做法 -- 删除后无法追溯
rm ~/Library/LaunchAgents/ai.mlx-server.plist

重命名后 launchd 不会再加载它,但文件保留可以追溯历史配置。


十、下一步

想了解真实的运维事故和修复过程?

实战故事 -->

想深入理解 512GB 统一内存的管理策略?

内存优化 -->


最后更新: 2026-03-07