首页
/ Headroom Proxy on macOS:基于 LaunchAgent 的常驻代理服务部署实战指南

Headroom Proxy on macOS:基于 LaunchAgent 的常驻代理服务部署实战指南

2026-09-06 11:59:26作者:何将鹤

本文围绕 headroom 仓库中 macOS 部署指南 展开,讲解如何将 headroom 代理服务器部署为 macOS 原生 LaunchAgent 后台服务,实现登录自动启动、崩溃自动拉起与标准日志落盘。读完本文,你可以独立完成从 CLI 安装、plist 生成、shell 集成到故障排查、卸载的完整生命周期管理,并理解安装脚本与 proxy 内存嵌入器(MPS GPU offload)在源码层面的实际行为。

1. 为什么选择 LaunchAgent 后台化部署

headroom 是一个"在内容到达 LLM 之前压缩工具输出、日志、文件与 RAG 分块"的代理层(库、proxy、MCP server)。在日常开发中,代理需要作为 Claude 等客户端的上游中转长期存活。手动运行 headroom proxy 需要一直占用一个终端窗口,且进程崩溃后无人重启。

macOS 的 LaunchAgent 提供了原生的解决方案,部署后获得四项能力:

  • 自动启动:用户登录(login)时自动拉起服务;
  • 崩溃恢复KeepAlive 机制在进程退出后自动重启;
  • 标准日志:stdout/stderr 分别写入 ~/Library/Logs/ 下的日志文件;
  • 原生生命周期管理:通过 launchctl 完成启动、停止、状态查询。

这非常适合"部署一次、忘记它"(set and forget)的本地开发环境。需要说明的是:LaunchAgent 是**按用户(per-user)**的,运行在用户上下文中、随用户登录启动;这与系统级的 LaunchDaemon(root/开机启动)不同,后者的取舍见 第 11 节安全考量

2. 前置条件与 CLI 安装

2.1 环境要求

条件 说明
macOS 版本 macOS 10.13+(High Sierra 或更新)
headroom 已安装并带 proxy 支持
API Key 已配置 Anthropic API key(代理上游默认指向 Anthropic)

2.2 安装带 proxy 支持的 headroom

# 安装宿主 CLI(含 proxy 支持)
uv tool install --python 3.13 "headroom-ai[proxy]"

# 如果安装后 shell 找不到 headroom
uv tool update-shell

# 验证安装
headroom proxy --help

在 macOS + Homebrew 环境下,python3 可能指向比当前 headroom wheel 支持的更新的解释器,显式传 --python 3.13 可以把 CLI 固定在 wheel 支持的 Python 上。若系统缺少 Python 3.13,先安装:

brew install python@3.13

2.3 API Key 的三种配置方式

方式一:Shell 环境(推荐)

# 添加到 ~/.bashrc 或 ~/.zshrc
export ANTHROPIC_API_KEY="sk-ant-..."

方式二:写入 LaunchAgent plist

<key>EnvironmentVariables</key>
<dict>
    <key>ANTHROPIC_API_KEY</key>
    <string>sk-ant-...</string>
</dict>

方式三:系统级环境

# 添加到 /etc/launchd.conf(需要管理员权限)
setenv ANTHROPIC_API_KEY sk-ant-...

plist 模板默认把 API key 相关配置保持注释状态(见 模板文件<!-- ANTHROPIC_API_KEY should be set in your shell environment --> 注释),即官方倾向让密钥留在 shell 环境中,而不是固化进 plist。

3. 部署资产与自动化安装

3.1 部署目录结构

所有 macOS 部署资产位于 examples/deployment/macos-launchagent/

文件 作用
com.headroom.proxy.plist.template LaunchAgent plist 模板,含 __HEADROOM_PATH____PORT____HOME__ 占位符
install.sh 自动化安装脚本
uninstall.sh 自动化卸载脚本
shell-integration.sh Shell 集成脚本,自动设置 ANTHROPIC_BASE_URL
README.md 该目录的快速上手说明

3.2 一键安装

cd examples/deployment/macos-launchagent
./install.sh

安装器完成六步操作:

  1. 检测 headroom 可执行文件(command -v headroom)并校验 headroom proxy --help 可用;
  2. 提示端口配置(默认 8787);
  3. 创建日志目录 ~/Library/Logs/headroom
  4. 从模板生成 LaunchAgent plist 并写入 ~/Library/LaunchAgents/com.headroom.proxy.plist
  5. 加载并启动服务(launchctl bootstrap);
  6. 验证服务状态与端口监听。

安装选项:

# 自定义端口
./install.sh --port 9000

# 无人值守安装(跳过所有交互提示)
./install.sh --port 8787 --unattended

# 已存在服务时重装(交互式确认 Reinstall? [y/N])
./install.sh

3.3 从源码看 install.sh 的实际行为

阅读 install.sh 可以确认以下实现细节,排障时很有用:

  • 平台守护uname -s 不为 Darwin 时直接退出并提示 "Use systemd on Linux"(L76-L79);
  • 端口校验:端口必须是 1024–65535 的整数,否则 fatal(L126-L128);
  • 端口占用检测:用 lsof -iTCP:$PORT -sTCP:LISTEN -t 预检,被占用时交互确认(L131-L140);
  • 模板渲染:用 sed 一次性替换三个占位符生成最终 plist,随后 chmod 644(L157-L165);
  • 幂等加载launchctl bootstrap 失败时会先 launchctl bootout 再重试一次(L169-L179);
  • 加载后验证sleep 2 后检查 launchctl print 与端口监听,未监听时提示 tail -f ${LOG_DIR}/proxy-error.log(L181-L198)。

安装成功后,脚本会打印服务详情(端口、日志路径、label)以及推荐的 shell 集成与常用命令,例如重启命令 launchctl kickstart -k gui/$USER_UID/com.headroom.proxy

4. 手动安装(完全掌控每一步)

如果不想走安装脚本,可以按以下四步手动完成。

Step 1:创建日志目录

mkdir -p ~/Library/Logs/headroom

Step 2:生成 LaunchAgent Plist

cd examples/deployment/macos-launchagent
cp com.headroom.proxy.plist.template ~/Library/LaunchAgents/com.headroom.proxy.plist

然后编辑 ~/Library/LaunchAgents/com.headroom.proxy.plist,替换三个占位符:

  1. __HEADROOM_PATH__command -v headroom 的输出(如 /usr/local/bin/headroom);
  2. __PORT__ → 目标端口(如 8787);
  3. __HOME__echo $HOME 的输出(如 /Users/yourusername)。

Step 3:加载 LaunchAgent

launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.headroom.proxy.plist

Step 4:验证服务

# 服务是否运行
launchctl print gui/$(id -u)/com.headroom.proxy

# 端口是否监听
lsof -iTCP:8787 -sTCP:LISTEN

# 健康检查
curl http://localhost:8787/health

Plist 模板逐项解读

com.headroom.proxy.plist.template 中每个键的作用:

<!-- 服务标签,必须与 plist 文件名一致 -->
<key>Label</key>
<string>com.headroom.proxy</string>

<!-- 启动命令:headroom proxy --host 127.0.0.1 --port <PORT> -->
<key>ProgramArguments</key>
<array>
    <string>__HEADROOM_PATH__</string>
    <string>proxy</string>
    <string>--host</string>
    <string>127.0.0.1</string>
    <string>--port</string>
    <string>__PORT__</string>
</array>

<key>EnvironmentVariables</key>
<dict>
    <!-- 端口环境(模板实际使用的键名是 HEADROOM_PROXY_PORT) -->
    <key>HEADROOM_PROXY_PORT</key>
    <string>__PORT__</string>
    <!-- ANTHROPIC_API_KEY 建议留在 shell 环境,模板中保持注释 -->
</dict>

<key>WorkingDirectory</key>
<string>__HOME__</string>

<!-- stdout / stderr 分别落盘 -->
<key>StandardOutPath</key>
<string>__HOME__/Library/Logs/headroom/proxy.log</string>
<key>StandardErrorPath</key>
<string>__HOME__/Library/Logs/headroom/proxy-error.log</string>

<!-- 崩溃自动重启 -->
<key>KeepAlive</key>
<true/>

<!-- 登录时自动启动 -->
<key>RunAtLoad</key>
<true/>

<!-- 后台自适应进程类型 -->
<key>ProcessType</key>
<string>Adaptive</string>

<!-- 重启间隔 10 秒 -->
<key>ThrottleInterval</key>
<integer>10</integer>

两个值得注意的点:

  • 默认绑定 127.0.0.1ProgramArguments 中写死 --host 127.0.0.1,即代理只监听本机回环地址,不暴露到外部网络;
  • ProcessType Adaptive:允许 launchd 将其作为后台进程管理,降低前台调度优先级。

5. 配置详解

5.1 端口

默认端口 8787。自定义方式:

安装时指定:

./install.sh --port 9000

安装后更换:

  1. 卸载:./uninstall.sh
  2. 用新端口重装:./install.sh --port 9000
  3. 同步更新 shell 侧的端口变量(见 5.4 说明)

关于端口环境变量的一个勘误说明:wiki 原文部分位置写作 HEADROOM_PORT,但从仓库源码看,实际生效的键名是 HEADROOM_PROXY_PORT——shell-integration.shHEADROOM_PROXY_PORT="${HEADROOM_PROXY_PORT:-8787}"install.sh 的成功提示也打印 export HEADROOM_PROXY_PORT=${PORT},plist 模板写入的也是 HEADROOM_PROXY_PORT。请以 HEADROOM_PROXY_PORT 为准。

5.2 日志位置

日志写入 macOS 标准位置:

  • 标准输出~/Library/Logs/headroom/proxy.log
  • 错误输出~/Library/Logs/headroom/proxy-error.log

如需改到自定义路径,编辑 plist 中的对应键:

<key>StandardOutPath</key>
<string>/custom/path/proxy.log</string>

(同理可改 StandardErrorPath。)

5.3 环境变量与压缩后端说明

在 plist 的 EnvironmentVariables 节中追加其他配置:

<key>EnvironmentVariables</key>
<dict>
    <!-- 代理端口 -->
    <key>HEADROOM_PROXY_PORT</key>
    <string>8787</string>

    <!-- 可选:API key(或设置在 shell 中) -->
    <key>ANTHROPIC_API_KEY</key>
    <string>sk-ant-...</string>
</dict>

重要变更提示:早先 LLMLingua-2 launch-agent 变量(HEADROOM_COMPRESSION_PROVIDER=llmlinguaHEADROOM_LLMLINGUA_DEVICE 以及 headroom-ai[llmlingua] extra)已随 --llmlingua 标志一起退役。模板中残留的 HEADROOM_COMPRESSION_PROVIDER 注释行即为历史痕迹。如今要启用 ML 压缩,应安装 [ml] extra 并参考 wiki/transforms.md

5.4 崩溃恢复

LaunchAgent 的恢复语义由两个键控制(见模板 L49-L63):

  • KeepAlive = true:进程退出(包括崩溃)后自动重启;
  • ThrottleInterval = 10:两次重启尝试之间至少间隔 10 秒,防止崩溃风暴打满 CPU。

如需禁用自动重启:

<key>KeepAlive</key>
<false/>

注意:修改 plist 后需要重新加载服务才能生效,重载命令见 第 7 节

6. Shell 集成

安装完服务后,可以让 shell 在登录时自动把 Anthropic 客户端指向代理。

6.1 配置方法

~/.bashrc(bash)或 ~/.zshrc(zsh)中追加:

# 端口(可选,默认 8787)——注意实际键名是 HEADROOM_PROXY_PORT
export HEADROOM_PROXY_PORT=8787

# 引入 shell 集成
source /path/to/headroom/examples/deployment/macos-launchagent/shell-integration.sh

6.2 脚本机制(源码级解读)

shell-integration.sh 的执行逻辑:

  1. 防重复加载:通过 HEADROOM_SHELL_INTEGRATION_LOADED 环境变量去重(L27-L30),该变量同时是排障探针——正常 source 后 echo $HEADROOM_SHELL_INTEGRATION_LOADED 应为 1
  2. 快速路径检测lsof -iTCP:${HEADROOM_PROXY_PORT} -sTCP:LISTEN -t 判断端口是否已有进程监听(L33-L35);
  3. 运行中则直接接管:设置 export ANTHROPIC_BASE_URL="http://localhost:${HEADROOM_PROXY_PORT}"(L57-L59);
  4. 未运行则尝试拉起:若 plist 文件存在,执行 launchctl bootstrap gui/$(id -u) <plist>(幂等,已加载不会失败),等待 1 秒后复检;启动成功才设置 ANTHROPIC_BASE_URL 并打印提示(L61-L70);
  5. 清理命名空间:结束时 unset -f 两个内部函数,不污染 shell(L74)。

这套机制让 Claude 系客户端无需手动配置即可走代理。

6.3 手动配置(不用集成脚本)

# 添加到 ~/.bashrc 或 ~/.zshrc
export ANTHROPIC_BASE_URL=http://localhost:8787

7. 服务管理

7.1 查看状态

# 服务状态
launchctl print gui/$(id -u)/com.headroom.proxy

# 端口监听
lsof -iTCP:8787 -sTCP:LISTEN

# 健康端点
curl http://localhost:8787/health

7.2 查看日志

tail -f ~/Library/Logs/headroom/proxy.log          # stdout
tail -f ~/Library/Logs/headroom/proxy-error.log   # stderr
tail -n 50 ~/Library/Logs/headroom/proxy-error.log # 最近 50 行

7.3 重启服务

# 优雅重启(stop + 依赖 KeepAlive 拉起)
launchctl kickstart -k gui/$(id -u)/com.headroom.proxy

# 手动 stop/start
launchctl bootout gui/$(id -u)/com.headroom.proxy
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.headroom.proxy.plist

7.4 临时停用(不卸载)

# 禁用(进程不会被 KeepAlive 拉起)
launchctl disable gui/$(id -u)/com.headroom.proxy

# 重新启用
launchctl enable gui/$(id -u)/com.headroom.proxy

disablebootout 的区别:bootout 会把整个 job 从 launchd 中卸载,disable 只是把该 label 标记为禁用状态,适合"暂时不想跑但保留注册"的场景。

8. 安装后验证清单

按顺序执行以下五步,全部通过即部署成功:

1. 服务状态 —— launchctl print gui/$(id -u)/com.headroom.proxy 输出应包含:

state = running

2. 端口监听 —— lsof -iTCP:8787 -sTCP:LISTEN 应显示 headroom 进程。

3. 健康端点 —— curl http://localhost:8787/health 期望返回:

{"status": "healthy"}

4. 真实代理请求 —— 走一遍完整链路:

export ANTHROPIC_BASE_URL=http://localhost:8787

python -c "
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
    model='claude-3-5-sonnet-20241022',
    max_tokens=50,
    messages=[{'role': 'user', 'content': 'Hi'}]
)
print(response.content[0].text)
"

5. 错误日志无异常

tail -n 20 ~/Library/Logs/headroom/proxy-error.log

应无错误输出;常见启动错误对照 第 9 节

9. 故障排查

9.1 服务无法启动

症状launchctl print 显示未加载或 failed 状态。先看日志:

tail -n 50 ~/Library/Logs/headroom/proxy-error.log

常见原因对照表:

错误 解决方案
ANTHROPIC_API_KEY not set 在环境或 plist 中设置 API key
ModuleNotFoundError: No module named 'headroom' 安装:uv tool install --python 3.13 "headroom-ai[proxy]"
command not found: headroom command -v headroom 的输出修正 plist 路径
Address already in use 换端口或停掉占用端口的服务

9.2 端口被占用

症状:服务起来了但端口不监听,日志出现 "Address already in use"。

lsof -iTCP:8787 -sTCP:LISTEN   # 找出占用者

处理:停掉冲突服务;或换端口 ./uninstall.sh && ./install.sh --port 9000

9.3 服务启动后立即崩溃

tail -f ~/Library/Logs/headroom/proxy-error.log

常见原因:依赖缺失(重装 headroom-ai[proxy])、API key 无效(校验 ANTHROPIC_API_KEY)、Python 版本不兼容(要求 3.10+)。

9.4 ANTHROPIC_BASE_URL 未生效

先确认代理在运行(curl http://localhost:8787/health),然后:

source ~/.bashrc   # 或 ~/.zshrc

# 集成脚本是否被 source(正常应为 1)
echo $HEADROOM_SHELL_INTEGRATION_LOADED

若为 0,说明 shell-integration.sh 未被当前 shell 加载。

9.5 登录/重启后未自动启动

launchctl list | grep headroom    # 确认已注册

未注册则重新加载:

launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.headroom.proxy.plist

并确认 plist 中 RunAtLoadtrue

grep -A1 RunAtLoad ~/Library/LaunchAgents/com.headroom.proxy.plist

9.6 权限问题("Operation not permitted")

chmod 644 ~/Library/LaunchAgents/com.headroom.proxy.plist
ls -l ~/Library/LaunchAgents/com.headroom.proxy.plist

plist 应属主为当前用户而非 root(安装脚本本身会 chmod 644,见 install.sh)。

10. 卸载

10.1 快速卸载

cd examples/deployment/macos-launchagent
./uninstall.sh

uninstall.sh 依次执行:launchctl bootout 停止服务 → 删除 plist → 询问(或按 --remove-logs 直接执行)是否删除日志目录。

10.2 彻底清理

./uninstall.sh --remove-logs

# 从 ~/.bashrc / ~/.zshrc 中删除或注释:
#   export HEADROOM_PROXY_PORT=8787
#   source .../shell-integration.sh
# 以及(如有)export ANTHROPIC_BASE_URL=...

10.3 手动卸载

launchctl bootout gui/$(id -u)/com.headroom.proxy
rm ~/Library/LaunchAgents/com.headroom.proxy.plist
rm -rf ~/Library/Logs/headroom   # 可选

11. 安全考量:LaunchAgent vs LaunchDaemon

LaunchAgent(本文方案):

  • 运行在用户上下文,无需 root;
  • 随用户登录启动;
  • 天然的用户级隔离。

LaunchDaemon(未覆盖):

  • 以 root 或指定用户运行;
  • 系统级服务,开机启动;
  • 需要管理员权限。

单用户开发场景下,LaunchAgent 在安全性上是更合适的选择。

API Key 安全实践:

  • ✅ 放在 shell 配置的环境变量中;
  • ✅ 使用 macOS Keychain(进阶方案);
  • ✅ 收紧含密钥的 plist 权限:chmod 600
  • ❌ 不要把 API key 提交到版本控制;
  • ❌ 不要存放在世界可读的文件中。

网络安全: 模板中 --host 127.0.0.1 使代理只绑定本机回环地址,无外部网络暴露面。不要改为绑定 0.0.0.0(除非有防火墙规则配合)。

12. 进阶配置

12.1 多实例

第一个实例走安装脚本;第二个实例手工创建不同 label 的 plist:

./install.sh --port 8787

cp com.headroom.proxy.plist.template ~/Library/LaunchAgents/com.headroom.proxy-2.plist
# 编辑:Label 改为 com.headroom.proxy-2,端口改为 8788
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.headroom.proxy-2.plist

注意 label 必须与 plist 文件名一致,这是 launchd 的硬性要求。

12.2 定时启动

仅在工作时间运行,向 plist 追加:

<key>StartCalendarInterval</key>
<dict>
    <key>Hour</key>
    <integer>9</integer>
    <key>Minute</key>
    <integer>0</integer>
</dict>

12.3 资源限制

<key>HardResourceLimits</key>
<dict>
    <key>NumberOfProcesses</key>
    <integer>1</integer>
    <key>MemoryMax</key>
    <integer>536870912</integer> <!-- 512 MB -->
</dict>

13. Apple Silicon:MPS GPU 嵌入 offload

Apple Silicon 上,proxy 的 memory 模块(本地记忆向量检索)可以把嵌入计算从默认 ONNX CPU 后端卸载到 Apple GPU(MPS),降低高负载下的 CPU 占用,对无风扇机型(如 M5 Air)上 CPU 饱和导致的超时尤为有用。

启用方式:

pip install 'headroom-ai[pytorch-mps]'   # [pytorch_mps] 写法亦可
export HEADROOM_EMBEDDER_RUNTIME=pytorch_mps

在 LaunchAgent 下则写进 plist 的 EnvironmentVariables

<key>HEADROOM_EMBEDDER_RUNTIME</key>
<string>pytorch_mps</string>

从源码看这条路径的精确行为:memory_handler.py 中,local 后端默认走 onnx 嵌入(模型 all-MiniLM-L6-v2,384 维向量);仅当 HEADROOM_EMBEDDER_RUNTIME 归一化后等于 pytorch_mps 时,才会尝试导入 sentence_transformerstorch,并在 torch.backends.mps.is_available() 为真时切换到 torch 句向量后端跑在 GPU 上。任何一环缺失(MPS 不可用、依赖未装)都只是打印 warning 并回落到默认选择路径,严格 opt-in,默认行为不变pyproject.toml 中也标注该 extra 为 macOS-only、刻意排除在 [all] 之外。详细背景见 wiki/memory.md

14. 常见问题(FAQ)

Q:为什么不用手动 headroom proxy A:LaunchAgent 提供自动启动、崩溃恢复与完整的生命周期管理,无需记住手动启动或保持终端窗口。

Q:能用于生产吗? A:LaunchAgent 面向开发环境。生产请使用 Docker、systemd 或云原生部署(见下节)。

Q:改配置后需要重启 proxy 吗? A:需要。修改 plist 后执行:

launchctl kickstart -k gui/$(id -u)/com.headroom.proxy

Q:能接多个 API provider 吗? A:本文的 LaunchAgent 配置面向 Anthropic。其他 provider 见 wiki/proxy.md 的配置选项。

Q:Apple Silicon(M1/M2/M3)兼容吗? A:完全兼容。ML 压缩(Kompress,通过 headroom-ai[ml] opt-in)在 Apple Silicon 上会自动检测 MPS。

15. 生产部署与跨平台替代

LaunchAgent 为单用户开发设计。生产环境建议评估:

  • LaunchDaemon(系统级)替代按用户 Agent;
  • plist 中增加资源限制(CPU、内存)与日志轮转
  • 通过外部工具做监控
  • 不同端口多实例做冗余。

跨平台方案:

平台 方案
Linux systemd
Windows 任务计划程序或 NSSM
容器化 wiki/proxy.mdwiki/docker-install.md

16. 相关文档

登录后查看全文
热门项目推荐
相关项目推荐