systemd 通过 timer 触发 service 执行含 rsync、inotifywait 和 .ready 标记的同步脚本,确保原子性与可观测性;需配对定义 composer-mirror-sync.timer 和 .service,Type=oneshot、Restart=on-failure,并避免仅依赖 inotifywait -m 长守进程。

systemd 服务如何接管 Composer 镜像同步任务
Composer 镜像同步本身没有内置 systemd 支持,必须靠外部脚本 + systemd 单元文件组合实现。核心逻辑是:用定时器(timer)触发同步脚本,用服务(service)保障执行环境与原子性,而非让 Composer 自己“注册为服务”。
同步脚本必须包含 rsync + inotify + 原子就绪标记
单纯写个 rsync -avz --delete ... 并不能直接交给 systemd 管理——它缺乏失败重试、状态反馈和上下文完整性校验。实际可部署的脚本需满足:
- 使用
rsync --delay-updates,避免下游拉到半截文件 - 同步完成后执行
sync && touch /path/to/mirror/.ready,作为“数据就绪”信号 - 配合
inotifywait -e moved_to,close_write -m /path/to/mirror/监听 .ready 文件(而非 packages.json),防止事件误触发 - 超时控制:用
timeout 600 rsync ...防止卡死
systemd timer/service 单元文件关键配置项
不能只写一个 .service 文件然后手动启动。必须成对定义:composer-mirror-sync.service + composer-mirror-sync.timer。重点字段如下:
-
Type=oneshot:确保 service 执行完即退出,不常驻 -
Restart=on-failure+StartLimitIntervalSec=3600:1 小时内最多重启 5 次,防无限崩溃 -
ExecStartPre=/usr/bin/test -f /path/to/mirror/.ready:可选前置检查,跳过已就绪状态 -
Timer中设OnCalendar=hourly或OnUnitActiveSec=1h,避免和上游 packagist 的增量更新节奏冲突
为什么不能只依赖 inotifywait -m 长守进程
把 inotifywait -m 写进 ExecStart 看似合理,但实际会出问题:
- systemd 默认在
simple类型下等待主进程 exit,而-m是永不停止的,导致服务状态永远是activating - 进程僵死后 systemd 无法自动拉起,除非改用
forking类型并正确设置PIDFile,但 inotifywait 不生成 PID 文件 - 更可靠的做法是:timer 定期触发 service,service 内部用 inotifywait 加
--timeout 30做有限监听,超时即退出,由 timer 下次再试
真正难的不是写单元文件,而是确保 rsync 拉取、本地元数据生成、.ready 标记、inotify 通知、下游消费这整条链路的原子性和可观测性——任何一个环节漏掉 fsync、权限或路径拼接错误,都会让 systemd 显示 “succeeded” 但下游始终收不到新包。


















