inotify-rsync: раскладываем файлы по роутерам, как только они изменились
Часть файлов на моих хабах появляется в одном месте: один хаб генерирует несколько статических файлов и скачивает несколько больших, а остальные хабы раздают копии. Копии должны догонять быстро. Задача в cron — это либо задержка, либо куча бесполезных запусков, поэтому хотелось, чтобы rsync срабатывал, когда что-то действительно поменялось.
inotify-rsync делает ровно это и ничего больше: следит за каталогами и при изменениях запускает rsync в нужное место.
Настройка
Одна секция UCI на каждое наблюдение:
# /etc/config/inotify-rsync
config watch 'site'
option enabled '1'
list src '/data/site'
option dest 'root@peer.example.org:/data/'
option rsync_args "-Ppra --delete --delete-delay -e 'dbclient -i /root/.ssh/id_ed25519 -y'"
list filter 'exclude *.tmp'
option textfile_dir '/run/prometheus/textfile'
Каждое наблюдение — отдельный инстанс procd, который перезапускается при
падении. /etc/init.d/inotify-rsync rsync [watch] запускает синхронизацию
вручную — удобно сразу после деплоя. В OpenWrt ssh-клиентом обычно служит
dbclient из Dropbear, поэтому rsync_args разбирается с учётом кавычек —
-e '…' выше остаётся одним аргументом.
От inotifyd к ucode
Первая версия была shell-скриптом вокруг апплета inotifyd из BusyBox. Она
работала, но inotifyd запускается дочерним процессом, и когда procd убивал
скрипт при перезапуске, дочерний процесс оставался — осиротевшие наблюдатели
копились.
Поэтому в августе 2026 года скрипт переписан на ucode, а для него я написал небольшой модуль на C, ucode-mod-inotify, который даёт ucode прямой доступ к Linux inotify API. Теперь скрипт:
- открывает один дескриптор inotify и вешает наблюдение на каждый каталог внутри источников (список собирается при старте);
- ждёт событий в цикле
uloop— вообще без подпроцессов; - делает debounce: пачка записей перезапускает таймер на 100 мс, так что целая порция файлов превращается в один запуск rsync, а не в десятки.
Метрики
Если задан textfile_dir, каждое наблюдение пишет небольшой .prom-файл для
textfile-коллектора node exporter:
inotify_rsync_events_total— сколько событий файловой системы увидено;inotify_rsync_runs_totalиinotify_rsync_errors_total;inotify_rsync_last_run_seconds— время последнего запуска.
Счётчики читаются обратно при старте, так что переживают перезапуски, а файл заменяется атомарно. Чтобы за ним следить, хватит двух алертов: растут ошибки, и последний запуск был подозрительно давно.