Netpulse_SasS/server/migrations/0006_ncm.sql
byrsapty 205dd5e079 Пакування: runner міграцій і вшитий у бінарник фронтенд
netpulse-migrate замість PowerShell-скрипта: у контейнері немає ані
psql, ані PowerShell, а тягнути клієнт Postgres в образ заради одного
запуску — це половина дистрибутива на порожньому місці.

Міграції вшиті через embed і переїхали в server/migrations: embed не
бачить нічого за межами кореня свого модуля, а міграції поруч із
бінарником, який їх накочує, не можуть розійтися версіями.

Накочування під advisory-блокуванням: два інстанси при rolling update
інакше застосували б ту саму міграцію двічі. Кожен файл в одній
транзакції разом із записом у schema_migrations; виняток — continuous
aggregates, які TimescaleDB забороняє в транзакції. Змінена вже
застосована міграція зупиняє запуск: у різних інсталяціях інакше
опиниться різна схема під одним номером.

Перевірено на чистій базі: 23 міграції, 101 таблиця, повторний запуск
каже «схема актуальна».

Веб віддає сам API через embed: на self-hosted це прибирає з інструкції
встановлення цілий компонент. Три політики кешування — назавжди для
assets із хешем у імені, ніколи для index.html, коротко для решти.

Знайдено живим прогоном: невідомий шлях під /api/ віддавав 200 з
index.html, і клієнт падав на розборі HTML як JSON замість чесного 404.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 01:01:55 +03:00

205 lines
11 KiB
SQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- =====================================================================
-- NetPulse :: 0006_ncm.sql
-- Network Config Management: репозиторії, бекапи конфігів, Git-версії,
-- diff, шаблони відповідності (compliance), відкат.
-- =====================================================================
-- Один Git-репозиторій на тенанта (bare repo під керуванням libgit2 на сервері).
-- Гілка на пристрій: refs/heads/device/<device_id>; файл: <site>/<device>.cfg
CREATE TABLE ncm.repos (
id uuid PRIMARY KEY DEFAULT core.new_id(),
tenant_id uuid NOT NULL REFERENCES core.tenants(id) ON DELETE CASCADE,
name text NOT NULL,
storage_path text NOT NULL, -- /var/lib/netpulse/git/<tenant>.git
default_branch text NOT NULL DEFAULT 'main',
-- Опційне дзеркалювання на зовнішній Git (GitHub/GitLab/Gitea)
remote_url text,
remote_secret_id uuid REFERENCES core.secrets(id) ON DELETE SET NULL,
mirror_enabled boolean NOT NULL DEFAULT false,
size_bytes bigint NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (tenant_id, name)
);
-- Профіль збору для конкретної моделі/вендора:
-- які команди виконати, як розпізнати prompt, що вирізати перед diff.
CREATE TABLE ncm.profiles (
id uuid PRIMARY KEY DEFAULT core.new_id(),
tenant_id uuid REFERENCES core.tenants(id) ON DELETE CASCADE, -- NULL = вбудований
key core.slug NOT NULL,
name text NOT NULL,
vendor text,
transport inv.credential_proto NOT NULL DEFAULT 'ssh',
-- ["terminal length 0","show running-config"]
commands jsonb NOT NULL DEFAULT '[]'::jsonb,
prompt_regex text,
enable_required boolean NOT NULL DEFAULT false,
-- Рядки, що змінюються щоразу (uptime, timestamps, хеші паролів) — не шумимо в diff
scrub_patterns jsonb NOT NULL DEFAULT '[]'::jsonb,
-- Маскування секретів перед збереженням у Git
redact_patterns jsonb NOT NULL DEFAULT '[]'::jsonb,
is_builtin boolean NOT NULL DEFAULT false,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX ncm_profiles_key_uniq
ON ncm.profiles (COALESCE(tenant_id,'00000000-0000-0000-0000-000000000000'::uuid), key);
-- Політика бекапу для пристрою (розклад + тригери)
CREATE TABLE ncm.device_policies (
device_id uuid PRIMARY KEY REFERENCES inv.devices(id) ON DELETE CASCADE,
tenant_id uuid NOT NULL REFERENCES core.tenants(id) ON DELETE CASCADE,
profile_id uuid REFERENCES ncm.profiles(id) ON DELETE SET NULL,
credential_id uuid REFERENCES inv.credentials(id) ON DELETE SET NULL,
enabled boolean NOT NULL DEFAULT true,
cron text NOT NULL DEFAULT '0 3 * * *',
-- Бекап за Syslog-подією зміни конфігу (%SYS-5-CONFIG_I)
on_syslog boolean NOT NULL DEFAULT true,
syslog_match text,
on_trap boolean NOT NULL DEFAULT false,
retention_versions int NOT NULL DEFAULT 100,
last_backup_at timestamptz,
next_backup_at timestamptz,
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX ncm_policies_schedule_idx ON ncm.device_policies (next_backup_at) WHERE enabled;
-- ---------------------------------------------------------------------
-- Задачі збору та їх результати
-- ---------------------------------------------------------------------
CREATE TYPE ncm.job_trigger AS ENUM ('schedule','manual','syslog','trap','api','discovery');
CREATE TYPE ncm.job_status AS ENUM ('queued','running','success','failed','unchanged','timeout');
CREATE TABLE ncm.jobs (
id uuid PRIMARY KEY DEFAULT core.new_id(),
tenant_id uuid NOT NULL REFERENCES core.tenants(id) ON DELETE CASCADE,
device_id uuid NOT NULL REFERENCES inv.devices(id) ON DELETE CASCADE,
agent_id uuid REFERENCES core.agents(id) ON DELETE SET NULL,
trigger ncm.job_trigger NOT NULL,
status ncm.job_status NOT NULL DEFAULT 'queued',
requested_by uuid REFERENCES core.users(id) ON DELETE SET NULL,
started_at timestamptz,
finished_at timestamptz,
duration_ms int,
error text,
log text, -- транскрипт сесії (для діагностики)
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX ncm_jobs_device_idx ON ncm.jobs (device_id, created_at DESC);
CREATE INDEX ncm_jobs_pending_idx ON ncm.jobs (status, created_at) WHERE status IN ('queued','running');
-- ---------------------------------------------------------------------
-- Версії конфігів. Тіло живе в Git; тут — метадані та індекс для пошуку.
-- ---------------------------------------------------------------------
CREATE TABLE ncm.configs (
id uuid PRIMARY KEY DEFAULT core.new_id(),
tenant_id uuid NOT NULL REFERENCES core.tenants(id) ON DELETE CASCADE,
device_id uuid NOT NULL REFERENCES inv.devices(id) ON DELETE CASCADE,
repo_id uuid NOT NULL REFERENCES ncm.repos(id) ON DELETE CASCADE,
job_id uuid REFERENCES ncm.jobs(id) ON DELETE SET NULL,
-- Git-координати
commit_sha text NOT NULL, -- 40 hex
blob_sha text NOT NULL,
branch text NOT NULL,
path text NOT NULL, -- kyiv-dc1/core-sw-01/running.cfg
-- Тип зрізу: running | startup | vlan | license | inventory
config_type text NOT NULL DEFAULT 'running',
size_bytes int NOT NULL,
line_count int,
-- sha256 нормалізованого (після scrub) тексту — швидка перевірка "змінилось?"
content_hash bytea NOT NULL,
-- Зашифрований AES-GCM-256 кеш тіла для миттєвого diff без читання Git
body_secret_id uuid REFERENCES core.secrets(id) ON DELETE SET NULL,
-- Порівняння з попередньою версією
prev_config_id uuid REFERENCES ncm.configs(id) ON DELETE SET NULL,
lines_added int NOT NULL DEFAULT 0,
lines_removed int NOT NULL DEFAULT 0,
is_change boolean NOT NULL DEFAULT false,
collected_at timestamptz NOT NULL DEFAULT now(),
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (device_id, config_type, commit_sha)
);
CREATE INDEX ncm_configs_device_time_idx ON ncm.configs (device_id, config_type, collected_at DESC);
CREATE INDEX ncm_configs_changes_idx ON ncm.configs (tenant_id, collected_at DESC) WHERE is_change;
CREATE INDEX ncm_configs_hash_idx ON ncm.configs (device_id, content_hash);
-- Кешовані diff-и для UI (щоб не рахувати щоразу при відкритті Telegram Mini App)
CREATE TABLE ncm.diffs (
id uuid PRIMARY KEY DEFAULT core.new_id(),
tenant_id uuid NOT NULL REFERENCES core.tenants(id) ON DELETE CASCADE,
from_config_id uuid NOT NULL REFERENCES ncm.configs(id) ON DELETE CASCADE,
to_config_id uuid NOT NULL REFERENCES ncm.configs(id) ON DELETE CASCADE,
format text NOT NULL DEFAULT 'unified', -- unified | side_by_side | json_hunks
-- [{"old_start":12,"old_lines":3,"new_start":12,"new_lines":4,"lines":[...]}]
hunks jsonb NOT NULL,
lines_added int NOT NULL DEFAULT 0,
lines_removed int NOT NULL DEFAULT 0,
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (from_config_id, to_config_id, format)
);
-- ---------------------------------------------------------------------
-- Compliance: правила відповідності конфігів (Enterprise)
-- ---------------------------------------------------------------------
CREATE TYPE ncm.rule_kind AS ENUM ('must_contain','must_not_contain','regex_match','regex_absent','jsonpath');
CREATE TYPE ncm.rule_severity AS ENUM ('info','low','medium','high','critical');
CREATE TABLE ncm.compliance_rules (
id uuid PRIMARY KEY DEFAULT core.new_id(),
tenant_id uuid NOT NULL REFERENCES core.tenants(id) ON DELETE CASCADE,
name text NOT NULL,
description text,
kind ncm.rule_kind NOT NULL,
pattern text NOT NULL,
severity ncm.rule_severity NOT NULL DEFAULT 'medium',
-- До яких пристроїв застосовувати (фільтр як у dynamic-групах)
selector jsonb NOT NULL DEFAULT '{}'::jsonb,
remediation text, -- підказка "як виправити"
enabled boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE ncm.compliance_results (
id uuid PRIMARY KEY DEFAULT core.new_id(),
tenant_id uuid NOT NULL REFERENCES core.tenants(id) ON DELETE CASCADE,
rule_id uuid NOT NULL REFERENCES ncm.compliance_rules(id) ON DELETE CASCADE,
device_id uuid NOT NULL REFERENCES inv.devices(id) ON DELETE CASCADE,
config_id uuid REFERENCES ncm.configs(id) ON DELETE SET NULL,
passed boolean NOT NULL,
details jsonb NOT NULL DEFAULT '{}'::jsonb,
checked_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (rule_id, device_id)
);
CREATE INDEX ncm_compliance_failed_idx ON ncm.compliance_results (tenant_id, checked_at DESC)
WHERE NOT passed;
-- ---------------------------------------------------------------------
-- Відкат конфігурації (push назад на пристрій) — небезпечна операція,
-- тому окрема сутність із двоетапним підтвердженням.
-- ---------------------------------------------------------------------
CREATE TYPE ncm.rollback_status AS ENUM ('draft','awaiting_approval','approved','applying','applied','failed','rejected');
CREATE TABLE ncm.rollbacks (
id uuid PRIMARY KEY DEFAULT core.new_id(),
tenant_id uuid NOT NULL REFERENCES core.tenants(id) ON DELETE CASCADE,
device_id uuid NOT NULL REFERENCES inv.devices(id) ON DELETE CASCADE,
target_config_id uuid NOT NULL REFERENCES ncm.configs(id) ON DELETE RESTRICT,
status ncm.rollback_status NOT NULL DEFAULT 'draft',
-- Команди, які реально підуть на пристрій (мінімальний набір змін)
commands jsonb NOT NULL DEFAULT '[]'::jsonb,
requested_by uuid REFERENCES core.users(id) ON DELETE SET NULL,
approved_by uuid REFERENCES core.users(id) ON DELETE SET NULL,
approved_at timestamptz,
applied_at timestamptz,
result_log text,
error text,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX ncm_rollbacks_device_idx ON ncm.rollbacks (device_id, created_at DESC);