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>
205 lines
11 KiB
SQL
205 lines
11 KiB
SQL
-- =====================================================================
|
||
-- 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);
|