Skip to content

Run OpenAI-Compatible API Proxy as macOS Background Service

Problem

I closed my terminal and my AI proxy died. The next morning, I rebooted my Mac and had to manually start the proxy again. Then the proxy crashed during a streaming response, and I didn’t notice until my application started failing with connection errors.

Here’s what my workflow looked like:

manual-management.txt
# Every day: Open terminal, start proxy
$ ai-menshen -config ~/.config/ai-menshen/config.toml
# If terminal closes: Proxy dies
# If Mac reboots: Proxy doesn't restart
# If proxy crashes: No automatic recovery
# Multiple terminals with proxy processes:
Terminal 1: ai-menshen running
Terminal 2: ai-menshen running (conflict!)
Terminal 3: Which one is the real proxy?

I was spending time managing the proxy instead of using it. The process was fragile—terminal sessions die, reboots happen, crashes occur.

What happened?

I searched for solutions to run services in the background. On Linux, I’d use systemd. But on macOS, the native service manager is launchd.

I discovered that ai-menshen already provides a ready-to-use launchd plist file. The tool’s repository includes configs/net.liujiacai.ai-menshen.plist configured for:

  • Auto-start on login (RunAtLoad)
  • Automatic crash recovery (KeepAlive)
  • Centralized logging to /tmp/ai-menshen-stderr.log
  • Standard binary path at ~/.local/bin/ai-menshen
  • Standard config path at ~/.config/ai-menshen/config.toml

The architecture is straightforward:

launchd-architecture.txt
BEFORE: Manual terminal management
┌─────────────┐
│ Terminal │──starts──▶│ ai-menshen │──▶│ OpenAI API │
│ window │ │ │ │ │
└─────────────┘ └─────────────┘ └────────────┘
Fragile Manual only Dies on crash
Dies on close No auto-restart No logging
AFTER: launchd managed service
┌─────────────┐ ┌─────────────┐ ┌────────────┐
│ launchd │──manages─▶│ ai-menshen │──▶│ OpenAI API │
│ (macOS) │ │ (background)│ │ │
└─────────────┘ └─────────────┘ └────────────┘
Auto-start Always running Continuous
Crash recovery No terminal Logging to file

How to solve it?

Step 1: Verify installation

I first ensured ai-menshen was properly installed:

check-install.sh
# Check binary exists
ls ~/.local/bin/ai-menshen
# If not installed, run the installer
curl -fsSL https://raw.githubusercontent.com/jiacai2050/ai-menshen/main/install.sh | sh
# Verify version
ai-menshen -version

The binary should be at ~/.local/bin/ai-menshen. If it’s elsewhere, I’d need to update the plist later.

Step 2: Create configuration

I generated the config file:

setup-config.sh
mkdir -p ~/.config/ai-menshen
ai-menshen -gen-config > ~/.config/ai-menshen/config.toml
# Edit to add your upstream API key
vi ~/.config/ai-menshen/config.toml

The config path must match what’s in the plist: ~/.config/ai-menshen/config.toml.

Step 3: Copy the plist

I copied the provided plist to the LaunchAgents directory:

install-plist.sh
# Create LaunchAgents directory if needed
mkdir -p ~/Library/LaunchAgents
# Copy the plist
cp configs/net.liujiacai.ai-menshen.plist ~/Library/LaunchAgents/
# Verify it's there
ls ~/Library/LaunchAgents/net.liujiacai.ai-menshen.plist

LaunchAgents are user-level services that run when you log in. LaunchDaemons are system-level services that run at boot before login. For a personal proxy, LaunchAgents is the right choice.

Step 4: Load the service

I loaded the service into launchd:

load-service.sh
launchctl load ~/Library/LaunchAgents/net.liujiacai.ai-menshen.plist
# The service starts immediately (RunAtLoad = true)

Step 5: Verify it’s running

I checked the service status:

check-status.sh
launchctl list | grep ai-menshen
# Expected output shows the process ID:
# 12345 0 net.liujiacai.ai-menshen
# PID exit code label
# If the second column shows a non-zero number, the service crashed
# If the PID is blank, the service isn't running

I also tested the proxy directly:

test-proxy.sh
curl http://localhost:8080/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-token" \
-d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "test"}]}'
# Open dashboard in browser
open http://localhost:8080

Step 6: Check logs

I viewed the service logs:

view-logs.sh
# Standard error output
tail -f /tmp/ai-menshen-stderr.log
# Standard output (if configured)
tail -f /tmp/ai-menshen-stdout.log

The logs showed startup messages and any errors.

Service management commands

Stop the service

stop-service.sh
launchctl unload ~/Library/LaunchAgents/net.liujiacai.ai-menshen.plist
# The proxy stops immediately

Restart the service

restart-service.sh
launchctl unload ~/Library/LaunchAgents/net.liujiacai.ai-menshen.plist
launchctl load ~/Library/LaunchAgents/net.liujiacai.ai-menshen.plist

Reload after plist changes

If I edited the plist, I needed to unload and reload:

reload-plist.sh
# FIRST unload
launchctl unload ~/Library/LaunchAgents/net.liujiacai.ai-menshen.plist
# THEN make changes
vi ~/Library/LaunchAgents/net.liujiacai.ai-menshen.plist
# THEN reload
launchctl load ~/Library/LaunchAgents/net.liujiacai.ai-menshen.plist

Customizing the plist

The provided plist has default paths. If my paths differ, I edited the plist:

net.liujiacai.ai-menshen.plist
<?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>net.liujiacai.ai-menshen</string>
<key>ProgramArguments</key>
<array>
<!-- UPDATE THIS PATH to match your binary location -->
<string>/Users/YOUR_NAME/.local/bin/ai-menshen</string>
<string>-config</string>
<!-- UPDATE THIS PATH to match your config location -->
<string>/Users/YOUR_NAME/.config/ai-menshen/config.toml</string>
</array>
<key>RunAtLoad</key>
<true/> <!-- Start immediately when loaded -->
<key>KeepAlive</key>
<true/> <!-- Restart on crash -->
<key>StandardErrorPath</key>
<string>/tmp/ai-menshen-stderr.log</string>
<key>StandardOutPath</key>
<string>/tmp/ai-menshen-stdout.log</string>
</dict>
</plist>

Key plist keys explained:

plist-keys.txt
Label: Unique identifier for the service
ProgramArguments: Command to run (binary + args)
RunAtLoad: Start immediately when loaded (true/false)
KeepAlive: Restart automatically if process dies (true/false)
StandardErrorPath: Where stderr output goes
StandardOutPath: Where stdout output goes

The reason

Why does launchd work better than manual terminal management?

Auto-start on login: RunAtLoad ensures the proxy starts when you load the plist. Combined with placing it in ~/Library/LaunchAgents, macOS loads it automatically when you log in.

Automatic crash recovery: KeepAlive tells launchd to restart the process if it exits. If the proxy crashes during a streaming response or runs out of memory, launchd brings it back up within seconds.

Background operation: No terminal window needed. The process runs as a daemon, freeing up your terminals for other work.

Centralized logging: All output goes to known files. I can check /tmp/ai-menshen-stderr.log anytime to see what happened.

Standard macOS pattern: launchd is the native way to manage services on macOS. It’s reliable, well-documented, and integrated with the OS.

Common mistakes

Mistake 1: Wrong paths in plist

wrong-path.xml
<!-- BAD: Generic path that doesn't exist -->
<string>/Users/YOUR_NAME/.local/bin/ai-menshen</string>
<!-- GOOD: Your actual username -->
<string>/Users/johndoe/.local/bin/ai-menshen</string>

I checked my actual path:

verify-path.sh
# Find where ai-menshen is installed
which ai-menshen
# Output: ~/.local/bin/ai-menshen
# Get absolute path
realpath ~/.local/bin/ai-menshen

Mistake 2: Not unloading before plist changes

reload-order.sh
# BAD: Edit while loaded
vi ~/Library/LaunchAgents/net.liujiacai.ai-menshen.plist
# Changes don't take effect until reload!
# GOOD: Unload, edit, reload
launchctl unload ~/Library/LaunchAgents/net.liujiacai.ai-menshen.plist
vi ~/Library/LaunchAgents/net.liujiacai.ai-menshen.plist
launchctl load ~/Library/LaunchAgents/net.liujiacai.ai-menshen.plist

Mistake 3: Ignoring stderr log

check-errors.sh
# If the service shows a non-zero exit code in launchctl list:
launchctl list | grep ai-menshen
# Output: - 1 net.liujiacai.ai-menshen (exit code 1 = error)
# CHECK THE LOG:
cat /tmp/ai-menshen-stderr.log
# Common errors:
# - "config file not found" → Check config path
# - "permission denied" → Check file permissions
# - "address already in use" → Another instance running

Mistake 4: Multiple proxy instances

check-conflicts.sh
# If the proxy won't start, check for other instances:
ps aux | grep ai-menshen
# Kill stray processes before starting launchd service:
pkill -f ai-menshen

Summary

In this post, I showed how to run ai-menshen as a macOS launchd background service. The key point is using launchd for auto-start on login and automatic crash recovery—no manual terminal management required.

The provided plist handles everything:

  • Auto-start via RunAtLoad
  • Crash recovery via KeepAlive
  • Logging to /tmp/ai-menshen-stderr.log

I went from fragile terminal sessions that died on close to a robust background service that survives reboots and crashes. The proxy is now always ready to handle AI API requests.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments