Один коміт, а не десяток тематичних, свідомо: теми переплетені в
спільних файлах (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 серпня.
135 lines
9.7 KiB
SQL
135 lines
9.7 KiB
SQL
-- =====================================================================
|
||
-- NetPulse :: 0054_git_mirror.sql
|
||
-- Дзеркалення локального сховища версій на зовнішній Git
|
||
-- (Forgejo/Gitea, GitHub, GitLab).
|
||
--
|
||
-- Поля remote_url / remote_secret_id / mirror_enabled завела ще
|
||
-- 0006_ncm.sql із коментарем «Опційне дзеркалювання на зовнішній Git».
|
||
-- Модель була спроєктована, але жоден рядок Go її не читав. Ця міграція
|
||
-- не вигадує нову, а добудовує ту саму трьома групами полів: чим
|
||
-- автентифікуватись, кому довіряти на тому кінці й що сталось минулого
|
||
-- разу.
|
||
--
|
||
-- Навіщо дзеркало взагалі. Локальний репозиторій — це один диск. Тіла
|
||
-- конфігів лежать зашифрованими в базі, і з неї Git відтворюваний
|
||
-- (netpulse-gitsync), але база й репозиторій живуть на одній машині:
|
||
-- подія, яка забирає диск, забирає обидва. Дзеркало — це перша копія
|
||
-- історії за межами інсталяції.
|
||
--
|
||
-- Головне рішення міграції — те, чого тут НЕМАЄ: жодного поля, яке
|
||
-- дозволяло б дзеркалу впливати на збір. Push робить окремий фоновий
|
||
-- такт, і його відмова не змінює нічого в ncm.jobs і ncm.configs.
|
||
-- Недоступний Forgejo не має коштувати жодного бекапу.
|
||
-- =====================================================================
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Чим автентифікуватись
|
||
-- ---------------------------------------------------------------------
|
||
|
||
ALTER TABLE ncm.repos
|
||
-- Спосіб входу видно з URL (https:// проти ssh://), але зберігаємо
|
||
-- явно: у core.secrets лягає або токен, або приватний ключ, і що саме
|
||
-- там лежить, читач має дізнатись до розшифрування, а не після.
|
||
ADD COLUMN remote_auth text NOT NULL DEFAULT 'none'
|
||
CHECK (remote_auth IN ('none','https','ssh')),
|
||
|
||
-- Логін для HTTPS. GitHub і Forgejo приймають будь-який непорожній
|
||
-- логін, коли пароль — токен; GitLab вимагає конкретний. Тримати його
|
||
-- поруч із токеном у шифротексті означало б розбирати секрет заради
|
||
-- показу форми, тож логін тут відкритим текстом: він не секрет.
|
||
ADD COLUMN remote_user text,
|
||
|
||
-- Закріплений ключ SSH-сервера, рядок формату authorized_keys
|
||
-- ("ssh-ed25519 AAAA... "). Для мережевого заліза ключі свідомо не
|
||
-- звіряються (agent/internal/ncmx/transport.go) — там сотня коробок,
|
||
-- які перегенеровують ключ після кожної прошивки. Тут інша задача:
|
||
-- один сервер, один ключ, який людина бачить під час перевірки
|
||
-- з'єднання й підтверджує збереженням. Далі кожен push його звіряє.
|
||
--
|
||
-- Без закріпленого ключа push по SSH не робиться взагалі. Дзеркало
|
||
-- везе конфіги з ключами й хешами паролів за межі периметра; віддати
|
||
-- їх першому, хто відповів на порту 22, — не той компроміс, який тут
|
||
-- можна дозволити.
|
||
ADD COLUMN remote_host_key text;
|
||
|
||
COMMENT ON COLUMN ncm.repos.remote_secret_id IS
|
||
'Токен HTTPS або приватний ключ SSH; що саме — каже remote_auth';
|
||
COMMENT ON COLUMN ncm.repos.remote_host_key IS
|
||
'Закріплений ключ SSH-сервера (authorized_keys). Порожній = push по SSH заборонено';
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Що сталось минулого разу
|
||
-- ---------------------------------------------------------------------
|
||
|
||
-- Найважливіша частина міграції, і не через дані, а через те, що вона
|
||
-- робить видимим.
|
||
--
|
||
-- Дзеркало заводять, щоб пережити втрату локального диска. Дізнатись у
|
||
-- момент втрати, що воно тиждень не працювало, — це не мати його
|
||
-- взагалі. Тому стан останньої спроби зберігається завжди, а не лише
|
||
-- в журналі: сторінка має відповісти «коли востаннє доїхало» числом, а
|
||
-- не мовчанням.
|
||
ALTER TABLE ncm.repos
|
||
ADD COLUMN mirror_last_attempt_at timestamptz,
|
||
ADD COLUMN mirror_last_ok_at timestamptz,
|
||
-- Текст останньої помилки — уже очищений від токена (див.
|
||
-- gitstore/mirror.go, Scrub). У базу він лягає рівно в тому вигляді,
|
||
-- у якому його побачить людина.
|
||
ADD COLUMN mirror_last_error text,
|
||
-- Скільки спроб поспіль впало. Нуль після кожного успіху. З нього
|
||
-- рахується і затримка повтору, і поріг, за яким піднімається алерт.
|
||
ADD COLUMN mirror_fail_count int NOT NULL DEFAULT 0,
|
||
-- Коли пробувати наступного разу. Повтор із наростанням: недоступний
|
||
-- сервер не має отримувати з'єднання щохвилини цілий тиждень.
|
||
ADD COLUMN mirror_next_attempt_at timestamptz,
|
||
-- Відбиток локальних посилань на момент останнього ВДАЛОГО push.
|
||
-- Поки він не змінився, такт не ходить у мережу взагалі: дзеркалення
|
||
-- не має бути постійним трафіком на чужий сервер.
|
||
ADD COLUMN mirror_state_hash text,
|
||
ADD COLUMN mirror_refs int NOT NULL DEFAULT 0,
|
||
ADD COLUMN mirror_duration_ms int,
|
||
-- Одноразовий дозвіл перезаписати віддалене дерево.
|
||
--
|
||
-- Звичайний push іде без -f і на розбіжності відмовляється. Але
|
||
-- розбіжність буває законною: після відновлення локального
|
||
-- репозиторію з бази (netpulse-gitsync) коміти складаються заново, і
|
||
-- їхні ідентифікатори не збігаються з тими, що колись поїхали.
|
||
-- Тоді дзеркало доводиться перезаписати — і це має бути ОКРЕМИЙ рух
|
||
-- людини, яка бачить попередження, а не режим, у якому система живе
|
||
-- постійно. Прапорець гасне після першого вдалого push.
|
||
ADD COLUMN mirror_force_once boolean NOT NULL DEFAULT false;
|
||
|
||
COMMENT ON COLUMN ncm.repos.mirror_state_hash IS
|
||
'Відбиток refs на момент останнього вдалого push; збіг = у мережу не ходимо';
|
||
|
||
CREATE INDEX ncm_repos_mirror_idx ON ncm.repos (mirror_next_attempt_at)
|
||
WHERE mirror_enabled;
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Право
|
||
-- ---------------------------------------------------------------------
|
||
|
||
-- Окреме право, і навмисно не ncm:write.
|
||
--
|
||
-- ncm:write означає «може міняти розклад збору» — дія оборотна й нікуди
|
||
-- нічого не відправляє. Увімкнути дзеркало означає почати ВИВОЗИТИ весь
|
||
-- архів конфігів за межі периметра: у конфігах живуть community-рядки,
|
||
-- ключі й хеші паролів, а адресу призначення задає той, хто натискає.
|
||
-- Це рішення рівня організації, а не щоденна робота інженера — той
|
||
-- самий висновок, що й у 0037 для ncm:delete.
|
||
--
|
||
-- Читання стану дзеркала лишається на ncm:read, і це важливо: помітити,
|
||
-- що копія тиждень не їде, має могти той, хто щодня дивиться на
|
||
-- конфіги, а не лише власник.
|
||
INSERT INTO core.permissions (key, description) VALUES
|
||
('ncm:mirror', 'Налаштування дзеркалення архіву конфігів на зовнішній Git')
|
||
ON CONFLICT (key) DO NOTHING;
|
||
|
||
-- Лише власник і адмін.
|
||
INSERT INTO core.role_permissions (role_id, permission_key) VALUES
|
||
('00000000-0000-0000-0000-0000000000a1', 'ncm:mirror'),
|
||
('00000000-0000-0000-0000-0000000000a2', 'ncm:mirror')
|
||
ON CONFLICT DO NOTHING;
|
||
|
||
COMMENT ON TABLE ncm.repos IS
|
||
'Локальне сховище версій конфігів тенанта і його дзеркало на зовнішньому Git';
|