在 macOS 中运行命令行程序,如果你不想开一个终端窗口,并且希望实现开机自启动等等功能,可以使用 macOS 自带的服务管理。这里以 easytier 这个组网工具为例:

直接执行的命令是:/usr/local/bin/easytier-core -c /Users/iuxt/config/easytier.toml

plist 分为系统级别和用户级别,分别位于:

特性 LaunchAgents (用户代理) LaunchDaemons (守护进程)
运行时机 用户登录后启动 系统开机时启动,即用户登录前
运行用户 当前登录的用户 一般为 root 或指定的其他用户
适用场景 需要 GUI 界面、用户偏好设置或访问用户目录的服务 需要高权限、不依赖用户界面的后台服务,如 Web 服务器、数据库等
存放路径 ~/Library/LaunchAgents/ (当前用户) /Library/LaunchAgents/ (所有用户) /Library/LaunchDaemons/
加载命令 launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.app.plist sudo launchctl bootstrap system /Library/LaunchDaemons/com.example.app.plist

系统级别服务

配置文件

vim /Library/LaunchDaemons/easytier.plist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated for serviceman. Edit as needed. Keep this line for 'serviceman list'. -->
<!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>easytier</string>

<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/easytier-core</string>
<string>-c</string>
<string>/Users/iuxt/config/easytier.toml</string>
</array>

<key>UserName</key>
<string>root</string>
<key>GroupName</key>
<string>wheel</string>
<key>InitGroups</key>
<true/>

<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>

<key>WorkingDirectory</key>
<string>/usr/local/bin</string>

<key>StandardOutPath</key>
<string>/var/log/easytier.log</string>
<key>StandardErrorPath</key>
<string>/var/log/easytier-error.log</string>
</dict>
</plist>

配置权限

1
2
sudo chown root:wheel /Library/LaunchDaemons/easytier.plist
sudo chmod 644 /Library/LaunchDaemons/easytier.plist

启动

1
sudo launchctl bootstrap system /Library/LaunchDaemons/easytier.plist

bootstrap 命令会同时完成“安装”和“根据 RunAtLoad 配置决定是否启动”两个步骤。
gui/$(id -u) 是当前用户的 “ 域 “ (domain),system 则是系统的域。

卸载服务

1
2
3
4
sudo launchctl bootout system /Library/LaunchDaemons/easytier.plist

# 删除plist文件
sudo rm -rf /Library/LaunchDaemons/easytier.plist

用户级别服务

配置文件

vim ~/Library/LaunchAgents/picup.plist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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>picup</string>
<key>ProgramArguments</key>
<array>
<string>/Users/iuxt/code/picup/picup</string>
</array>

<key>WorkingDirectory</key>
<string>/Users/iuxt/code/picup/</string>

<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>

<key>StandardOutPath</key>
<string>/Users/iuxt/code/picup/logs/stdout.log</string>
<key>StandardErrorPath</key>
<string>/Users/iuxt/code/picup/logs/stderr.log</string>
</dict>
</plist>

启动

1
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/picup.plist

停止服务

1
launchctl stop picup

卸载服务

1
launchctl bootout gui/$(id -u)/picup

从对应的目录中删除 .plist 文件。

1
rm -rf ~/Library/LaunchAgents/picup.plist