<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Заметки on Владимир Ермаков</title><link>https://vehq.ru/ru/notes/</link><description>Recent content in Заметки on Владимир Ермаков</description><generator>Hugo</generator><language>ru</language><lastBuildDate>Sat, 17 Oct 2026 00:00:00 +0300</lastBuildDate><atom:link href="https://vehq.ru/ru/notes/index.xml" rel="self" type="application/rss+xml"/><item><title>obserwrt: экспорт потоков на OpenWrt после netobserv, pmacct и softflowd</title><link>https://vehq.ru/ru/notes/2026/obserwrt/</link><pubDate>Sat, 17 Oct 2026 00:00:00 +0300</pubDate><guid>https://vehq.ru/ru/notes/2026/obserwrt/</guid><description>&lt;p&gt;В августе мне захотелось посмотреть, что на самом деле ходит по
&lt;a href="https://vehq.ru/ru/notes/2026/awg-mesh/"&gt;mesh-сети&lt;/a&gt;: кто с кем общается, через
какой туннель и сколько. С коллектором вопросов не было —
&lt;a href="https://github.com/akvorado/akvorado"&gt;Akvorado&lt;/a&gt; принимает IPFIX, сам опрашивает
экспортёры по SNMP, чтобы знать имена интерфейсов, и умеет забирать
BGP-маршруты по BMP. Сложности начались с экспортёра на роутерах.&lt;/p&gt;
&lt;p&gt;В итоге получился &lt;a href="https://github.com/vooon/obserwrt"&gt;obserwrt&lt;/a&gt;: eBPF-программы
на TC считают потоки прямо в ядре, а небольшой агент забирает их из карты и
отправляет в IPFIX. Возможности описаны в README. Здесь — о том, как я к нему
пришёл: сначала три готовых экспортёра, потом свой, и что ломалось по дороге.&lt;/p&gt;</description></item><item><title>Бэкап OpenWrt-хостов и их виртуалок в Proxmox Backup Server</title><link>https://vehq.ru/ru/notes/2026/openwrt-pbs-backup/</link><pubDate>Fri, 16 Oct 2026 00:00:00 +0300</pubDate><guid>https://vehq.ru/ru/notes/2026/openwrt-pbs-backup/</guid><description>&lt;p&gt;Часть моих коробок на OpenWrt — x86-машины, на которых под голым QEMU
крутится ещё одна-две виртуалки. Всё остальное в хоумлабе бэкапится в
&lt;a href="https://www.proxmox.com/en/products/proxmox-backup-server/overview"&gt;Proxmox Backup Server&lt;/a&gt;,
значит, и они должны. Этим занимаются два пакета из
&lt;a href="https://github.com/vooon/my-openwrt-feed"&gt;моего фида для OpenWrt&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="клиент"&gt;Клиент&lt;/h2&gt;
&lt;p&gt;Для бэкапов в PBS нужен &lt;code&gt;proxmox-backup-client&lt;/code&gt;, а он на Rust, и в OpenWrt
его нет. Пакет &lt;a href="https://github.com/vooon/my-openwrt-feed/tree/master/proxmox-backup"&gt;proxmox-backup&lt;/a&gt;
в фиде собирает из исходников upstream только клиент, с несколькими патчами:
сборка cargo только клиента, без libsystemd и с системным zstd. Пока я
проверял его только на x86_64.&lt;/p&gt;</description></item><item><title>siphon: как довезти WAL из CloudNativePG до Proxmox Backup Server</title><link>https://vehq.ru/ru/notes/2026/siphon/</link><pubDate>Fri, 09 Oct 2026 00:00:00 +0300</pubDate><guid>https://vehq.ru/ru/notes/2026/siphon/</guid><description>&lt;p&gt;Всё в моём хоумлабе бэкапится в Proxmox Backup Server: с ротацией,
верификацией и копией на ленту. В начале октября для баз данных это
перестало быть правдой, и на исправление ушла неделя и небольшая утилита на
Rust. &lt;a href="https://github.com/vooon/siphon"&gt;siphon&lt;/a&gt; переносит S3-бакет в снапшот
PBS и обратно; подробности — в README. А здесь — зачем он понадобился и что
мешало.&lt;/p&gt;
&lt;h2 id="как-базы-выпали-из-pbs"&gt;Как базы выпали из PBS&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;3–7 октября.&lt;/em&gt; Я переносил сервисы с виртуалок и compose в небольшие
кластеры Kubernetes: сначала Forgejo, потом Healthchecks, Woodpecker, Immich,
Paperless, Open Archiver. Файлы по-прежнему уезжали в PBS: общий CronJob
монтирует том рядом с работающим подом в режиме только для чтения и
запускает клиент бэкапа, а при необходимости сначала выполняет подготовку
(Paperless, например, перед этим выгружает документы).&lt;/p&gt;</description></item><item><title>pathosd: чего стоило анонсировать адрес, только пока сервис жив</title><link>https://vehq.ru/ru/notes/2026/pathosd/</link><pubDate>Wed, 07 Oct 2026 00:00:00 +0300</pubDate><guid>https://vehq.ru/ru/notes/2026/pathosd/</guid><description>&lt;p&gt;Некоторые сервисы в моей сети работают в нескольких местах сразу —
DNS-резолвер на каждом хабе, S3-шлюз больше чем на одной ноде хранилища.
Клиенты должны видеть один стабильный адрес, а маршрутизация — отправлять их
на ближайший экземпляр, который действительно работает. В маршрутизируемой
сети это anycast: каждый экземпляр анонсирует один и тот же адрес по BGP.
Загвоздка в слове &lt;em&gt;работает&lt;/em&gt; — маршрут понятия не имеет, жив ли сервис за
ним.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/vooon/pathosd"&gt;pathosd&lt;/a&gt; закрывает этот разрыв. Это
проверка здоровья и источник BGP-маршрутов в одном процессе на Go: он
проверяет сервис локально (HTTP, DNS, ping, TCP, UDP, gRPC) и анонсирует
адрес, только пока проверка проходит, — или оставляет его с пониженным
приоритетом, чтобы предпочитались другие. keepalived/VRRP решает похожую
задачу, но в одном L2-сегменте с общим MAC, а не поперёк маршрутизируемой
mesh-сети из туннелей.&lt;/p&gt;</description></item><item><title>Zone-o-Matic: как DynDNS-эндпоинт вырос в сторону записи моего DNS</title><link>https://vehq.ru/ru/notes/2026/zoneomatic/</link><pubDate>Mon, 05 Oct 2026 00:00:00 +0300</pubDate><guid>https://vehq.ru/ru/notes/2026/zoneomatic/</guid><description>&lt;p&gt;Мой авторитативный DNS проще некуда: CoreDNS на хабах отдаёт обычные
зонные файлы плагином &lt;code&gt;file&lt;/code&gt;, который перечитывает их, когда меняется serial
в SOA. Легко читать, легко бэкапить, легко смотреть diff в git.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/vooon/zoneomatic"&gt;Zone-o-Matic&lt;/a&gt; — небольшой сервис на Go,
который пишет в эти файлы. Он не задумывался как большой проект — каждый API
в нём появился вместе с новым клиентом, которому нужно было поменять запись.&lt;/p&gt;
&lt;h2 id="исходная-задача-роутеры-и-acmesh"&gt;Исходная задача: роутеры и acme.sh&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Август 2025.&lt;/em&gt; Часть моих роутеров сидит за динамическими адресами, и мне
хотелось держать их имена в своих зонах, а не у стороннего DynDNS-провайдера.
Эти же роутеры получают TLS-сертификаты через &lt;code&gt;acme.sh&lt;/code&gt;, которому нужно
публиковать записи для вызовов &lt;code&gt;dns-01&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Перевод community.openwrt с shell на ucode</title><link>https://vehq.ru/ru/notes/2026/community-openwrt-ucode/</link><pubDate>Fri, 02 Oct 2026 00:00:00 +0300</pubDate><guid>https://vehq.ru/ru/notes/2026/community-openwrt-ucode/</guid><description>&lt;p&gt;Я управляю небольшим парком роутеров на OpenWrt через Ansible: облачные
роутеры, VPS и домашние коробки. Инструмент для этого —
&lt;a href="https://github.com/ansible-collections/community.openwrt"&gt;community.openwrt&lt;/a&gt;,
наследник старой роли &lt;code&gt;gekmihesg.openwrt&lt;/code&gt;. Его фишка в том, что на роутере
не нужен Python: модули были shell-скриптами, которые контроллер копировал по
SSH и запускал в BusyBox самого роутера.&lt;/p&gt;
&lt;p&gt;Это работает, но медленно — именно скорость и заставила меня его
переписать. В shell-модуле &lt;code&gt;uci&lt;/code&gt; каждая опция означала ещё несколько вызовов
&lt;code&gt;uci&lt;/code&gt; и &lt;code&gt;jsonfilter&lt;/code&gt;, а моей mesh-сети нужно много опций на каждом роутере —
по задаче и по пачке процессов на каждую. Вдобавок JSON собирается из
shell-строк, экранирование — минное поле, а тестировать такие модули тяжело.&lt;/p&gt;</description></item><item><title>Домашняя mesh-сеть на AmneziaWG</title><link>https://vehq.ru/ru/notes/2026/awg-mesh/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0300</pubDate><guid>https://vehq.ru/ru/notes/2026/awg-mesh/</guid><description>&lt;p&gt;Мои роутеры стоят в разных местах: несколько хабов на арендованных серверах
в разных странах и несколько площадок — дом, стойка хоумлаба и ещё пара мест. Все на
OpenWrt, а связаны между собой
&lt;a href="https://github.com/amnezia-vpn"&gt;AmneziaWG&lt;/a&gt; — форком
WireGuard, пакеты которого не похожи на WireGuard. Эта заметка — про
маршрутизацию поверх, а не про сами туннели.&lt;/p&gt;
&lt;h2 id="устройство"&gt;Устройство&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Один туннель на линк.&lt;/strong&gt; Каждый линк — отдельный интерфейс точка-точка
ровно с одним пиром: хабы связаны в mesh-сеть, каждая площадка — с каждым
хабом. Интерфейсов больше, зато каждый простой, со своими счётчиками, и
перезапускается отдельно.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IPv6 внутри туннелей.&lt;/strong&gt; На каждом линке &lt;code&gt;/127&lt;/code&gt; и link-local адрес. У
каждого роутера есть loopback-адрес, по которому до него достаёт остальная
сеть. Loopback-адреса живут на dummy-интерфейсах Linux, которые штатный
OpenWrt не умеет объявлять в &lt;code&gt;/etc/config/network&lt;/code&gt;, поэтому я собрал для
них небольшой протокол netifd — &lt;a href="https://github.com/vooon/my-openwrt-feed/tree/master/netifd-proto-dummy"&gt;netifd-proto-dummy&lt;/a&gt;,
и страницу для LuCI — &lt;a href="https://github.com/vooon/my-openwrt-feed/tree/master/luci-proto-dummy"&gt;luci-proto-dummy&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Всё в Ansible.&lt;/strong&gt; Интерфейсы, ключи, зоны файрвола и маршрутизация
генерируются из инвентаря с помощью
&lt;a href="https://github.com/ansible-collections/community.openwrt"&gt;community.openwrt&lt;/a&gt;.
Сами туннели — ключи, общие PSK, пиры, удаление лишнего — ведут модули
&lt;code&gt;wg_interface&lt;/code&gt; и &lt;code&gt;wg_peer&lt;/code&gt;, которые я написал как раз для этой сети
(&lt;a href="https://github.com/ansible-collections/community.openwrt/pull/281"&gt;#281&lt;/a&gt;),
а всё остальное идёт пакетными операциями &lt;code&gt;uci&lt;/code&gt; — см.
&lt;a href="https://vehq.ru/ru/notes/2026/community-openwrt-ucode/"&gt;заметку про ucode&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="маршрутизация"&gt;Маршрутизация&lt;/h2&gt;
&lt;p&gt;На каждом роутере один демон &lt;a href="https://bird.network.cz/"&gt;BIRD&lt;/a&gt;:&lt;/p&gt;</description></item><item><title>Наблюдаем за BIRD на OpenWrt: метрики, ubus и страница в LuCI</title><link>https://vehq.ru/ru/notes/2026/bird-monitoring/</link><pubDate>Sat, 12 Sep 2026 00:00:00 +0300</pubDate><guid>https://vehq.ru/ru/notes/2026/bird-monitoring/</guid><description>&lt;p&gt;На каждом роутере &lt;a href="https://vehq.ru/ru/notes/2026/awg-mesh/"&gt;моей mesh-сети&lt;/a&gt;
крутится &lt;a href="https://bird.network.cz/"&gt;BIRD&lt;/a&gt;. &lt;code&gt;birdc&lt;/code&gt; по SSH нормально для
одной коробки и утомительно для десяти, поэтому я сделал три небольших пакета,
которые смотрят на BIRD с трёх сторон: Prometheus, ubus и LuCI. Все они
лежат в &lt;a href="https://github.com/vooon/my-openwrt-feed"&gt;моём фиде для OpenWrt&lt;/a&gt; и
все написаны на ucode.&lt;/p&gt;
&lt;h2 id="управляющий-сокет-bird"&gt;Управляющий сокет BIRD&lt;/h2&gt;
&lt;p&gt;У BIRD простой строковый протокол на unix-сокете — тот же, что использует
&lt;code&gt;birdc&lt;/code&gt;. Подключаешься, читаешь приветствие, отправляешь команду и читаешь
ответ; каждая строка ответа начинается с четырёхзначного кода и &lt;code&gt;-&lt;/code&gt; (дальше
есть ещё строки) или пробела (последняя строка). С модулем &lt;code&gt;socket&lt;/code&gt; на ucode
это говорится легко, так что ни одному пакету не нужен ни &lt;code&gt;birdc&lt;/code&gt;, ни
отдельный бинарник экспортера.&lt;/p&gt;</description></item><item><title>inotify-rsync: раскладываем файлы по роутерам, как только они изменились</title><link>https://vehq.ru/ru/notes/2026/inotify-rsync/</link><pubDate>Sun, 16 Aug 2026 00:00:00 +0300</pubDate><guid>https://vehq.ru/ru/notes/2026/inotify-rsync/</guid><description>&lt;p&gt;Часть файлов на моих хабах появляется в одном месте: один хаб генерирует
несколько статических файлов и скачивает несколько больших, а остальные хабы
раздают копии. Копии должны догонять быстро. Задача в cron — это либо
задержка, либо куча бесполезных запусков, поэтому хотелось, чтобы rsync
срабатывал, когда что-то действительно поменялось.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/vooon/my-openwrt-feed/tree/master/inotify-rsync"&gt;inotify-rsync&lt;/a&gt;
делает ровно это и ничего больше: следит за каталогами и при изменениях
запускает rsync в нужное место.&lt;/p&gt;
&lt;h2 id="настройка"&gt;Настройка&lt;/h2&gt;
&lt;p&gt;Одна секция UCI на каждое наблюдение:&lt;/p&gt;</description></item></channel></rss>