# macOS Deployment Guide This guide covers deploying the headroom proxy server as a background service on macOS using LaunchAgent. The service will start automatically on login and restart on crash. ## Overview macOS LaunchAgent provides a native way to run background services with: - **Automatic startup** on user login - **Crash recovery** with automatic restart - **Standard logging** to `~/Library/Logs/` - **Native lifecycle management** via `launchctl` This is ideal for local development environments where you want "set and forget" proxy configuration. ## Prerequisites - macOS 10.13+ (High Sierra or later) - headroom-ai installed with proxy support - Anthropic API key configured ### Installing Headroom with Proxy Support ```bash # Install with proxy support pip install headroom-ai[proxy] # Verify installation headroom proxy --help ``` ### API Key Configuration Your Anthropic API key can be configured in several ways: **Option 1: Shell environment (recommended)** ```bash # Add to ~/.bashrc or ~/.zshrc export ANTHROPIC_API_KEY="sk-ant-..." ``` **Option 2: LaunchAgent plist** ```xml EnvironmentVariables ANTHROPIC_API_KEY sk-ant-... ``` **Option 3: System environment** ```bash # Add to /etc/launchd.conf (requires admin) setenv ANTHROPIC_API_KEY sk-ant-... ``` ## Quick Install The automated installer handles all setup: ```bash # Clone or navigate to headroom repository cd examples/deployment/macos-launchagent # Run installer ./install.sh ``` The installer will: 1. Detect your headroom installation 2. Prompt for port configuration (default: 8787) 3. Create log directory 4. Generate LaunchAgent plist 5. Load and start the service 6. Verify service is running ### Installation Options **Custom port:** ```bash ./install.sh --port 9000 ``` **Unattended install (no prompts):** ```bash ./install.sh --port 8787 --unattended ``` **Reinstall over existing:** ```bash # Installer will prompt to reinstall if service exists ./install.sh ``` ## Manual Installation If you prefer full control over the installation: ### Step 1: Create Log Directory ```bash mkdir -p ~/Library/Logs/headroom ``` ### Step 2: Generate LaunchAgent Plist Copy and customize the template: ```bash cd examples/deployment/macos-launchagent cp com.headroom.proxy.plist.template ~/Library/LaunchAgents/com.headroom.proxy.plist ``` Edit `~/Library/LaunchAgents/com.headroom.proxy.plist`: 1. Replace `__HEADROOM_PATH__` with your headroom path: ```bash command -v headroom # Example output: /usr/local/bin/headroom ``` 2. Replace `__PORT__` with your desired port (e.g., `8787`) 3. Replace `__HOME__` with your home directory: ```bash echo $HOME # Example output: /Users/yourusername ``` ### Step 3: Load the LaunchAgent ```bash launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.headroom.proxy.plist ``` ### Step 4: Verify Service ```bash # Check if service is running launchctl print gui/$(id -u)/com.headroom.proxy # Check if port is listening lsof -iTCP:8787 -sTCP:LISTEN # Test health endpoint curl http://localhost:8787/health ``` ## Configuration ### Port Customization The default port is 8787. To use a custom port: **During installation:** ```bash ./install.sh --port 9000 ``` **After installation:** 1. Uninstall: `./uninstall.sh` 2. Reinstall with new port: `./install.sh --port 9000` 3. Update shell integration: `export HEADROOM_PROXY_PORT=9000` ### Log Location Logs are written to standard macOS locations: - **Standard output**: `~/Library/Logs/headroom/proxy.log` - **Error output**: `~/Library/Logs/headroom/proxy-error.log` To change log locations, edit the plist: ```xml StandardOutPath /custom/path/proxy.log ``` ### Environment Variables Configure additional options in the plist `EnvironmentVariables` section: ```xml EnvironmentVariables HEADROOM_PROXY_PORT 8787 ANTHROPIC_API_KEY sk-ant-... HEADROOM_COMPRESSION_PROVIDER llmlingua HEADROOM_LLMLINGUA_DEVICE mps ``` **Note:** LLMLingua requires additional installation: ```bash pip install headroom-ai[llmlingua] ``` ### Crash Recovery The LaunchAgent is configured with: - **KeepAlive**: Automatically restarts on crash - **ThrottleInterval**: 10 seconds between restart attempts To disable automatic restart, edit the plist: ```xml KeepAlive ``` ## Shell Integration Automatically configure your shell to use the proxy when available. ### Setup Add to `~/.bashrc` (bash) or `~/.zshrc` (zsh): ```bash # Configure port (optional, defaults to 8787) export HEADROOM_PROXY_PORT=8787 # Source shell integration source /path/to/headroom/examples/deployment/macos-launchagent/shell-integration.sh ``` ### What It Does The shell integration script: 1. Checks if proxy is running on configured port 2. If running, sets `ANTHROPIC_BASE_URL=http://localhost:8787` 3. If not running, attempts to start the LaunchAgent 4. Provides status messages on first load This makes Claude clients automatically use the proxy without manual configuration. ### Manual Configuration If you prefer not to use shell integration: ```bash # Add to ~/.bashrc or ~/.zshrc export ANTHROPIC_BASE_URL=http://localhost:8787 ``` ## Service Management ### Check Status ```bash # View service status launchctl print gui/$(id -u)/com.headroom.proxy # Check if port is listening lsof -iTCP:8787 -sTCP:LISTEN # Test health endpoint curl http://localhost:8787/health ``` ### View Logs ```bash # Tail standard output tail -f ~/Library/Logs/headroom/proxy.log # Tail error output tail -f ~/Library/Logs/headroom/proxy-error.log # View last 50 lines tail -n 50 ~/Library/Logs/headroom/proxy-error.log ``` ### Restart Service ```bash # Graceful restart (stop and let KeepAlive restart it) launchctl kickstart -k gui/$(id -u)/com.headroom.proxy # Manual stop/start launchctl bootout gui/$(id -u)/com.headroom.proxy launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.headroom.proxy.plist ``` ### Stop Service Temporarily ```bash # Disable without uninstalling launchctl disable gui/$(id -u)/com.headroom.proxy # Re-enable launchctl enable gui/$(id -u)/com.headroom.proxy ``` ## Verification After installation, verify everything is working: ### 1. Check Service Status ```bash launchctl print gui/$(id -u)/com.headroom.proxy ``` Expected output includes: ``` state = running ``` ### 2. Check Port ```bash lsof -iTCP:8787 -sTCP:LISTEN ``` Should show headroom listening on port 8787. ### 3. Test Health Endpoint ```bash curl http://localhost:8787/health ``` Expected response: ```json {"status": "healthy"} ``` ### 4. Test Proxy Functionality ```bash # Set base URL export ANTHROPIC_BASE_URL=http://localhost:8787 # Test with Python 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. Check Logs for Errors ```bash tail -n 20 ~/Library/Logs/headroom/proxy-error.log ``` Should show no errors. Common startup errors are listed in [Troubleshooting](#troubleshooting). ## Troubleshooting ### Service Won't Start **Symptom:** `launchctl print` shows service not loaded or failed state **Check logs:** ```bash tail -n 50 ~/Library/Logs/headroom/proxy-error.log ``` **Common causes:** | Error | Solution | |-------|----------| | `ANTHROPIC_API_KEY not set` | Set API key in environment or plist | | `ModuleNotFoundError: No module named 'headroom'` | Install: `pip install headroom-ai[proxy]` | | `command not found: headroom` | Update plist with correct path: `command -v headroom` | | `Address already in use` | Change port or stop conflicting service | ### Port Already in Use **Symptom:** Service starts but port not listening, logs show "Address already in use" **Find what's using the port:** ```bash lsof -iTCP:8787 -sTCP:LISTEN ``` **Solutions:** 1. Stop conflicting service 2. Use different port: `./uninstall.sh && ./install.sh --port 9000` ### Service Crashes Immediately **Symptom:** Service starts but immediately exits **Check for Python errors:** ```bash tail -f ~/Library/Logs/headroom/proxy-error.log ``` **Common causes:** - Missing dependencies: `pip install headroom-ai[proxy]` - Invalid API key: Verify `ANTHROPIC_API_KEY` - Python version incompatible: Requires Python 3.9+ ### ANTHROPIC_BASE_URL Not Set **Symptom:** Shell integration not setting environment variable **Verify proxy is running:** ```bash curl http://localhost:8787/health ``` **Reload shell configuration:** ```bash source ~/.bashrc # or ~/.zshrc ``` **Check shell integration is sourced:** ```bash # Should be set to 1 echo $HEADROOM_SHELL_INTEGRATION_LOADED ``` ### Service Not Auto-Starting on Login **Symptom:** Service doesn't start after reboot **Verify LaunchAgent is loaded:** ```bash launchctl list | grep headroom ``` **If not listed:** ```bash launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.headroom.proxy.plist ``` **Check RunAtLoad is enabled:** ```bash grep -A1 RunAtLoad ~/Library/LaunchAgents/com.headroom.proxy.plist ``` Should show: ```xml RunAtLoad ``` ### Permission Issues **Symptom:** "Operation not permitted" errors **Ensure plist has correct permissions:** ```bash chmod 644 ~/Library/LaunchAgents/com.headroom.proxy.plist ``` **Verify ownership:** ```bash ls -l ~/Library/LaunchAgents/com.headroom.proxy.plist ``` Should be owned by your user, not root. ## Uninstallation ### Quick Uninstall ```bash cd examples/deployment/macos-launchagent ./uninstall.sh ``` This will: 1. Stop the service 2. Remove LaunchAgent plist 3. Optionally remove log directory (prompts) ### Remove Everything ```bash # Uninstall service and remove logs ./uninstall.sh --remove-logs # Remove shell integration from ~/.bashrc or ~/.zshrc # Delete or comment out: # export HEADROOM_PROXY_PORT=8787 # source .../shell-integration.sh ``` ### Manual Uninstall ```bash # Stop service launchctl bootout gui/$(id -u)/com.headroom.proxy # Remove plist rm ~/Library/LaunchAgents/com.headroom.proxy.plist # Remove logs (optional) rm -rf ~/Library/Logs/headroom ``` ## Production Deployment For production environments, consider: - **System-wide LaunchDaemon** instead of per-user LaunchAgent - **Resource limits** in plist (CPU, memory) - **Log rotation** for long-running deployments - **Monitoring** via external tools - **Multiple instances** on different ports for redundancy LaunchAgent is designed for single-user development. For production, evaluate: - [Docker deployment](proxy.md#docker-deployment) for containerized environments - systemd on Linux servers - Cloud-native solutions (ECS, Cloud Run, etc.) ## Related Documentation - [Proxy Server Documentation](proxy.md) - Core proxy configuration and features - [Configuration Guide](configuration.md) - Detailed configuration options - [Architecture](ARCHITECTURE.md) - How Headroom works internally - [Troubleshooting](troubleshooting.md) - General troubleshooting guide ## Platform Alternatives - **Linux**: Use systemd instead of LaunchAgent - **Windows**: Use Task Scheduler or NSSM (Non-Sucking Service Manager) - **Docker**: See [proxy.md](proxy.md) for containerized deployment ## Security Considerations ### LaunchAgent vs LaunchDaemon **LaunchAgent** (used here): - Runs in user context - No root privileges required - Starts on user login - Per-user isolation **LaunchDaemon** (not covered): - Runs as root or specific user - System-wide service - Starts on boot - Requires admin privileges For single-user development, LaunchAgent is recommended for security. ### API Key Security Store API keys securely: - ✅ Use environment variables in shell config - ✅ Use macOS Keychain (advanced) - ✅ Restrict plist file permissions: `chmod 600` - ❌ Don't commit API keys to version control - ❌ Don't store in world-readable files ### Network Security The proxy binds to `127.0.0.1` (localhost only) by default: - ✅ Only accessible from local machine - ✅ No external network exposure - ❌ Don't bind to `0.0.0.0` without firewall rules ## Advanced Configuration ### Multiple Proxy Instances Run multiple proxies on different ports: ```bash # Install first instance ./install.sh --port 8787 # For second instance, manually create plist with different label cp com.headroom.proxy.plist.template ~/Library/LaunchAgents/com.headroom.proxy-2.plist # Edit: Change Label to com.headroom.proxy-2, port to 8788 launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.headroom.proxy-2.plist ``` ### Custom LaunchAgent Schedule Run proxy only during business hours: ```xml StartCalendarInterval Hour 9 Minute 0 ``` ### Resource Limits Limit CPU and memory usage: ```xml HardResourceLimits NumberOfProcesses 1 MemoryMax 536870912 ``` ## FAQ **Q: Why LaunchAgent instead of running `headroom proxy` manually?** A: LaunchAgent provides automatic startup, crash recovery, and proper lifecycle management. You don't have to remember to start the proxy or keep a terminal window open. **Q: Can I use this in production?** A: LaunchAgent is designed for development. For production, use Docker, systemd, or cloud-native deployment. **Q: How much does the proxy impact performance?** A: Minimal. The proxy adds ~10-50ms latency while reducing token costs by 50-90%. The cost savings far outweigh the latency. **Q: Do I need to restart the proxy when configuration changes?** A: Yes. After changing the plist, reload the service: ```bash launchctl kickstart -k gui/$(id -u)/com.headroom.proxy ``` **Q: Can I use this with multiple API providers?** A: The LaunchAgent setup is Anthropic-specific. For other providers, see [proxy.md](proxy.md) for configuration options. **Q: Does this work with Apple Silicon (M1/M2/M3)?** A: Yes, fully compatible. For LLMLingua compression, use `--llmlingua-device mps` for Apple Silicon acceleration.