Перевод community.openwrt с shell на ucode
Я управляю небольшим парком роутеров на OpenWrt через Ansible: облачные
роутеры, VPS и домашние коробки. Инструмент для этого —
community.openwrt,
наследник старой роли gekmihesg.openwrt. Его фишка в том, что на роутере
не нужен Python: модули были shell-скриптами, которые контроллер копировал по
SSH и запускал в BusyBox самого роутера.
Это работает, но медленно — именно скорость и заставила меня его
переписать. В shell-модуле uci каждая опция означала ещё несколько вызовов
uci и jsonfilter, а моей mesh-сети нужно много опций на каждом роутере —
по задаче и по пачке процессов на каждую. Вдобавок JSON собирается из
shell-строк, экранирование — минное поле, а тестировать такие модули тяжело.
Почему ucode
На каждом устройстве с OpenWrt уже есть рантайм получше:
ucode — небольшой JavaScript-подобный язык,
на котором написан firewall4. В нём есть встроенный JSON и привязки к fs,
uci и ubus, так что модуль читает и пишет конфигурацию напрямую, а не
запускает внешнюю команду ради каждого значения.
Что произошло
- Начал я с UCI-модуля на ucode и общей библиотеки помощников (#264). Его закрыли в пользу более мелких и сфокусированных pull request’ов.
- Перевод самого модуля
uciвошёл как #280. Главное в нём —operations: целая пачка изменений (удалить устаревшие секции, создать или обновить новые) применяется по порядку за один вызов, а не по задаче на каждую опцию. Ещё он добавляетredact_keys, чтобы не показывать секреты в выводе diff. - Релиз 1.9.0 — первый релиз коллекции на ucode вместо shell-скриптов:
17 модулей, от
apkиcopyдоserviceиuci.
Дальше: WireGuard и AmneziaWG
Моя домашняя mesh-сеть построена на туннелях WireGuard и AmneziaWG, и до
сих пор вся их обвязка жила в большой Ansible-роли. Новые модули
wg_interface и wg_peer
(#281,
на ревью) забирают на себя логику на стороне устройства: генерацию ключей,
общие PSK, пиров и удаление ненужных интерфейсов. Я уже использую их из
своего форка, а интеграционными тестами служат сценарии Molecule на
настоящей прошивке роутеров в виртуальных машинах.
Несколько подводных камней ucode
- Модуль получает аргументы как путь к JSON-файлу в
ARGV[0]; оставьте в файле маркерWANT_JSON, чтобы Ansible передавал их именно так. - У строк и массивов нет
.length— используйте глобальнуюlength(x). - Объекты сравниваются по ссылке, поэтому для определения изменений нужна своя рекурсивная проверка равенства.
sprintf("%J", obj)— самый быстрый способ вывести JSON.