Два образи замість пʼяти: серверні команди їдуть з одного модуля й
мусять ходити в схему БД однією версією, а зонд лишається окремо —
у чужій мережі йому не місце з DSN і ключами шифрування.
Стек піднімає БД, кеш, міграції, API, колектор і Caddy з автоматичним
TLS. Міграції окремою службою, бо API піднімається в кількох
примірниках і гонка за схему нікому не потрібна.
deploy/README.md описує бекап як три речі: дамп, DEK і JWT-ключ.
Без DEK дамп не відновлюється — у БД лише шифротекст.
Дорогою виправлено .gitignore: голі "netpulse-agent" і
"netpulse-server" ігнорували ще й каталоги cmd/ з кодом команд.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
netpulse-migrate замість PowerShell-скрипта: у контейнері немає ані
psql, ані PowerShell, а тягнути клієнт Postgres в образ заради одного
запуску — це половина дистрибутива на порожньому місці.
Міграції вшиті через embed і переїхали в server/migrations: embed не
бачить нічого за межами кореня свого модуля, а міграції поруч із
бінарником, який їх накочує, не можуть розійтися версіями.
Накочування під advisory-блокуванням: два інстанси при rolling update
інакше застосували б ту саму міграцію двічі. Кожен файл в одній
транзакції разом із записом у schema_migrations; виняток — continuous
aggregates, які TimescaleDB забороняє в транзакції. Змінена вже
застосована міграція зупиняє запуск: у різних інсталяціях інакше
опиниться різна схема під одним номером.
Перевірено на чистій базі: 23 міграції, 101 таблиця, повторний запуск
каже «схема актуальна».
Веб віддає сам API через embed: на self-hosted це прибирає з інструкції
встановлення цілий компонент. Три політики кешування — назавжди для
assets із хешем у імені, ніколи для index.html, коротко для решти.
Знайдено живим прогоном: невідомий шлях під /api/ віддавав 200 з
index.html, і клієнт падав на розборі HTML як JSON замість чесного 404.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
React Flow поверх наявного API: вузли фарбуються статусом, ребра
кольором із порогів, точка на ребрі біжить зі швидкістю, оберненою до
завантаження каналу. WebSocket дає живі статуси й util, реконект
повертає підписку сам.
Робота з живим UI знайшла дві вади:
1. Ревізія мапи росла від самих кліків — React Flow віддає
onNodeDragStop на будь-яке натискання, і кожен клік писав порожню
ревізію, змушуючи інші відкриті полотна перечитуватись.
2. Після збереження хук оновлював лише номер ревізії, тож наступна
подія device.status відкидала щойно перетягнутий вузол назад.
Перевірено в браузері проти повного стека зі справжнім snmpd: два
зелені вузли з живим RTT, ребро eth0 з util і capacity 10 Гбіт/с,
індикатор "наживо", кнопка автопобудови з topo.links.
Драг через автоматизацію відтворити не вдалося — синтетичні події не
запускають d3-drag; цей шлях покритий тестами сервера.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>