# Розгортання 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` портів не публікують: до них ходять через нього. ## Перший запуск ```sh git clone <репозиторій> netpulse && cd netpulse cp deploy/.env.example .env ``` Заповнити `.env`. Три значення обовʼязкові й генеруються так: ```sh openssl rand -base64 24 # POSTGRES_PASSWORD echo "np1=$(openssl rand -hex 32)" # NETPULSE_DEK openssl rand -base64 48 # NETPULSE_JWT_SECRET ``` Далі: ```sh docker compose up -d --build docker compose run --rm api netpulse-user \ -tenant default -login admin -role owner -name "Адміністратор" ``` Пароль команда спитає інтерактивно — щоб він не осів в історії оболонки й у списку процесів. Інтерфейс — на `https://`. ## Підключення зонда В інтерфейсі: **Зонди → Додати зонд**. Видане запрошення (`np_enr_…`) одноразове — після реєстрації воно згоряє. На машині, де стоятиме зонд: ```sh 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). Наступні запуски токена вже не потребують — том із посвідченням має пережити перестворення контейнера, інакше кожен старт вимагатиме нового запрошення. Усі зʼєднання зонда вихідні: у мережі клієнта не треба відкривати жодного порту. ## Бекап Три речі, і всі три обовʼязкові: 1. **База** — усе, крім секретів у відкритому вигляді. 2. **`NETPULSE_DEK`** — без нього паролі SSH і SNMP-community з бекапу не розшифрувати. У БД лежить лише шифротекст. 3. **`NETPULSE_JWT_SECRET`** — без нього після відновлення всі активні сесії відваляться. Не смертельно, але користувачі помітять. ```sh docker compose exec -T db \ pg_dump -U netpulse -d netpulse -Fc --no-owner \ > netpulse-$(date +%F).dump ``` Формат `-Fc` (custom), а не простий SQL: він стискається і дозволяє відновлювати вибірково. Ключі зберігати **окремо від дампа** — інакше сенс шифрування секретів зникає: той, хто дістав бекап, дістав і ключ до нього. Том `git-data` бекапити не обовʼязково: тіла конфігів лежать зашифрованими в базі, і репозиторій повністю відтворюється з неї — ```sh docker compose run --rm --entrypoint netpulse-gitsync api ``` Зворотне невірно: з репозиторію базу не відновити. Тому джерелом істини лишається дамп, а Git — похідне сховище, яке коштує один запуск команди. ### Автоматично, щодня ```cron 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 вимагає рамки навколо відновлення: без неї фонові процеси агрегації втручаються в наливання даних і дамп лягає пошкодженим. ```sh 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`, що й на момент дампа. Інакше застосунок підніметься, але кожна спроба скористатись збереженим паролем поверне помилку розшифрування — і виглядатиме це як зламані креденшели, а не як втрачений ключ. Перевірка після відновлення: ```sh docker compose exec -T db psql -U netpulse -d netpulse -c \ 'SELECT count(*) FROM core.devices;' curl -sf https:///healthz && echo OK ``` ## Оновлення ```sh git pull docker compose up -d --build ``` `migrate` відпрацює першим і не дасть піднятись API, якщо схема не накотилась. Міграції йдуть по одній у транзакції; уже застосований файл зі зміненою контрольною сумою зупиняє весь запуск — це захист від мовчазного розходження схеми з кодом. Відкат схеми не передбачений: зворотні міграції на даних телеметрії коштують дорожче, ніж відновлення з бекапу. ## Зміна ключа шифрування Ключі перелічуються через кому, новий — першим: ``` 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` його не перечитає. ## Дашборд на телевізор В інтерфейсі: **Дашборд → На телевізор → Видати посилання**. Отриману адресу відкривають на екрані в диспетчерській — вона не потребує входу. Телевізор нікуди не залогиниш: сесія протермінується, браузер оновиться, і зранку на стіні висітиме форма входу замість карти мережі — рівно тоді, коли на неї дивляться. Що дає посилання і чого не дає: - **тільки читання** цього дашборда: його плитки, активні алерти й метрики тих хостів, які на ньому показані; - метрики чужого хоста за ним не дістати навіть підбором ідентифікатора; - решта кабінету — інвентар, конфіги, налаштування, секрети — недоступна; - відкликається одним рухом, старе посилання одразу мертве. Токен показується один раз. Видати нове можна будь-коли — попереднє при цьому перестає працювати.