macOS 26 后台守护进程开机自启:frp / EasyTier 应该放进 LaunchDaemon,而不是登录项
先说结论
如果你要让
frp、EasyTier、同步器、采集器、机器人、代理程序这类后台服务在 macOS 26 上开机自动运行,核心不是“把命令藏在哪里”,而是“让系统的服务管理器负责它”。这篇文章只讨论后台常驻服务,不讨论普通桌面 App 的登录启动。macOS 26 上如果希望后台服务在用户登录前就启动,应该用 /Library/LaunchDaemons。登录项和 LaunchAgent 更像“人进门后打开的工具”,而 LaunchDaemon 才像“楼里的电梯和水泵”,机器起来就要工作。
我会给出两条路:人工配置,适合你想理解每一步;Agent/一键脚本自动配置,适合你已经知道二进制和配置文件放在哪里,希望复制一段脚本直接落地。脚本不依赖第三方服务,不下载外部 wrapper,也不会写入任何真实 IP、真实主机名或私有域名。

AI 生成封面:后台服务在系统启动阶段被统一拉起。
一、问题背景:后台服务不是“我登录后顺手打开的软件”
很多家庭实验室、小公司内网、开发机和边缘节点,都会跑一些“没人看着也必须一直在”的小服务。最典型的就是 frp、EasyTier、反向代理、监控采集器、文件同步器、通知机器人、定时拉取器。它们的共同特点是:界面不重要,持续运行才重要;人不登录,服务也应该起来;机器重启后,服务应该自动恢复。
很多人第一次配置时,会把命令放在终端里手敲:
/opt/frp/frpc -c /etc/frp/frpc.toml
或者写进某个用户的 shell 启动脚本。短期看似能用,但它有三个硬伤。第一,终端一关进程就可能没了;第二,机器重启后必须有人登录;第三,服务崩溃后没人负责拉起来。
打个比方:后台服务像小区的水泵,不能等业主起床下楼才打开。开机自启动的正确目标,是让它变成“楼宇基础设施”,电闸合上、网络准备好、水泵就自己转;坏了会报警或重启;关机时也按顺序停。
解释图:从开机到网络就绪再到服务常驻的生命周期。
二、问题表现:为什么“能运行”不等于“能自启动”
后台服务自启动失败,表现通常不是一个大红错,而是几个很烦的小症状:
- 重启机器后隧道断了,手动执行命令又恢复。
- SSH 断开或终端关闭后服务消失。
- 配置文件改完没有生效,因为其实跑的是旧进程。
- 服务偶发退出,没有任何人自动重启。
- 关机时卡很久,日志里能看到服务没有响应停止信号。
这里最容易误判的是“命令能跑”。命令能跑,只说明二进制、配置文件、网络在当前 shell 环境里大体没问题;它并不说明系统启动阶段也能找到同样的路径、权限、环境变量和网络状态。
三、问题根因:缺的不是命令,而是生命周期管理
后台服务真正需要的是完整生命周期:什么时候启动、以谁的身份启动、在哪个目录启动、依赖什么、挂了怎么办、日志写到哪里、关机时怎么停。
macOS 26 的标准机制是 launchd LaunchDaemon。你可以把它理解成“值班经理”。我们不再让服务自己站在门口喊“我开工了”,而是告诉值班经理:这名员工叫什么、几点上班、工具在哪、出故障要不要叫回来、下班时怎么收尾。
对 frp / EasyTier 这类服务,最重要的判断有四个:
- 是否必须在用户登录前启动:后台隧道通常是必须的。
- 是否需要网络就绪:几乎一定需要。
- 是否需要 root 或管理员权限:取决于是否创建虚拟网卡、监听低端口、修改路由。
- 是否能被系统监督重启:长期运行的服务应该能失败重启。

真实截图 1:Apple 的 launchd job 创建文档,说明 macOS 后台任务由 launchd 管。
四、人工配置:先慢一点,把关键点看懂
人工配置不是为了显得高级,而是为了让你知道自动脚本到底改了什么。建议按这四步走:
- 确认二进制能执行:先用绝对路径运行,避免依赖
PATH。 - 确认配置文件存在:配置里不要写真实地址到博客或截图里,生产机器自己保留真实值即可。
- 写入服务定义:声明启动命令、工作目录、重启策略、日志位置。
- 启用并立即启动:启用代表下次开机自动启动,立即启动代表现在就验证。
以 frp 为例,人工配置的核心不是那一行 ExecStart,而是把“网络就绪后启动、失败后重启、用专门用户运行、日志可查”这些约束写清楚。以 EasyTier 为例,思路完全一样,只是二进制路径和参数换成 easytier-core。
人工配置后,不要只看进程在不在。至少做三类验证:服务管理器看到它是 enabled;当前状态是 running 或 active;日志里没有连续失败重启。
解释图:人工配置时应该按检查、写配置、启用、验证的顺序走。
五、Agent 自动配置:一句话让 Agent 做,但要给它边界
我推荐给 Agent 的一句话是:
请在当前 macOS 26 机器上,把指定后台程序配置为开机自启动服务。服务名使用
<SERVICE_NAME>,二进制路径使用<BINARY_PATH>,配置文件使用<CONFIG_PATH>。请先检查路径和权限,再写入系统服务配置,启用开机自启并立即启动,最后输出状态、日志和回滚命令。不要联网下载第三方 wrapper,不要把真实 IP、真实主机名、私有域名或密钥写入说明。
这句话的重点是“先检查,再写入,最后验证”。Agent 自动化不是许愿池,它更像一个执行力很强的实习生。你要告诉它工作边界:不下载第三方工具,不覆盖已有配置,不泄露真实地址,不跳过验证。
解释图:Agent 自动配置不是魔法,本质是把检查和写配置脚本化。
下面是一键脚本模板。你只需要改环境变量里的服务名、二进制路径、配置路径和参数。它的设计目标是:可重复执行、失败时尽早退出、最后给出状态证据。
#!/bin/zsh
set -euo pipefail
# Usage:
# sudo SERVICE_ID=net.example.frpc BINARY_PATH=/opt/frp/frpc EXTRA_ARGS="-c /etc/frp/frpc.toml" ./install-launchdaemon.zsh
# sudo SERVICE_ID=net.example.easytier BINARY_PATH=/opt/easytier/easytier-core EXTRA_ARGS="--config-file /etc/easytier/easytier.toml" ./install-launchdaemon.zsh
SERVICE_ID="${SERVICE_ID:-net.example.frpc}"
BINARY_PATH="${BINARY_PATH:-/opt/frp/frpc}"
EXTRA_ARGS="${EXTRA_ARGS:--c /etc/frp/frpc.toml}"
WORK_DIR="${WORK_DIR:-$(dirname "$BINARY_PATH")}"
LOG_DIR="${LOG_DIR:-/var/log/${SERVICE_ID}}"
PLIST="/Library/LaunchDaemons/${SERVICE_ID}.plist"
if [[ "$(id -u)" != "0" ]]; then
echo "Please run with sudo." >&2
exit 1
fi
if [[ ! -x "$BINARY_PATH" ]]; then
echo "Binary is not executable: $BINARY_PATH" >&2
exit 1
fi
mkdir -p "$LOG_DIR"
chmod 755 "$LOG_DIR"
args_xml=""
args_xml="${args_xml} <string>${BINARY_PATH}</string>\n"
for arg in ${(z)EXTRA_ARGS}; do
safe_arg="${arg//&/&}"
safe_arg="${safe_arg//</<}"
safe_arg="${safe_arg//>/>}"
args_xml="${args_xml} <string>${safe_arg}</string>\n"
done
cat > "$PLIST" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>${SERVICE_ID}</string>
<key>ProgramArguments</key>
<array>
${args_xml} </array>
<key>WorkingDirectory</key>
<string>${WORK_DIR}</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>StandardOutPath</key>
<string>${LOG_DIR}/stdout.log</string>
<key>StandardErrorPath</key>
<string>${LOG_DIR}/stderr.log</string>
</dict>
</plist>
PLIST
chown root:wheel "$PLIST"
chmod 644 "$PLIST"
/usr/bin/plutil -lint "$PLIST"
/bin/launchctl bootout system "$PLIST" 2>/dev/null || true
/bin/launchctl bootstrap system "$PLIST"
/bin/launchctl enable "system/${SERVICE_ID}"
/bin/launchctl kickstart -k "system/${SERVICE_ID}"
/bin/launchctl print "system/${SERVICE_ID}" | head -80

真实截图 2:Apple ServiceManagement 文档,说明现代 App 可用 SMAppService 注册后台项。
六、frp / EasyTier 的套用方式
frp 的典型套用方式是:服务名叫 frpc,二进制在 /opt/frp/frpc 或你的工具目录,配置文件在 /etc/frp/frpc.toml。脚本会把它包装成系统后台服务,开机时自动拉起。
EasyTier 的典型套用方式是:服务名叫 easytier,二进制是 easytier-core,参数使用 --config-file <CONFIG_PATH>。如果你的 EasyTier 需要创建虚拟网卡或修改路由,就要确认运行身份有足够权限。
这两者的本质没有区别:一个是“反向连接的隧道工”,一个是“组网的道路工”。你要做的是给它们发一张长期工牌,而不是每天早上手动叫它们上班。
七、验证清单:不要相信“应该可以”
配置完成后,按下面顺序验证:
- 开机启用状态:确认服务已经设置为随系统启动。
- 当前运行状态:确认服务现在已经跑起来。
- 日志状态:确认没有连续报错、没有反复重启。
- 重启验证:计划一次受控重启,确认无人登录时服务也能恢复。
- 业务验证:确认客户端能连通隧道或虚拟网络,但博客和截图里不要暴露真实地址。
验证就像收快递签字。你不能只看快递员说“送到了”,你要打开门看包裹真的在门口。
八、常见坑位
坑 1:相对路径。 手动运行时当前目录正确,开机时当前目录不一定正确。后台服务统一使用绝对路径。
坑 2:依赖用户登录。 登录项、shell profile、终端窗口都不是可靠后台服务方案。
坑 3:网络还没准备好。 隧道服务通常依赖网络,启动过早会失败。要让服务管理器知道它需要网络。
坑 4:把密钥写进命令行。 命令行参数可能被进程列表看到。密钥优先放进权限受控的配置文件。
坑 5:没有重启策略。 后台服务不怕偶发失败,怕的是失败后没人知道、没人拉起。
解释图:自启动失败时,按权限、路径、网络、日志四条线排查。
九、回滚方式
回滚也要脚本化。思路很简单:停止服务、禁用开机自启、删除服务定义、重新加载服务管理器。不要直接删除二进制和配置文件,除非你确认它们不再被别的服务使用。
如果你把自启动理解成“给服务办入职”,回滚就是“办离职”。离职要交回门禁卡,但不一定要把办公桌一起砸了。
十、Q&A
Q:能不能继续用 tmux、screen、nohup?
可以临时用,但不建议当长期方案。它们更像临时帐篷,适合调试,不适合当房子。
Q:一键脚本是不是一定安全?
不是。一键脚本的安全性来自可读、可审计、可回滚。复制前先看变量和写入路径。
Q:为什么不用第三方服务 wrapper?
不是不能用,而是这篇文章的目标是“系统原生能力优先”。少一个外部依赖,就少一个升级和供应链风险。
Q:配置里必须出现真实服务器地址怎么办?
生产配置当然需要真实地址,但文章、截图、提交记录里不要出现。可以在本机保留真实配置,分享时使用 <SERVER_ENDPOINT> 这类占位符。

真实截图 3:launchd.plist manpage,包含 RunAtLoad、KeepAlive、ProgramArguments 等字段。