Один коміт, а не десяток тематичних, свідомо: теми переплетені в
спільних файлах (store.go, docker-compose.yml, deploy/README.md), і
розділити їх можна було б лише індексуванням шматків. Коміти, які не
збираються, гірші за один великий — тим паче що це рівно той стан, який
перевірявся разом.
ЩО ПРАЦЮЄ НА СТЕНДІ Й ПЕРЕВІРЕНО ТАМ
0058 подієві алерти: syslog, ncm, compliance спрацьовують у мить
події; правило з нереалізованим джерелом більше не зберігається
мовчки
0059 snmp.walk і прототипи шаблонів — таблиці з динамічним індексом
описуються шаблоном, а не Go
0060 відкат конфігу: план як різниця, маскування паролів із підписом
плану, обов'язковий контрольний збір, verifying при обриві
0061 кнопки Telegram: довге опитування, авторизація не з callback_data
0062 аудит і архів хостів; тест на AST, що падає на ключі без назви
0063 RLS: три ролі, окремий пул для фонових тактів
0064 строки зберігання даних і сторінка сховища
0065 приймач SNMP-трапів; перевірено справжніми пакетами по дроту,
переклад v1→v2 за RFC 3584 дає правильний OID
0066 ескалації сповіщень
0067 алерт про вичерпання диска
0068 поля заливки конфігу переїхали в каталог профілів
Плюс: 137 тестів вебу з нуля (їх не було взагалі), одинадцять справжніх
вад, знайдених ними й виправлених, і виправлення двох інтеграційних
тестів grpcapi, які мовчки пропускались півтора року.
ЩО ЩЕ НЕ ЗАПУСКАЛОСЬ
netpulse установник: одна команда замість 18 змінних і
593 рядків інструкції
RLS з першого запуску нова інсталяція під політиками одразу;
RLS-EXISTING-INSTALL.md лишається тільки для
старих інсталяцій
.forgejo + CI раннер не зареєстрований
Ці три перевірені компіляцією й міркуванням, але не виконанням.
ГОЛОВНИЙ ВИСНОВОК ДВОХ СЕСІЙ
Зелена перевірка доводить рівно те, що вона перевіряє. Тест ізоляції RLS
був правильний і зелений — і пропустив зламаний вхід, бо перевіряв «чи
не видно чужого», коли зламалось «чи видно своє». Інтеграційні тести
grpcapi були зелені, бо не виконувались. Схема, довідник і протокол
описували те, чого в коді не існувало, і виглядало це як готове.
Тому в кожному завданні цих сесій стояла вимога назвати НЕПОКРИТЕ, а
чотири задачі закінчились не можливістю, а відмовою: правило з
нереалізованим джерелом не зберігається, профіль без команд заливки
каже про це замість мовчазної кнопки, міграція RLS валить сама себе на
таблиці без політики, тест словника аудиту падає на ключі без назви.
Подробиці — HISTORY.md, розділи за 26 і 27 серпня.
|
||
|---|---|---|
| .. | ||
| .env.example | ||
| act-runner.config.yml | ||
| Caddyfile | ||
| docker-compose.ci.yml | ||
| Dockerfile.agent | ||
| Dockerfile.server | ||
| files.conf.example | ||
| README.md | ||
| RLS-EXISTING-INSTALL.md | ||
Розгортання NetPulse
Один хост, docker compose, автоматичний TLS. Такого розгортання
вистачає до кількох тисяч хостів на моніторингу; розносити служби по
машинах має сенс тоді, коли впирається БД, а не застосунок.
Що з чого складається
| Служба | Роль | Порт |
|---|---|---|
db |
PostgreSQL 16 + TimescaleDB — усі дані | — |
cache |
DragonflyDB — черги й тимчасові стани | — |
migrate |
накочування схеми; відпрацьовує і зупиняється | — |
api |
HTTP API + вшитий інтерфейс | 8080 |
collector |
gRPC-колектор, до якого підключаються зонди | 9443 |
proxy |
Caddy: сертифікати, HTTPS, проксі до двох служб | 80/443/9443 |
Історія конфігів живе в Git на томі git-data, спільному для api й
collector: перший читає її для порівняння версій, другий пише під час
бекапу.
Назовні дивиться лише proxy. api і collector портів не публікують:
до них ходять через нього.
Перший запуск
git clone <репозиторій> netpulse && cd netpulse
cp deploy/.env.example .env
Заповнити .env. Три значення обовʼязкові й генеруються так:
openssl rand -base64 24 # POSTGRES_PASSWORD
echo "np1=$(openssl rand -hex 32)" # NETPULSE_DEK
openssl rand -base64 48 # NETPULSE_JWT_SECRET
Далі:
docker compose up -d --build
docker compose run --rm --entrypoint netpulse-user cli \
-tenant default -login admin -role owner -name "Адміністратор"
Пароль команда спитає інтерактивно — щоб він не осів в історії оболонки й у списку процесів.
Інтерфейс — на https://<NETPULSE_DOMAIN>.
Підключення зонда
В інтерфейсі: Зонди → Додати зонд. Видане запрошення (np_enr_…)
одноразове — після реєстрації воно згоряє.
На машині, де стоятиме зонд:
docker run -d --name netpulse-agent --restart unless-stopped \
--cap-add NET_RAW \
-v netpulse-agent:/var/lib/netpulse \
netpulse/agent:dev \
-server netpulse.example.com:9443 \
-enroll np_enr_… \
-name "Зонд у Львові" \
-modules icmp,snmp,topology,ncm
Зонд обміняє запрошення на постійний токен і збереже його в
/var/lib/netpulse/agent.json (права 0600). Наступні запуски токена вже
не потребують — том із посвідченням має пережити перестворення
контейнера, інакше кожен старт вимагатиме нового запрошення.
Усі зʼєднання зонда вихідні: у мережі клієнта не треба відкривати жодного порту.
Бекап
Три речі, і всі три обовʼязкові:
- База — усе, крім секретів у відкритому вигляді.
NETPULSE_DEK— без нього паролі SSH і SNMP-community з бекапу не розшифрувати. У БД лежить лише шифротекст.NETPULSE_JWT_SECRET— без нього після відновлення всі активні сесії відваляться. Не смертельно, але користувачі помітять.
docker compose exec -T db \
pg_dump -U netpulse -d netpulse -Fc --no-owner \
> netpulse-$(date +%F).dump
Формат -Fc (custom), а не простий SQL: він стискається і дозволяє
відновлювати вибірково.
Ключі зберігати окремо від дампа — інакше сенс шифрування секретів зникає: той, хто дістав бекап, дістав і ключ до нього.
Том git-data бекапити не обовʼязково: тіла конфігів лежать
зашифрованими в базі, і репозиторій повністю відтворюється з неї —
docker compose run --rm --entrypoint netpulse-gitsync api
Зворотне невірно: з репозиторію базу не відновити. Тому джерелом істини лишається дамп, а Git — похідне сховище, яке коштує один запуск команди.
Автоматично, щодня
15 3 * * * cd /opt/netpulse && docker compose exec -T db pg_dump -U netpulse -d netpulse -Fc --no-owner > /var/backups/netpulse-$(date +\%F).dump && find /var/backups -name 'netpulse-*.dump' -mtime +30 -delete
Відновлення
TimescaleDB вимагає рамки навколо відновлення: без неї фонові процеси агрегації втручаються в наливання даних і дамп лягає пошкодженим.
docker compose stop api collector
docker compose exec -T db psql -U netpulse -d postgres -c \
'DROP DATABASE IF EXISTS netpulse; CREATE DATABASE netpulse;'
docker compose exec -T db psql -U netpulse -d netpulse -c \
'CREATE EXTENSION IF NOT EXISTS timescaledb;'
docker compose exec -T db psql -U netpulse -d netpulse -c \
'SELECT timescaledb_pre_restore();'
docker compose exec -T db pg_restore -U netpulse -d netpulse --no-owner \
< netpulse-2026-08-25.dump
docker compose exec -T db psql -U netpulse -d netpulse -c \
'SELECT timescaledb_post_restore();'
docker compose up -d api collector
У .env має лежати той самий NETPULSE_DEK, що й на момент дампа.
Інакше застосунок підніметься, але кожна спроба скористатись збереженим
паролем поверне помилку розшифрування — і виглядатиме це як зламані
креденшели, а не як втрачений ключ.
Перевірка після відновлення:
docker compose exec -T db psql -U netpulse -d netpulse -c \
'SELECT count(*) FROM core.devices;'
curl -sf https://<NETPULSE_DOMAIN>/healthz && echo OK
Оновлення
git pull
docker compose up -d --build
migrate відпрацює першим і не дасть піднятись API, якщо схема не
накотилась. Міграції йдуть по одній у транзакції; уже застосований файл
зі зміненою контрольною сумою зупиняє весь запуск — це захист від
мовчазного розходження схеми з кодом.
Відкат схеми не передбачений: зворотні міграції на даних телеметрії коштують дорожче, ніж відновлення з бекапу.
Ізоляція кабінетів (RLS)
Нова інсталяція вже під політиками — робити нічого не треба.
netpulse-migrate на чистій базі сам видає паролі ролям netpulse_app
і netpulse_worker, і застосунок з першої секунди ходить роллю без
BYPASSRLS.
Ізоляція тримається на двох незалежних рубежах: політика RLS у базі й
предикат tenant_id у кожному запиті коду. Другий потрібен окремо, бо
на гіпертаблицях RLS не працює взагалі — TimescaleDB не поєднує його зі
стисненням, а туди йде вся телеметрія.
Інсталяціям, старшим за 0063, застосунок і далі ходить роллю
netpulse — тобто суперкористувачем, який політики обходить, — і другий
рубіж вмикається окремою оборотною процедурою:
deploy/RLS-EXISTING-INSTALL.md. Вона не змінює даних. Робити її
разом з оновленням версії не варто: ламатись у них різне, і розбирати
доведеться одночасно.
Свій випадок видно одним запитом:
docker compose exec -T db psql -U netpulse -d netpulse -c "SELECT fresh FROM public.netpulse_install"
Зміна ключа шифрування
Ключі перелічуються через кому, новий — першим:
NETPULSE_DEK=np2=<новий hex>,np1=<старий hex>
Нові секрети шифруються першим ключем, старі читаються своїм. Прибирати старий ключ можна лише після того, як усі секрети перезаписані.
Чому саме так
Два образи, а не пʼять. api, collector, migrate, netpulse-user
і netpulse-secret — з одного модуля, з половиною спільного коду. Один
образ гарантує, що API і колектор ходять у схему БД однією версією; окремі
образи дають їм можливість розʼїхатись саме там, де це найдорожче.
Зонд — окремо: він їде в чужу мережу, і DSN, ключі шифрування та команди заведення користувачів не повинні бути в тому образі навіть як невикористані файли.
Міграції окремою службою. API піднімається в кількох примірниках; накочування схеми зі старту означало б гонку між ними.
TLS на проксі, а не в застосунку. Прострочений сертифікат на системі, яка сама має повідомляти про проблеми, — найгірший спосіб дізнатись про проблему. Caddy оновлює його сам.
Без домену
Домен потрібен для справжнього сертифіката: Let's Encrypt не видає їх на
IP-адреси. Поки домену немає, лишіть ACME_EMAIL порожнім — Caddy
випише самопідписаний, і система працюватиме одразу, з попередженням у
браузері.
Коли домен зʼявиться: замінити NETPULSE_DOMAIN, вписати ACME_EMAIL і
docker compose restart proxy. Caddyfile змонтований, тож перезбирати
образи не треба — але й up -d без restart його не перечитає.
Дашборд на телевізор
В інтерфейсі: Дашборд → На телевізор → Видати посилання. Отриману адресу відкривають на екрані в диспетчерській — вона не потребує входу.
Телевізор нікуди не залогиниш: сесія протермінується, браузер оновиться, і зранку на стіні висітиме форма входу замість карти мережі — рівно тоді, коли на неї дивляться.
Що дає посилання і чого не дає:
- тільки читання цього дашборда: його плитки, активні алерти й метрики тих хостів, які на ньому показані;
- метрики чужого хоста за ним не дістати навіть підбором ідентифікатора;
- решта кабінету — інвентар, конфіги, налаштування, секрети — недоступна;
- відкликається одним рухом, старе посилання одразу мертве.
Токен показується один раз. Видати нове можна будь-коли — попереднє при цьому перестає працювати.