我一直觉得 systemd 是一套相当无聊的底层设施,用来启动系统和运行后台服务。我也知道它刚推出时曾引发巨大争议,但除此之外,我其实没怎么深入了解过它。
实际上,systemd 有大量有趣的功能,我认为它们对运行临时编写的代理代码甚至更加有用。
注意,这些功能都只适用于 Linux——抱歉了 Mac 用户,macOS 上没有真正对应 systemd 的东西(launchd 涵盖了一些类似 cron 的功能,但没有资源限制)。
你可能遇到过这种情况:让代理写一个脚本,比如做一些数据分析。结果脚本有严重的内存泄漏,机器突然卡死,(但愿)过一会儿内核会恢复运行,并报出 OOM 错误。
我遇到过几次这种情况,非常烦人。后来发现 systemd 有个简单的方法,可以限制进程及其所有子进程的资源:MemoryMax 和 CPUQuota。这些实际上使用的,和 Docker 使用的是同一套底层 cgroup 限制。
举例来说,如果你想运行 Claude Code,将内存上限设为 4GB、只用 2 个 CPU 核心,以免它耗尽所有 RAM,只要这样运行即可:
systemd-run --user --scope -p MemoryMax=4G -p CPUQuota=200% claude
显然,你可以用这种方式运行任何命令。还有个额外好处:如果用 systemd-cgtop 而不是 top(或 btop),就能按 cgroup 查看内存和 CPU 使用情况。
附注:systemd 也有沙箱功能(ProtectHome、ReadWritePaths 等),你可能会觉得这对代理来说再合适不过。但其中的坑实在太多,本文篇幅不够解释——也许哪天我会再写一篇。
类似地,如果你想限制某个自主代理的运行时间,例如最多运行 30 分钟,只需使用 RuntimeMaxSec 参数。比如,你想让 OpenCode 调查某件事,但不希望它运行超过 30 分钟,可以这样运行:
systemd-run --user --same-dir --unit=opencode-agent \
-p RuntimeMaxSec=30m \
~/.opencode/bin/opencode run --standalone --auto \
"Fix the failing tests in this repo and summarise what you changed"
# follow it: journalctl --user -u opencode-agent -f
注意:--standalone 参数是必需的,因为 OpenCode 默认会在后台服务器上运行任务,而你可能已经启动了该服务器;这样就会绕过时间限制。
这个功能可能对笔记本电脑更有用。如果你想运行一个进程,然后合上笔记本电脑,systemd-inhibit 是个不错的办法。你可以和前一个功能结合使用,确保过一段时间后进程会停止,以免电池电量耗尽:
systemd-run --user --same-dir --unit=claude-agent \
-p RuntimeMaxSec=30m \
systemd-inhibit --what=sleep:idle:handle-lid-switch --why="claude agent run" \
~/.local/bin/claude -p \
"Fix the failing tests in this repo and summarise what you changed"
比较显而易见的一点是,systemd 的功能远比简单的 cron 丰富。我建议阅读完整手册页了解详情,其中有些功能很酷。比如我一直在用的一个功能是 Persistent:如果电脑关机或处于睡眠状态,任务会在电脑重新上线后运行。如果你和我一样,想让代理每周总结某些内容并通知你,这就非常实用。
同样,OnSuccess 和 OnFailure 参数也很有用。如果你定义一个 Slack 或电子邮件单元,就能轻松地将运行结果通过管道传递出去。这里也很适合放置调用 Healthchecks.io 等服务的“死人开关”请求。
另一个不错的功能是为 cron 任务添加随机延迟。如果你有一批机器会在比如凌晨 1 点同时访问某个服务,可以用 RandomizedDelaySec 让启动时间随机延后一点,避免负载突然飙升。
这是个很酷的功能,我直到研究这篇文章时才知道。简单来说,文件夹里的内容一有变化,它就会触发。
# ~/.config/systemd/user/agent-inbox.path
[Path]
DirectoryNotEmpty=%h/agent-inbox
[Install]
WantedBy=default.target
# ~/.config/systemd/user/agent-inbox.service
[Unit]
OnSuccess=slack-notify.service
[Service]
Type=exec
RuntimeMaxSec=30m
MemoryMax=4G
ExecStart=/bin/sh -c YOUR_COMMAND_HERE
所以你可以用它来监视一个文件夹,每次文件夹发生变化时就触发某项操作。如果你有一个存放例如费用单或发票的文件夹,只要把文件放进去,就能让代理在新文件加入时为你分类。
总的来说,如果你和我一样,原以为 systemd 只适合运行守护进程,除此之外没什么用,那它绝对值得深入了解。它常常能提供你所需的大量底层功能,让你不必自己从头实现;尤其如今我们往往会积累大量几乎“用完即弃”的软件,这些功能就更有用了。