From d73f693cd7308b1f102445576376c58882a2b527 Mon Sep 17 00:00:00 2001 From: byrsapty Date: Fri, 28 Aug 2026 23:58:18 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=96=D1=81=D0=BE=D1=87=D0=BD=D0=B8?= =?UTF-8?q?=D1=86=D1=8F:=20=D1=80=D0=B5=D0=B6=D0=B8=D0=BC=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=B2=D1=96=D1=80=D0=BA=D0=B8=20=D0=BE=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=BD=D1=8F=20=C2=AB=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D1=80=D0=B0=20=D0=B2=D0=B5=D1=80=D1=81=D1=96=D1=8F=20=E2=86=92?= =?UTF-8?q?=20=D0=BD=D0=BE=D0=B2=D0=B0=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Наявна пісочниця перевіряла лише установку з нуля. Шлях «стояла 0.1, стала 0.2» не перевіряло ніщо, а ламається він частіше: міграції поверх даних, зміна конфігурації, старий зонд проти нового колектора. «Попередня версія» — це КОМІТ, а не тег і не дамп. Тег неможливий: перевірка оновлення є вхідним квитком до першого тегу. Дамп не запускає старий колектор і старий зонд, тобто не бачить двох класів поломки з трьох. Коміт дає повне дерево, яке ВИКОНУЄТЬСЯ, а не описується — і тег теж ref, тож --from v0.1.0 запрацює в день першого тегу. Ізоляція закрита першою, зокрема неочевидна діра: обидві версії збиралися б під тегом netpulse/server:dev — тим самим, що ділить бойова інсталяція, і обірваний прогін лишив би :dev на старій збірці. ПРИМІТКА ДО ІСТОРІЇ: частина цих файлів уже потрапила в коміти c83324a, 5c9143c і 32442f8 — я підмів незакомічену роботу агента власним `git add -A`. Історію не переписую (на тих комітах уже пройшов CI), але називаю це тут, щоб `git log` не вводив в оману: 5c9143c про CI містить і 113 рядків цього режиму. Co-Authored-By: Claude Opus 5 --- HISTORY.md | 159 +++++++++++++++++++++++++++++++++++++++++++++++++++++ ROADMAP.md | 23 +++++++- 2 files changed, 179 insertions(+), 3 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 480ba5a..172cf40 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -7862,3 +7862,162 @@ git-дзеркало ДОСЛІВНО. Маскувати їх означає з * архів і дзеркало НЕ маскуються навмисно; * отже, доступ до дзеркала треба вважати рівносильним доступу до всіх паролів у мережі. Це не побічний ефект, а свідомо прийнята ціна. + +--- + +## 2026-08-28 — Пісочниця вміє другий шлях: оновлення з версії на версію + +Установку з нуля пісочниця перевіряє з Етапу 12, і саме вона знайшла дві +вади, які інакше зустрів би перший клієнт. Але з нуля клієнт ставить один +раз, а оновлюється щоразу — і саме цього шляху не перевіряло **ніщо**. +ROADMAP називав три причини, з яких ламається саме він: міграції поверх +наявних даних, зміна конфігурації, несумісність зонда з новим колектором. +Перевіряв їх перший клієнт. + +Тепер є `./netpulse sandbox upgrade`. + +### Питання, на яке довелось відповісти першим + +**Що взагалі означає «попередня версія», якщо версіонування ще немає?** +Тегів немає, реєстру немає, образ завжди `netpulse/server:dev`. Робити +вигляд, що питання не існує, не можна: від відповіді залежить, чого +вартий увесь прогін. + +Варіантів було три. + +* **Тег.** Найправильніший — і сьогодні неможливий. Перевірка оновлення є + вхідним квитком *до* першого тегу. Вимагати тег означало б вимагати те, + заради чого вона й пишеться; коло замикається на собі. +* **Збережений дамп бази.** Найдешевший і доводить найменше. Дамп — це + схема з даними, але не бінарники: він не запускає СТАРИЙ колектор і + СТАРИЙ зонд, тобто не бачить двох названих класів поломки з трьох. + Гірше інше: дамп старіє мовчки. Його зробила версія, яку вже ніхто не + збере, і коли прогін почервоніє, розрізнити «зламався код» і «протух + дамп» буде нічим. Перевірка, яка вміє брехати про причину, гірша за + відсутність перевірки — це вже сплачений урок. +* **Коміт git.** Обрано. Коміт — це ПОВНЕ дерево: Go-код, міграції, + compose-файл, Caddyfile, Dockerfile. З нього збираються справжні старі + образи, тобто попередня версія не описана, а **виконується**. Той самий + ref дасть той самий стенд і за півроку. + +І головне: це не тимчасове рішення. Тег у git — теж ref, тому в день +першого тегу тут не зміниться жодного рядка, `--from v0.1.0` запрацює +сам. + +Типовий ref **обчислюється**: найновіший коміт, у якого міграцій менше, +ніж у HEAD. Причина та сама, що й у решті установника — зелений прогін, +який нічого не перевірив, гірший за відсутність прогону. Оновлення без +жодної нової міграції доводить лише те, що служби перезапустились, і +мовчки видається за доказ, що міграції котяться поверх даних. Якщо в парі +версій нових міграцій немає, режим кричить про це двічі: при виборі й у +підсумку. + +### П'ять етапів, і кожен називає себе при падінні + +`ЗУПИНКА на кроці «схема й ролі»` не каже головного — чиї саме міграції +не накотились, старі чи нові. Тому з'явився рівень крупніший за крок: +`ЗУПИНКА на етапі «В · оновлення до нової версії», крок «міграції поверх +наявних даних»`. + +* **А** — попередня версія ставиться з нуля тим самим `cmd_install` і + проходить ту саму самоперевірку. Якщо впало тут, оновлення ні до чого: + зламана сама попередня версія. +* **Б** — наливання. Локації, 24 хости, доба ICMP і метрик із кроком + 5 хвилин (тобто кілька тисяч рядків через межу чанка Timescale), + конфіги NCM, тригер і алерти. Без даних накочування поверх не доводить + нічого: на порожній таблиці проходить будь-яка міграція. +* **В** — дамп, збірка нової версії, міграції, перезапуск служб. +* **Г** — та сама `selfcheck`, що й після чистої установки. Окремого + набору тверджень навмисно немає. +* **Д** — кількості «до» проти «після», по рядках. + +### Старий зонд проти нового колектора + +Найдорожча з трьох названих поломок і єдина, якої не видно з сервера: +зонди стоять у чужих мережах і оновлюються не разом із сервером. Тут це +перевіряється безкоштовно, бо `agent` живе під профілем compose, і +`up -d` його не чіпає: після оновлення служб контейнер зонда лишається на +образі **попередньої** версії. Питання одне — чи дійшов від нього хоч +один такт із міткою пізнішою за момент перезапуску. Дві хвилини чекання, +бо такт раз на 30 секунд плюс перепідключення; хибне червоне тут +відправило б шукати неіснуючу несумісність протоколу. Другим кроком зонд +перезбирається й перевіряється ще раз — заразом доводячи, що посвідчення +в томі переживає перестворення контейнера й нового запрошення не треба. + +### Ізоляція: що довелось закрити першим + +Окреме ім'я проєкту compose, окремі томи, окрема мережа, +`.env.sandbox-upgrade` замість бойового `.env`, порти від **18300** — +свідомо за межами діапазону 18080…18272, який перебирає перший режим. + +Але головне знайшлось не тут. Обидві версії збиралися б під тегом +`netpulse/server:dev` — тим самим, що ділить із пісочницею **бойова +інсталяція**. Друга збірка перетерла б першу, а обірваний прогін лишив би +`:dev` указувати на СТАРУ збірку. Тому тег став змінною: `sandbox-prev` +і `sandbox-new`, і `:dev` не згадується в цьому режимі жодного разу. +Прибирання зносить і теги. + +Дерево попередньої версії розгортається через `git archive` — він лише +читає; `worktree` завів би запис у `.git`, `checkout` зрушив би робочу +копію, тобто перевірка міняла б те, що перевіряє. Лежить дерево в +`.sandbox-prev/` поруч із проєктом, а не в `/tmp`: там часто tmpfs, і +другий примірник вихідних текстів ліг би в оперативну пам'ять — рівно ту, +якої пісочниці й так ледве вистачає. З контексту збірки його виключає +новий `.dockerignore` (заодно звідти прибрано `.env`: контекст іде +демонові цілком, і паролям бази там робити нічого). + +`./netpulse sandbox down` тепер прибирає **обидва** режими одразу. +Пам'ятати, у якому режимі стенд піднімали, людина не зобов'язана. + +### Дві хибні тривоги, вловлені до першого прогону + +Звірка «до й після» мала два місця, у яких вона збрехала б **червоним**: + +* Алерти рахувались у `alr.alerts` за `dedup_key`. Але фоновий такт + переносить погашені алерти в `alr.alerts_history`, куди `dedup_key` не + переїжджає взагалі, — і рядок, що переїхав, виглядав би як знищений. + Тепер налиті алерти мічені в `context`, а рахуються по обох таблицях: + `context` переїжджає разом із рядком. +* Ряди метрик рахувались за `metric_key='cpu.util'` без прив'язки до + хоста. Локальний зонд увесь цей час працює й пише свою телеметрію — + вона потрапила б у той самий лічильник. Тепер кожен точний запит + звужено до хостів `sb-sw-*`. + +Правил у звірці два, і різниця між ними змістовна. `exact` — рядки, які +налила сама пісочниця: їхня кількість не має права змінитись ані в який +бік, бо поменшало означає «знищило», а побільшало — «роздвоїло», і обидва +беззвучні. `min` — усе інше: система під час оновлення жива, і вимагати +там рівності означало б хибне червоне на кожному другому прогоні. + +### Чого цей режим НЕ доводить + +Абзац обов'язковий, і не з ввічливості: перевірка, яку вважають повнішою +за неї саму, шкідливіша за її відсутність. + +* **Гілку «наявна інсталяція» в міграторі.** База пісочниці народжується + чистою, тому `public.netpulse_install.fresh` лишається `true` назавжди + — і мігратор іде гілкою «чиста база: видано паролі ролям». Рядок + «наявна інсталяція: паролі ролей не чіпаю» тут не з'явиться ніколи, а + отже перехід стенду, зробленого до 0063 + (`deploy/RLS-EXISTING-INSTALL.md`), лишається неперевіреним. +* **Зміни в самому установнику між версіями.** Обидва етапи веде один і + той самий `./netpulse` — старий не виконується взагалі. Вимірювальний + прилад має бути тим самим на обох кінцях вимірювання, інакше різниця в + приладі читається як різниця у виробі. Ціна відома й прийнята. +* **Оновлення через кілька версій підряд** — перевіряється рівно один + стрибок. +* Усе те, чого не доводить і перший режим: Let's Encrypt, DNS, трапи на + 162/udp, поведінка під навантаженням. + +### І чесно про те, що не прогнано + +Написано й перевірено на розборі: `sh -n` і `dash -n` (POSIX, не bash), +`scripts/check.sh` — усе зелене; вибір ref і розгортання дерева прогнано +наживо на трьох випадках (автовибір, `--from` із двома новими міграціями, +`--from` без жодної), прибирання після кожного не лишило слідів. +**Самого стенду не піднято жодного разу: у цьому оточенні немає docker.** +Тобто збірка старого дерева, наливання SQL, накат міграцій поверх даних, +такт старого зонда й звірка кількостей — усе це поки доведене +міркуванням, а не запуском. Саме той стан, проти якого написана +пісочниця; перший прогін на стенді має бути перед першим тегом, а не +після. diff --git a/ROADMAP.md b/ROADMAP.md index 4944f70..2b88ad8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -482,6 +482,20 @@ CI-раннер ми запустили 2026-08-27. Бракує лише тог даних, оновити до нової, і прогнати ту саму самоперевірку. Без цього перший же реліз перевіряє себе на клієнтові. +**Написано 2026-08-28: `./netpulse sandbox upgrade`.** Попередня версія +береться з коміта git (тег — теж ref, тому `--from v0.1.0` запрацює в +день першого тегу без правок); ставиться з нуля, наливається хостами, +телеметрією, конфігами й алертами, оновлюється, проганяє ту саму +самоперевірку, і окремо звіряє кількості до й після. Заразом перевіряє +СТАРИЙ зонд проти НОВОГО колектора — контейнер зонда під профілем +compose, тому `up -d` його не чіпає. + +**Але на стенді це ще не бігало жодного разу** — писалось там, де немає +docker. Поки не прогнано наживо, цей рядок закритим вважати не можна: +установка, перевірена лише розбором, доводить рівно те саме, що й сухий +прогін. Подробиці — `deploy/README.md`, розділ «Пісочниця, режим +другий», і `HISTORY.md` за 2026-08-28. + **Сумісність зонда й сервера.** Зонди стоять у мережах клієнтів і оновлюються не одночасно з сервером. Правило «який зонд працює з яким сервером» ніде не записане й не перевіряється. @@ -509,9 +523,12 @@ CI-раннер ми запустили 2026-08-27. Бракує лише тог Це єдина річ, яка блокує продаж, і вона не про код. -1. **Оновлення з версії на версію не перевіряв ніхто.** Пісочниця - перевіряє установку з нуля. Шлях «стояла 0.1 — стала 0.2» ламається - частіше, а перевіряє його зараз перший клієнт. +1. **Оновлення з версії на версію: перевірка написана, наживо не + бігала.** `./netpulse sandbox upgrade` робить повний шлях — попередня + версія з нуля, дані, накат міграцій поверх них, та сама + самоперевірка, звірка кількостей і старий зонд проти нового + колектора. Лишається прогнати її на стенді: доки цього немає, + перевіряє шлях і далі перший клієнт. 2. **Немає версіонування.** Образ завжди `v0.1.0`, реєстру немає, тега немає. Поки цього немає, «оновитись» означає «залити дерево й зібрати», тобто те, що роблю я вручну.