ФАЙЛИ В /var/log/netpulse. rsyslog читає journald і розкладає по файлах
на службу. Драйвер docker лишається journald, а не syslog: syslog
віддав би рядки назовні й нічого не лишив докеру, тож `docker logs`
замовк би назавжди — і мовчання виглядало б як «служба нічого не пише».
Тепер працюють усі три шляхи: docker logs, journalctl, файли.
Дорогою три власні помилки, кожна виглядала як «rsyslog не працює»:
1. Умова матчила $!CONTAINER_NAME — метадані журналу. journalctl їх
бачить, а до правил rsyslog вони доходять не завжди. Каталог
створювався й лишався порожнім.
2. Тег «netpulse/{{.Name}}» здавався охайнішим, але rsyslog обриває
programname на скісній: для ВСІХ контейнерів вона ставала просто
«netpulse». Тег тепер — саме ім'я контейнера, воно й так має
префікс проєкту.
3. Шаблон `%msg:::sp-if-no-1st-sp,drop-last-lf%` давав ПОРОЖНІЙ
текст: файли були, рядки були, слів не було. Журнал виглядав
робочим і не містив нічого — найгірший різновид поломки.
Конфігурації в deploy/, щоб їхали клієнтам, а не лишались разовим
налаштуванням одного сервера.
МІНІКАРТА. Штрих рядка був 2 px при 3 px на рядок — просвіт в один
піксель. На дробовому масштабі екрана (1.25, 1.5 — тобто на більшості
ноутбуків) він губився при округленні, і рядки злипались у суцільну
пляму: мінікарта показувала не форму конфігу, а сірий прямокутник.
Тепер штрих 1 px, просвіт удвічі товщий за нього й переживає будь-яке
округлення. Малюнок став блідішим — це правильний бік розміну: на
мінікарту дивляться, щоб побачити структуру, а не прочитати текст.
П'ять паралельних задач. Найцінніше в них — не можливості, а знайдене.
0069 БІЛІНГ. Аудит 0009 показав, що перевірка ліміту не спрацювала б
жодного разу: isPlanLimit шукала слово «ліміт», а тригер писав
"device limit reached" англійською. Перше ж досягнення стелі дало б
клієнту 500 замість пояснення. Плюс три діри: тригер лише на INSERT
(стеля в 15 обходилась за чотири дії через архів), max_maps/max_agents/
max_users не перевіряло ніщо — тобто рівно те, чим відрізняються плани,
і license_keys була закрита політикою tenant_isolation з 0011, хоча
tenant_id там NULLABLE навмисно: головний сценарій self-hosted був
недосяжний.
Після закінчення ліцензії не вимикається нічого — замерзає лише ріст.
Моніторинг, що перестав моніторити через несплачений рахунок, це
аварія в мережі клієнта, спричинена нами.
0070 SLA. Джерелом обрано ts.icmp_1h, а не device_status_history:
остання не вміє сказати «ми не знали» — перехід пишеться лише при
зміні стану, тож доба мовчання зонда виглядає як доба роботи. Час
розкладено на чотири частини, і «немає даних» не додається ні до чого;
замість вибору між двома брехнями звіт каже, яку частку періоду він
бачив. Закритий період тримає тригер, а не домовленість у Go.
0071 ВІДПОВІДНІСТЬ. 20 правил, кожне прив'язане до родини: об'єднаний
вираз, що покриває Cisco й не покриває MikroTik, дав би «0 порушень» і
сховав сліпу пляму. Вендор не входить у перелік, доки для нього немає
зразка конфігу в тесті. TestBuiltinRulesAreNotAlwaysGreen вимагає, щоб
у кожного правила був конфіг, де воно спрацювало, І де ні.
ПІСОЧНИЦЯ УСТАНОВНИКА — та сама установка в ізольованому проєкті
compose. Знайшла дві справжні вади з трьох спроб:
* healthcheck бази ходив unix-сокетом, а споживачі по TCP. При
первинній ініціалізації Postgres слухає лише сокет — compose
вважав базу здоровою, migrate отримував connection refused. На
створеній базі цієї фази немає, тож вада чекала на першого клієнта;
* у білому переліку модулів API не було traps і filecfg — зонд із
приймачем трапів неможливо було зареєструвати взагалі.
ТЕСТИ СТОРІНОК: 137 → 252. Мережевий шар, права доступу, незворотні
дії, фільтри з адресного рядка. Підмінюється лише fetch і WebSocket —
api/client.ts працює справжній.
Обидві знайдені першим справжнім стартом на стенді — саме там, де автор
файла чесно написав «поведінка act_runner це міркування з документації,
а не спостереження».
1. Демон падав у циклі з «permission denied … docker.sock». Образ
раннера працює від uid 1000, а сокет належить root:docker з правами
лише для власника й групи. Додано group_add із номером групи сокета;
номер різний на різних машинах, тому змінна DOCKER_GID і команда для
пошуку в коментарі.
2. Прибиральник падав у циклі з «syntax error: unexpected end of file
(expecting "do")». `command:` рядком compose розбиває на слова за
правилами оболонки, тож /bin/sh -c отримував аргументом саме «while».
Тепер список з одного блокового скаляра — один аргумент, як і
задумано.
Друга особливо показова: YAML валідний, синтаксис sh валідний, і кожна
перевірка окремо зелена. Ламається лише поєднання, і лише при запуску.
Один коміт, а не десяток тематичних, свідомо: теми переплетені в
спільних файлах (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 серпня.
Образи зібрались із першого разу, але далі знайшлось три речі.
memlock без обмеження для Dragonfly дозволений не всюди: у контейнерній
віртуалізації ядро відмовляє, і контейнер не стартує взагалі. Швидкість
того не варта — обмеження знято.
Caddy нескінченно просив Let's Encrypt видати сертифікат на IP, чого той
не робить, і сайт лишався без TLS. Тепер порожня ACME_EMAIL означає
самопідписаний сертифікат. Плюс default_sni: SNI не містить IP-адрес,
тож без нього рукостискання обривалось «internal error».
У README команда заведення власника не працювала: у образу ENTRYPOINT, і
без --entrypoint ім'я команди йшло аргументом до netpulse-api. Заразом
netpulse-user навчився створювати перший кабінет — свіжа база не має
жодного, а користувач без кабінету нікуди не належить.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Телевізор нікуди не залогиниш — зранку на стіні висітиме форма входу
замість карти мережі. Тому /tv/<токен> розгалужується до перевірки
сесії, а доступ дає токен у посиланні.
Публічний зріз навмисно вузький: розкладка, алерти, хости й метрики
ЛИШЕ тих хостів, які показані на цьому дашборді. Ширший доступ був би
простішим у коді й перетворив би забутий лінк на ключ до кабінету.
Плитки ті самі, що в кабінеті: джерело даних підмінюється контекстом.
Дорогою знайдено дві помилки. Вбудовані icmp-тригери мали умову у
формі, якої движок не розуміє, і мовчали, засипаючи журнал помилками —
виправлено міграцією 0026 і в редакторі. База стенда виявилась у
SQL_ASCII: тепер міграція відмовляється накочуватись на не-UTF8, а
compose задає кодування явно.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
go-git, чистий Go без cgo. Голий репозиторій на тенанта, гілка на
пристрій за ідентифікатором (ім'я змінюють, історія не має від цього
розсипатись), файл за іменем. Однаковий вміст нового коміту не створює.
netpulse-gitsync переливає накопичену історію й відтворює втрачений
репозиторій із бази — тіла конфігів там і так лежать зашифрованими.
У вебі з'явився вибір версії, з якою порівнювати: сервер це вмів
(?from=), інтерфейс — ні.
Дорогою виправлено чотири тести, які CI запустив уперше з базою.
Серед них справжня помилка: машинний токен не міг читати мапи —
ACL мап отримував порожній рядок замість uuid і давав 500.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Два образи замість пʼяти: серверні команди їдуть з одного модуля й
мусять ходити в схему БД однією версією, а зонд лишається окремо —
у чужій мережі йому не місце з 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>