运维自动化
开机即运行 -- LaunchAgent 自启、一键切换、巡检脚本、NeoWatch 监控
你将学到什么
- 用 macOS LaunchAgent 实现所有 AI 服务开机自启和崩溃重启
- 用统一脚本管理 10 个服务的启停和日志
- 用 ai-switch 一键切换在线/离线/4bit/8bit 模式
- 用巡检脚本定时检查全部端口、内存、磁盘、日志异常
- 用 NeoWatch 仪表盘实时监控整个基础设施
前置条件:
- Mac Studio 已部署 MLX 推理引擎(参见 Mac Setup)
- 已安装 Claude Code、OpenClaw 等服务
- 了解基本的终端操作
预计时间:20 分钟
一、为什么需要自动化
当你的 Mac Studio 上运行着 10 个 AI 服务时,手动管理就是灾难:
手动管理的一天:
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 崩溃循环开始...
自动化之后:
自动化管理的一天:
06:00 机器意外重启
06:01 LaunchAgent 自动按序启动所有服务(ThrottleInterval 防崩溃循环)
06:03 全部就绪
06:10 巡检脚本定时跑,确认 10 个端口全部健康
你还在睡觉
三个支柱:开机自启 + 崩溃重启 + 定时巡检。
二、LaunchAgent 基础
macOS 的 LaunchAgent 是原生服务管理器,类似 Linux 的 systemd。每个服务对应一个 .plist 文件,放在 ~/Library/LaunchAgents/ 目录下。
plist 文件结构
以 AI 推理引擎为例:
<?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 | 进程工作目录 | 按需设置 |
StandardOutPath | stdout 日志文件 | ~/logs/xxx.log |
StandardErrorPath | stderr 日志文件 | ~/logs/xxx.err.log |
ThrottleInterval 为什么必须 >= 60 秒? 大模型崩溃时,Metal GPU buffer 释放需要 10-30 秒。如果 ThrottleInterval 太短(比如默认 10 秒),新进程在内存还没释放完时就启动,立刻 OOM 再次崩溃,形成崩溃循环。60 秒给了足够的内存回收时间。
管理命令
# 注册并启动服务
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 脚本将所有操作统一到一个入口。
支持的命令
ai-status # 查看所有服务状态(PID、端口、运行时长)
ai-start # 启动所有服务
ai-start mlx # 只启动 MLX 推理引擎
ai-stop # 停止所有服务
ai-restart # 重启所有服务(带安全等待逻辑)
ai-logs # 查看最近日志
ai-logs openclaw # 查看 OpenClaw 日志
ai-patrol # 运行巡检脚本Shell 别名配置
添加到 ~/.zshrc:
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 的安全等待逻辑
重启大模型服务不是简单的"停了再开"。脚本内置了安全等待:
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 是最核心的运维工具。它不只是切换模型,而是联动整个基础设施。
四种模式
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 会触发以下操作:
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 个端口和关键指标。
检查项目
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 小时运行一次:
<!-- 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 是整个基础设施的"中控室",提供实时可视化监控。
仪表盘布局
┌─────────────────────────────────────────────────────────┐
│ 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 态有限状态机):
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.server | 8000 | ai.mlx-lm-server | MLX 推理引擎(核心) |
| OpenClaw 二宝 | 18789 | ai.openclaw.gateway | AI 管家 + Telegram Bot |
| OpenClaw 三宝 | 18790 | ai.openclaw.trader | 交易员 + Crypto 分析 |
| OpenClaw 四宝大脑 | 18796 | ai.openclaw.quant | 量化策略 AI 大脑 |
| OpenClaw 六宝大脑 | 18795 | ai.openclaw.liubao | 预测市场 AI 大脑 |
| 四宝引擎 | 3721 | ai.quant-agent | 量化策略执行引擎 |
| 六宝引擎 | 6789 | ai.liubao | 预测市场交易引擎 |
| NeoWatch Backend | 3940 | ai.neowatch.backend | 系统监控 API |
| NeoWatch Frontend | 3939 | ai.neowatch.frontend | 监控仪表盘 |
| Open WebUI | 3000 | Docker Desktop | Web 聊天界面 |
服务依赖关系
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 中的启动脚本实现等待依赖:
<!-- 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>&1 && break
sleep 1
done
exec /opt/homebrew/bin/bun run dev
</string>
</array>循环 30 秒等 Backend 健康检查通过,然后 exec 替换当前进程启动前端。
八、运维铁律
从无数次事故中总结的三条铁律。违反任何一条都可能导致 GPU Hang 或数据丢失。
铁律一:绝不自行重启 MLX 推理引擎
错误做法:
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-restart 或 ai-switch,并确保你在机器旁边(万一需要物理重启)。
铁律二:所有变更通过运维 Agent 执行
架构变更涉及的文件远比你想象的多。一次"简单的"端口修改可能影响:
- LaunchAgent plist(服务配置)
- ai-switch.sh(模式切换逻辑)
- ai-services.sh(服务管理数组)
- OpenClaw config(模型路由)
- NeoWatch 采集器(监控目标)
- 巡检脚本(端口列表)
- 运维 Agent 配置(服务清单)
- 运维记忆(架构描述)
- 桌面笔记(运维记录)
遗漏任何一个 = 下次巡检时认知错误 = 误操作。
铁律三:架构变更须同步所有运维组件
这是铁律二的推论。每次变更后必须检查以下清单:
变更同步清单:
[ ] LaunchAgent plist 已更新
[ ] ai-switch.sh 已更新
[ ] ai-services.sh 已更新
[ ] OpenClaw 相关 config 已更新
[ ] NeoWatch 采集器已更新
[ ] 巡检脚本端口列表已更新
[ ] 运维 Agent 配置已更新
[ ] 运维记忆已更新
[ ] MEMORY.md 已记录
[ ] 桌面笔记已写入
九、常见问题
Q: 服务启动失败怎么排查?
# 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 8000Q: ai-switch 和 KeepAlive 冲突吗?
不冲突。ai-switch 在停止服务时用 launchctl unload(而不是 kill),这会同时停止进程并禁用 KeepAlive 重启。切换完成后再 launchctl load 重新启用。
Q: 如何查看某个服务的实时日志?
# 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.logQ: 巡检报告在哪看?
# 最近一次巡检结果
cat ~/logs/neo-patrol.log
# 或通过 NeoWatch 仪表盘查看(Patrol 面板)
open http://localhost:3939Q: 如何临时禁用某个服务的自启?
# 禁用自启(不影响当前运行状态)
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.plistQ: 旧的 plist 文件怎么处理?
架构迁移后废弃的 plist 必须重命名为 .disabled 后缀,而不是删除:
# 正确做法 -- 保留记录,防止 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