--- name: EvoBGP архитектура overview: Оба профиля по умолчанию на PostgreSQL (один движок, одни миграции). reference — микросервисы + PG + брокер; microVPS — evobgp-all + контейнер PG (жёсткий тюнинг). SQLite — опция только для одного контейнера без PG. REST API проработан в §7 плана (пути /v1, jobs, бандлы нод); OpenAPI — канон при появлении схемы. todos: - id: schema-db content: "Схема БД: PostgreSQL (основной); опционально SQLite (microVPS single-container); модули, ревизии, jobs" status: completed - id: bird-generator content: Формат bird.conf, include-фрагменты, фильтры, birdc configure status: completed - id: rest-jobs content: REST (refresh, apply, preview, rollback, bundle API) и async jobs status: completed - id: node-agent content: evobgp-agent + bird2 в Docker на мастере; evobgp-node + тот же паттерн на реплике (отдельная ВМ) status: completed - id: observability content: Метрики, алерты префиксов и BGP-сессий status: completed - id: docker-ms content: Compose profiles reference + microVPS; сервисы bird2 + evobgp-agent; сеть BGP; лимиты; логи; prune status: completed - id: go-modules content: Monorepo по §1 (дерево репозитория); internal/*, cmd/evobgp-all и cmd/* для reference status: completed - id: replica-bundle content: Подписанный бандл ревизии, API, evobgp-node status: completed - id: risk-hardening content: Двухфазный deploy, LKG, pin BIRD, политика миграций, обязательная подпись бандла на ноде status: completed - id: test-bird-matrix content: Матрица сценариев BIRD + golden-тесты birdfmt + bird -p в CI status: completed - id: ci-gitea content: .gitea/workflows/ci.yaml, документация для act runner status: completed - id: web-ui content: "Web UI (Svelte + shadcn-svelte) в отдельном контейнере: списки, расписание, мониторинг" status: completed isProject: false --- # EvoBGP — архитектурный план Control-plane на **Go**, анонс префиксов через **BIRD**, политика и история в **SQL-БД**, управление по **REST**. Репозиторий кода пока пустой — документ задаёт целевую архитектуру. --- ## Содержание 1. [Структура репозитория (файлы и пакеты)](#1-структура-репозитория-файлы-и-пакеты) 2. [Два эталонных профиля](#2-два-эталонных-профиля-развёртывания) 3. [Логическая модель и термины](#3-логическая-модель-общая-для-обоих-профилей) 4. [Сервисы и контейнеры](#4-сервисы-сравнение-профилей) - [4.1. Топология Docker Compose](#41-топология-docker-compose) 5. [База данных, ETL, ER-схема](#5-база-данных-etl-er-схема) 6. [Модули префиксов и FQDN](#6-модули-префиксов-as-cdn-домены) 7. [REST API](#7-rest-api) - [7.1. Общие соглашения](#71-общие-соглашения) - [7.2. Системные и служебные](#72-системные-и-служебные) - [7.3. Модули префиксов](#73-модули-префиксов-module) - [7.4. DoH-профили](#74-doh-профили-doh_profile) - [7.5. BGP community](#75-bgp-community-bgp_community) - [7.6. Пиры](#76-пиры-bgp_peer) - [7.7. Спикеры BIRD](#77-спикеры-bird-bgp_speaker) - [7.8. Ревизии конфигурации](#78-ревизии-конфигурации-config_revision) - [7.9. Применение конфигурации](#79-применение-конфигурации-deploy-bird) - [7.10. Задачи](#710-задачи-job_audit) - [7.11. Реплики и бандлы](#711-реплики-evobgp-node-бандлы) - [7.12. Глобальные настройки](#712-глобальные-настройки-опционально) - [7.13. Матрица прав](#713-матрица-прав-роли) - [7.14. Следующие итерации](#714-следующие-итерации-api) - [7.15. Связь API с разделами плана](#715-связь-api-с-разделами-плана) 8. [Генерация BIRD и ревизии](#8-генерация-bird-и-ревизии) 9. [Эксплуатация и масштаб пиров](#9-эксплуатация-и-масштаб) 10. [Реплика evobgp-node](#10-реплика-evobgp-node) 11. [Выбор СУБД](#11-выбор-субд) 12. [Профиль microVPS — детализация](#12-профиль-microvps-детализация) 13. [Снижение рисков (меры и процессы)](#13-снижение-рисков-меры-и-процессы) 14. [Тестирование BIRD2 и матрица сценариев](#14-тестирование-bird2-и-матрица-сценариев) 15. [CI/CD (Gitea Actions)](#15-cicd-gitea-actions) 16. [Риски и этапы внедрения](#16-риски-и-этапы-внедрения) 17. [Web UI (Svelte, shadcn-svelte)](#17-web-ui-svelte-shadcn-svelte) --- ## 1. Структура репозитория (файлы и пакеты) Monorepo на **Go**: общая логика в `internal/*`, отдельные бинарники в `cmd/*`. Цель — одна кодовая база для профилей **reference** (несколько процессов) и **microVPS** (`evobgp-all`). **Принципы:** - `**cmd/`** — только точки входа: флаги, переменные окружения, сборка зависимостей (DI), запуск; без доменной логики. - `**internal/`** — весь прикладной код; внешние модули Go не могут импортировать эти пакеты (правило компилятора). - **Слои:** `domain` (модели и инварианты без I/O) → `repository` и адаптеры к БД/внешним API → пакеты воркеров (оркестрация) → транспорт (`httpapi`, `jobs`, клиент к агенту). - **Один код — две упаковки:** микросервисы и `evobgp-all` используют одни и те же пакеты `internal/*`; отличается только набор процессов в `cmd/*`. - `**docs/`**, `**scripts/`** — как сейчас в репозитории (контракт API: [docs/openapi.yaml](docs/openapi.yaml), вспомогательные скрипты). - `**deploy/**` — Dockerfiles, `docker-compose` с профилями `reference` / `microVPS`, при необходимости entrypoint’ы для **bird2** / **evobgp-agent**; инфраструктура не смешивается с `internal/`. **Целевое дерево каталогов** (ориентир; имена подпакетов можно уточнить при первой итерации кода, **роли** каталогов зафиксированы): ``` EvoBGP/ ├── cmd/ │ ├── evobgp-api/ # REST + постановка jobs (reference) │ ├── evobgp-scheduler/ │ ├── evobgp-ingest/ │ ├── evobgp-render/ │ ├── evobgp-deploy/ │ ├── evobgp-all/ # microVPS: те же пакеты, один процесс / несколько goroutine │ ├── evobgp-agent/ # рядом с bird2: запись конфигов, birdc │ └── evobgp-node/ # pull бандла, проверка подписи, apply ├── internal/ │ ├── config/ # загрузка конфигурации (ENV, файлы) │ ├── platform/ # логирование, метрики, трассировка, health │ ├── db/ # пул, транзакции; embed миграций или вызов migrate │ ├── repository/ # SQL по сущностям (tenant, module, peer, revision, jobs, …) │ ├── domain/ # типы и правила без I/O (префиксы, community, ревизии) │ ├── httpapi/ # роуты OpenAPI, middleware, валидация, маппинг в сервисы │ ├── jobs/ # задачи: сейчас in-memory Registry в процессе API; PG job_audit / брокер — целевое расширение (см. примечание ниже §2) │ ├── scheduler/ # триггеры по расписанию модулей │ ├── ingest/ # CDN, DoH, нормализация, запись в БД │ ├── render/ # материализация префиксов, ревизия, текст артефактов BIRD │ ├── birdfmt/ # шаблоны и сборка include-фрагментов (альтернатива имени: bird/) │ ├── deploy/ # доставка на volume, взаимодействие с evobgp-agent / бандлы │ ├── bundle/ # упаковка и подпись бандла для evobgp-node │ └── signing/ # ключи, проверка подписи на ноде ├── migrations/ # SQL миграции PostgreSQL (единый набор для обоих профилей) ├── deploy/ │ ├── compose/ # docker-compose с профилями │ └── docker/ # Dockerfile на бинарь + общие слои ├── docs/ ├── scripts/ ├── go.mod ├── go.sum └── README.md ``` **Соответствие сервисам плана:** | Сервис (план) | Код | | -------------------- | ------------------------------------------------------------------------------------------------ | | **evobgp-api** | `cmd/evobgp-api`, `internal/httpapi`, `internal/jobs`, `internal/repository` | | **evobgp-scheduler** | `cmd/evobgp-scheduler`, `internal/scheduler`, `internal/jobs`, `internal/repository` | | **evobgp-ingest** | `cmd/evobgp-ingest`, `internal/ingest`, `internal/repository` | | **evobgp-render** | `cmd/evobgp-render`, `internal/render`, `internal/birdfmt`, `internal/repository` | | **evobgp-deploy** | `cmd/evobgp-deploy`, `internal/deploy`, `internal/bundle`, `internal/repository` | | **evobgp-all** | `cmd/evobgp-all` — поднимает HTTP и воркеры, импортируя те же `internal/`* | | **evobgp-agent** | `cmd/evobgp-agent`, `internal/deploy` (запись на volume, `birdc`) | | **evobgp-node** | `cmd/evobgp-node`, `internal/bundle`, `internal/signing`, `internal/birdfmt` / `internal/deploy` | **Поток пакетов (упрощённо):** ```mermaid flowchart TB subgraph entry [cmd] API[evobgp-api] W[scheduler ingest render deploy] end subgraph internal [internal] HTTP[httpapi] J[jobs] R[repository] DBpkg[db] REN[render] BF[birdfmt] DEP[deploy] end PG[(PostgreSQL)] AG[evobgp-agent] API --> HTTP HTTP --> J HTTP --> R W --> J W --> R R --> DBpkg DBpkg --> PG REN --> BF REN --> R DEP --> R DEP --> AG ``` --- ## 2. Два эталонных профиля развёртывания Один и тот же **код** в дереве `internal/` (все подпакеты), две **упаковки** в Docker Compose. | Критерий | **reference** (эталон) | **microVPS** | | ---------------------------- | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | **Назначение** | Production control-plane без жёсткого лимита RAM; горизонтальное масштабирование воркеров | Один хост: маленькая VPS / вложенная ВМ | | **CPU** | 2+ vCPU (рекомендуется) | **1 vCPU** | | **RAM** | 4+ ГиБ (ориентир) | **~1 ГиБ** | | **Диск под Docker + данные** | по объёму проекта | **7–10 ГиБ** (бюджет; не весь диск ОС) | | **ОС** | Linux (в т.ч. Ubuntu 22.04/24.04) | **Ubuntu 24.04 LTS** (референс) | | **Контейнеры Go** | 5 образов: `api`, `scheduler`, `ingest`, `render`, `deploy` | **1** образ: `evobgp-all` | | **БД** | **PostgreSQL** (отдельный сервис или managed) | **PostgreSQL** в контейнере (**жёсткий** тюнинг под 1 ГиБ) | | **Очередь** | **NATS JetStream** или **Redis Streams** (на выбор) | **Нет брокера** — `job_audit` в **PostgreSQL** + `SKIP LOCKED` / малый пул коннектов | | **Reverse proxy** | Traefik / Nginx (опционально) | **Нет** — прямой порт API | | **Object storage** | MinIO / S3 опционально для артефактов | Только **локальный volume** или небольшие BLOB в БД | | **BIRD** | **BIRD 2** в **Docker** (privileged / нужные capabilities) + контейнер `**evobgp-agent`** с общим volume | То же: **bird2** + **evobgp-agent** + **postgres** + **evobgp-all** | | **Контейнеры (итого)** | 5×Go + **postgres** + брокер + **bird2** + **evobgp-agent** (+ опц. proxy) | **4:** **evobgp-all** + **postgres** + **bird2** + **evobgp-agent** (+ опц. нода вне VPS) | ```mermaid flowchart LR subgraph ref [reference] R1[5x Go] R2[(PostgreSQL)] R3[(Broker)] R4[bird2] R5[evobgp-agent] R1 --> R2 R1 --> R3 R1 --> R5 R5 --> R4 end subgraph micro [microVPS] M1[evobgp-all] M2[(PostgreSQL)] M3[bird2] M4[evobgp-agent] M1 --> M2 M1 --> M4 M4 --> M3 end ``` > **Правило:** логика домена **одинакова**; **один движок БД — PostgreSQL** (одинаковые миграции и SQL). Отличаются число процессов Go, наличие брокера и **настройки** PG. > **Примечание (текущий код vs целевая очередь):** исполнение async jobs идёт через **in-memory `jobs.Registry` в процессе `evobgp-api` / `evobgp-all`**; таблица `job_audit` в миграциях заложена под будущую персистенцию и идемпотентность между процессами. В **reference** Compose отдельный `evobgp-scheduler` **не** читает эту очередь, а вызывает `POST .../modules/{id}/refresh` по HTTP. NATS в compose — для будущей интеграции; см. [docs/architecture.md](../../docs/architecture.md). > **Опция `microVPS_sqlite`:** один контейнер `evobgp-all` без PG — только если критичен абсолютный минимум контейнеров; иначе **не рекомендуется** как основной путь. --- ## 3. Логическая модель (общая для обоих профилей) - **BGP-сервер (ваш)** — процесс **BIRD**, который **анонсирует** префиксы и держит сессии. - **Клиенты** — **внешние** роутеры (BGP-пиры), подключающиеся **к вам** и **получающие** маршруты. Это не контейнеры EvoBGP. - **Мастер** — control-plane + **BIRD 2** в **контейнере** и контейнер `**evobgp-agent`** (общий volume: сгенерированные `bird.d`, сокет/канал для `birdc`). - **EvoBGP-нода (опционально)** — отдельная площадка: **только** pull подписанного **бандла** с мастера + **BIRD 2 в Docker** (тот же паттерн agent + bird2); **своей** полной БД и ingest **нет**. **Быстрый путь данных:** БД → ingest (CDN / DoH / AS; **IP_RANGES** только читаются при render из БД) → render (префиксы + community + ревизия) → deploy → файлы BIRD → `birdc configure` → клиентские сессии. --- ## 4. Сервисы (сравнение профилей) | Сервис | Назначение | reference | microVPS | | -------------------- | --------------------------------------------------------------- | -------------------------------- | ------------------------ | | **evobgp-api** | REST, CRUD, задачи, `/jobs` | отдельный контейнер | goroutine в `evobgp-all` | | **evobgp-scheduler** | Интервалы модулей/CDN → события | отдельный контейнер | goroutine в `evobgp-all` | | **evobgp-ingest** | CDN, DoH, материализация в БД | отдельный контейнер | goroutine в `evobgp-all` | | **evobgp-render** | Итоговые префиксы, ревизия, текст BIRD | отдельный контейнер | goroutine в `evobgp-all` | | **evobgp-deploy** | Доставка на мастерский BIRD, публикация бандла | отдельный контейнер | goroutine в `evobgp-all` | | **evobgp-agent** | Контейнер рядом с **bird2**: запись конфигов, `birdc configure` | то же (общий volume с **bird2**) | то же | | **evobgp-node** | Реплика: pull бандла, локальный BIRD | отдельный хост | **не на microVPS** | **Инфраструктура reference:** PostgreSQL, NATS или Redis, опционально Traefik, MinIO, **bird2** + **evobgp-agent** в Docker. ### Зависимости (профиль reference) ```mermaid flowchart TB subgraph cp [Control plane] API[evobgp-api] SCH[scheduler] ING[ingest] REN[render] DEP[deploy] end DB[(PostgreSQL)] MQ[(Broker)] API --> DB API --> MQ SCH --> DB SCH --> MQ ING --> DB ING --> MQ REN --> DB REN --> MQ DEP --> DB DEP --> MQ DEP --> AG[evobgp-agent] AG --> BIRD2[bird2] ``` ### 4.1. Топология Docker Compose Целевая упаковка: **все компоненты мастера в Compose**, включая **BIRD 2** (`bird2`). Пара **bird2 + evobgp-agent** делает **общий именованный volume** (или bind-mount) для каталога конфигурации и точки управления `birdc` (см. образ/entrypoint в репозитории). **Сеть для BGP:** на практике для входящих TCP **179** часто нужны `network_mode: host` (Linux), **macvlan/ipvlan** или публикация портов / отдельный L3-интерфейс — выбор фиксируется в профиле Compose и документации оператора; контейнер **bird2** получает **privileged** и набор **capabilities** (`NET_ADMIN`, и при необходимости `NET_RAW`), плюс `sysctls` под forwarding, если не на host-сети. ```mermaid flowchart TB subgraph refDocker [profile_reference] subgraph goRef [Сервисы Go] API1[evobgp-api] SCH1[scheduler] ING1[ingest] REN1[render] DEP1[deploy] end PG1[(postgres)] BR1[broker] subgraph bgpRef [Стек BGP Docker] AG1[evobgp-agent] B21[bird2] end VOL1[vol_bird_config] API1 --> PG1 API1 --> BR1 SCH1 --> PG1 SCH1 --> BR1 ING1 --> PG1 ING1 --> BR1 REN1 --> PG1 REN1 --> BR1 DEP1 --> PG1 DEP1 --> BR1 DEP1 --> AG1 AG1 --> VOL1 B21 --> VOL1 AG1 -->|birdc| B21 end subgraph microDocker [profile_microVPS] ALL[evobgp-all] PG2[(postgres)] subgraph bgpMicro [Стек BGP Docker] AG2[evobgp-agent] B22[bird2] end VOL2[vol_bird_config] ALL --> PG2 ALL --> AG2 AG2 --> VOL2 B22 --> VOL2 AG2 -->|birdc| B22 end ``` На **реплике** (`evobgp-node`) — тот же паттерн **bird2 + evobgp-agent** в Docker на отдельном хосте; pull бандла и `birdc configure` без полной БД (см. §10). --- ## 5. База данных, ETL, ER-схема ### 5.1. Группы сущностей | Область | Назначение | | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | **module** | Тип `AS_PREFIXES` / `CDN_CIDRS` / `DOMAINS` / `IP_RANGES`, расписание, DoH, приоритет | | **module_cdn_source** | URL + `source_kind` (формат тела списка); примеры наброска: `text_cidr_lines`, `json_prefix_list`, `custom`; ETag; свой `refresh_interval` опционально | | **doh_profile** | URL DoH, таймауты, секреты по ссылке | | **module_domain_entry** / **module_as_entry** / **module_ip_range_entry** | FQDN; или ASN/префикс; или **только CIDR/диапазон** + `community_id` (`IP_RANGES`) | | **bgp_community** | Справочник community для BIRD | | **bgp_peer** | Клиентский пир; `speaker_id` NULL = все спикеры в бандле | | **bgp_speaker** | master / replica, `last_applied_revision_id` | | **config_revision** / **revision_materialized_prefix** | История, diff, откат | | **job_audit** | Асинхронные задачи; в reference дополняет брокер | ### 5.2. ETL ```mermaid flowchart LR subgraph ex [Extract] CDN[CDN fetch] DOH[DoH] AS[AS из БД] IPR[IP ranges из БД] end subgraph tr [Transform] NORM[Нормализация CIDR] DEDUP[Дедуп] COMM[Community] end subgraph ld [Load] DB[(SQL БД)] REV[revision] ART[артефакты BIRD] end CDN --> NORM DOH --> NORM AS --> NORM IPR --> NORM NORM --> DEDUP --> COMM COMM --> DB COMM --> REV REV --> ART ``` ### 5.3. ER (упрощённо) ```mermaid erDiagram tenant ||--o{ module : owns tenant ||--o{ bgp_community : owns tenant ||--o{ bgp_peer : owns tenant ||--o{ bgp_speaker : owns doh_profile ||--o{ module : uses module ||--o{ module_cdn_source : contains module ||--o{ module_domain_entry : contains module ||--o{ module_as_entry : contains module ||--o{ module_ip_range_entry : contains bgp_community ||--o{ module_domain_entry : tags bgp_community ||--o{ module_as_entry : tags bgp_community ||--o{ module_ip_range_entry : tags bgp_community ||--o{ module_cdn_source : tags bgp_speaker ||--o{ bgp_peer : scope module ||--o{ config_revision : produces config_revision ||--o{ revision_materialized_prefix : snapshot module ||--o{ job_audit : tasks ``` ### 5.4. Пояснения к таблицам | Таблица | Назначение | Ключевые поля | Кто использует | | -------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | | **tenant** | Multi-tenant | `id`, `name`, `slug` | API, все сервисы | | **module** | Блок политики | `type`, `enabled`, `priority`, `doh_profile_id`, `refresh_interval_sec`, `cron_expr`, `default_community_id` | API, scheduler, ingest, render | | **doh_profile** | DoH | `url`, таймауты, ссылка на секрет | DOMAINS, ingest | | **module_cdn_source** | Строка CDN | `source_kind` (набросок: `text_cidr_lines`, `json_prefix_list`, `custom`), `url`, `etag`, `refresh_interval_sec`, `community_id` | ingest, render | | **module_domain_entry** | FQDN | `fqdn`, `community_id`, метаданные резолва | ingest, render | | **module_as_entry** | AS/префикс | `asn`, `prefix`, `community_id` | API, render | | **module_ip_range_entry** | Статический CIDR | `prefix` (CIDR), `community_id`; без ASN и без внешнего URL — модуль типа `IP_RANGES` | API, render | | **bgp_community** | Справочник | `kind`, значения, уникальность в tenant | API, render | | **bgp_peer** | Клиентский пир | neighbor, ASN, политики, `bgp_speaker_id` (NULL = все спикеры) | API, render, бандл | | **bgp_speaker** | Экземпляр BIRD | `role` master/replica, endpoint, `last_applied_revision_id` | deploy, нода | | **config_revision** | История | `hash`, артефакт, `parent_revision_id` | render, deploy, rollback | | **revision_materialized_prefix** | Снимок префиксов | `revision_id`, `prefix`, `community_id`, `source` | preview, diff, откат | | **job_audit** | Задачи | `kind`, `status`, `idempotency_key` | API, воркеры | Дополнительно: `**global_settings`** (KV); `**module_cdn_fetch_log`** (опционально, TTL). В **reference** очередь: брокер + `job_audit`; в **microVPS** — только БД и идемпотентность в `job_audit`. --- ## 6. Модули префиксов (AS / CDN / домены) ### Продуктовая трактовка В терминах продукта **модуль** — это один из **четырёх видов источника префиксов**: **AS** (`AS_PREFIXES`), **CDN** (`CDN_CIDRS`), **DOMAINS** (`DOMAINS`), **IP-диапазоны** (`IP_RANGES`). Наполнение задаётся либо **записями в таблице** (ASN или префикс + `community_id`; FQDN + `community_id` для доменов; **CIDR + `community_id`** для `IP_RANGES` без ASN и без внешнего URL), либо для CDN — **источником по URL** с полем `source_kind`, определяющим формат скачанного списка и парсер. Строка сущности `**module`** в БД — это экземпляр модуля выбранного типа (расписание, приоритет, DoH для доменов и т.д.); `**module_id` в API** — идентификатор экземпляра, а не «имя типа». При минимальном развёртывании (один bird2, мало пиров) допустим **один экземпляр на каждый нужный тип** или узкий набор экземпляров — это не противоречит модели. | Тип | Ввод | Поведение | | ---------------- | ---------------------------- | ------------------------------------------------------------------------- | | **AS** | ASN / префиксы | Таблица + опционально внешний `PrefixProvider` | | **CDN** | URL или список | Fetch, ETag, интервал модуля или строки | | **Домены** | FQDN | В BIRD попадают **только** IP-префиксы после DoH-резолва | | **IP-диапазоны** | CIDR (IPv4/IPv6) + community | Только таблица `module_ip_range_entry`; без IRR/ASN-семантики и без fetch | **FQDN:** воркер резолвит через **DoH-профиль** модуля → `/32` / `/128` (или политика) → материализация в БД → генерация static include для BIRD. --- ## 7. REST API **Статус:** черновик для согласования и реализации; каноничная машиночитаемая форма — **[docs/openapi.yaml](docs/openapi.yaml)** (по мере заполнения). Ниже — **единая проработка** путей и семантики (консолидация с [docs/evobgp-api-sketches.md](docs/evobgp-api-sketches.md)). Базовый префикс: `**/v1`**. Термины: [§3](#3-логическая-модель-общая-для-обоих-профилей), [§5](#5-база-данных-etl-er-схема), [§6](#6-модули-префиксов-as-cdn-домены). ### 7.1. Общие соглашения | Тема | Решение (набросок) | | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | | **Аутентификация** | Заголовок `Authorization: Bearer ` или mTLS на edge; ключи привязаны к tenant и роли. | | **Multi-tenant** | Все сущности в скоупе tenant: либо из ключа, либо явный префикс `X-Tenant-Id` (только для супер-ролей). | | **Идентификаторы** | UUID v7 или ULID в URL; в JSON — строки. | | **Время** | ISO 8601 UTC (`2026-04-03T12:00:00Z`). | | **Ошибки** | Тело `application/problem+json` (RFC 9457): `type`, `title`, `status`, `detail`, `instance`, опционально `errors[]` по полям. | | **Идемпотентность** | Для мутаций, создающих задачи или побочные эффекты: заголовок `Idempotency-Key` (опционально обязателен для `POST` apply/refresh). | | **Пагинация** | `?cursor=&limit=50` (cursor-based); ответ: `items`, `next_cursor`, `has_more`. | | **Асинхронные операции** | `202 Accepted`, заголовок `Location: /v1/jobs/{job_id}`; тело `{ "job_id", "status": "queued" }`. | | **Версионирование** | Несовместимые изменения — новый префикс `/v2`. | ### 7.2. Системные и служебные | Метод | Путь | Назначение | | ----- | ------------- | ------------------------------------------------------------ | | `GET` | `/v1/health` | Liveness (процесс жив). | | `GET` | `/v1/ready` | Readiness (БД, брокер при reference, и т.д.). | | `GET` | `/v1/version` | Версия сборки API и control-plane (`git_sha`, `build_time`). | ### 7.3. Модули префиксов (`module`) **Связь с продуктом.** В продуктовой формулировке **ASN / CDN / DOMAINS / IP-диапазоны** — это **четыре вида модулей**. Поле `**type`** в `POST /v1/modules` выбирает вид: `AS_PREFIXES`, `CDN_CIDRS`, `DOMAINS` или `**IP_RANGES`**. Дочерние ресурсы: `**as-entries`**, `**cdn-sources**`, `**domain-entries**`; `**ip-range-entries**` — статические **CIDR + `community_id`** (без ASN и без URL). `**module_id**` в пути — идентификатор **конкретного экземпляра** модуля, а не имя типа (согласовано с [§6](#6-модули-префиксов-as-cdn-домены)). | Метод | Путь | Описание | | -------- | ------------------------- | ----------------------------------------------------------------- | | `GET` | `/v1/modules` | Список модулей tenant (фильтры: `?type=`, `?enabled=`). | | `POST` | `/v1/modules` | Создать модуль. | | `GET` | `/v1/modules/{module_id}` | Детали модуля. | | `PATCH` | `/v1/modules/{module_id}` | Частичное обновление (расписание, DoH, приоритет, `enabled`). | | `DELETE` | `/v1/modules/{module_id}` | Мягкое удаление или `enabled=false` — зафиксировать в реализации. | **CDN-источники модуля** | Метод | Путь | Описание | | -------- | ------------------------------------------------- | --------------------------------------------------------------------- | | `GET` | `/v1/modules/{module_id}/cdn-sources` | Список строк CDN. | | `POST` | `/v1/modules/{module_id}/cdn-sources` | Добавить источник (URL + `source_kind` + опционально `community_id`). | | `PATCH` | `/v1/modules/{module_id}/cdn-sources/{source_id}` | URL, `source_kind`, `community_id`, свой `refresh_interval_sec`. | | `DELETE` | `/v1/modules/{module_id}/cdn-sources/{source_id}` | Удалить. | **Записи AS / домены** | Метод | Путь | Описание | | -------- | --------------------------------------------------- | --------------------- | | `GET` | `/v1/modules/{module_id}/as-entries` | Список ASN/префиксов. | | `POST` | `/v1/modules/{module_id}/as-entries` | Добавить. | | `PATCH` | `/v1/modules/{module_id}/as-entries/{entry_id}` | Обновить. | | `DELETE` | `/v1/modules/{module_id}/as-entries/{entry_id}` | Удалить. | | `GET` | `/v1/modules/{module_id}/domain-entries` | FQDN + community. | | `POST` | `/v1/modules/{module_id}/domain-entries` | Добавить. | | `PATCH` | `/v1/modules/{module_id}/domain-entries/{entry_id}` | Обновить. | | `DELETE` | `/v1/modules/{module_id}/domain-entries/{entry_id}` | Удалить. | **Записи IP-диапазонов** (только для `type: IP_RANGES`) | Метод | Путь | Описание | | -------- | ----------------------------------------------------- | ------------------------------------ | | `GET` | `/v1/modules/{module_id}/ip-range-entries` | Список CIDR + `community_id`. | | `POST` | `/v1/modules/{module_id}/ip-range-entries` | Добавить (`prefix`, `community_id`). | | `PATCH` | `/v1/modules/{module_id}/ip-range-entries/{entry_id}` | Обновить. | | `DELETE` | `/v1/modules/{module_id}/ip-range-entries/{entry_id}` | Удалить. | **Refresh (ingest)** | Метод | Путь | Описание | | ------ | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `POST` | `/v1/modules/{module_id}/refresh` | Запуск ingest для модуля (CDN / DoH / AS по типу). Для `**IP_RANGES`** обычно **не требуется** (данные только в БД); возможен `**204`** / no-op или `**400`**, если тип не поддерживает refresh — зафиксировать в реализации. | **Пример тела создания модуля (набросок)** ```json { "type": "CDN_CIDRS", "name": "edge-v4", "enabled": true, "priority": 10, "doh_profile_id": null, "refresh_interval_sec": 3600, "cron_expr": null, "default_community_id": "550e8400-e29b-41d4-a716-446655440000" } ``` Пример модуля `**IP_RANGES**`: `type: "IP_RANGES"`, `doh_profile_id: null`, далее строки через `ip-range-entries` с полями `prefix` (например `203.0.113.0/24`) и `community_id`. ### 7.4. DoH-профили (`doh_profile`) | Метод | Путь | Описание | | -------- | ----------------------- | ------------------------------------------------------------------------------------- | | `GET` | `/v1/doh-profiles` | Список. | | `POST` | `/v1/doh-profiles` | Создать (URL, таймауты; секрет — ссылка на vault id или отдельный `POST .../secret`). | | `GET` | `/v1/doh-profiles/{id}` | Детали (без раскрытия секрета). | | `PATCH` | `/v1/doh-profiles/{id}` | Обновить. | | `DELETE` | `/v1/doh-profiles/{id}` | Удалить, если не используется модулями. | ### 7.5. BGP community (bgp_community) | Метод | Путь | Описание | | -------- | ---------------------- | ------------------------------ | | `GET` | `/v1/communities` | Список справочника. | | `POST` | `/v1/communities` | Создать. | | `GET` | `/v1/communities/{id}` | Детали. | | `PATCH` | `/v1/communities/{id}` | Обновить. | | `DELETE` | `/v1/communities/{id}` | Удалить при отсутствии ссылок. | ### 7.6. Пиры (`bgp_peer`) | Метод | Путь | Описание | | -------- | ---------------- | ------------------------------------------------------------------------------ | | `GET` | `/v1/peers` | Список (`?speaker_id=`, пагинация). | | `POST` | `/v1/peers` | Создать пира. | | `GET` | `/v1/peers/{id}` | Детали. | | `PATCH` | `/v1/peers/{id}` | Политики, neighbor, ASN, привязка к `bgp_speaker_id` или `null` = все спикеры. | | `DELETE` | `/v1/peers/{id}` | Удалить / отключить. | ### 7.7. Спикеры BIRD (`bgp_speaker`) | Метод | Путь | Описание | | ------- | ------------------- | ------------------------------------------ | | `GET` | `/v1/speakers` | Список (master / replica, endpoint). | | `POST` | `/v1/speakers` | Зарегистрировать спикер (реплика, canary). | | `GET` | `/v1/speakers/{id}` | Детали + `last_applied_revision_id`. | | `PATCH` | `/v1/speakers/{id}` | Метаданные, endpoint. | ### 7.8. Ревизии конфигурации (config_revision) | Метод | Путь | Описание | | ------ | -------------------------------------- | --------------------------------------------------------------- | | `GET` | `/v1/revisions` | История ревизий (`?module_id=`, `?limit=`). | | `GET` | `/v1/revisions/{revision_id}` | Метаданные: хэш, родитель, время, артефакты. | | `GET` | `/v1/revisions/{revision_id}/prefixes` | Материализованный снимок префиксов (пагинация). | | `GET` | `/v1/revisions/{revision_id}/preview` | Превью фрагментов BIRD (read-only, без apply). | | `POST` | `/v1/revisions/{revision_id}/rollback` | Создать **новую** ревизию с содержимым отката; часто `**202`**. | **Сравнение ревизий (набросок)** | Метод | Путь | Описание | | ----- | ---------------------------- | ----------------------------------------------------------------------------- | | `GET` | `/v1/revisions/{a}/diff/{b}` | Diff префиксов / метаданных (формат зафиксировать: JSON patch или табличный). | ### 7.9. Применение конфигурации (deploy, BIRD) | Метод | Путь | Описание | | ------ | ------------------------- | --------------------------------------------------------------------------------------------- | | `POST` | `/v1/apply` | Применить текущую целевую ревизию на всех спикерах (или по политике по умолчанию). `**202`**. | | `POST` | `/v1/speakers/{id}/apply` | Применить на одном спикере (canary). `**202`**. | | `POST` | `/v1/bird/reload` | Опционально: явный мягкий reload политики (если отделён от apply); иначе часть `apply`. | **Тело `POST /v1/apply` (набросок, опционально)** ```json { "revision_id": "01JQXYZ...", "strategy": "all_speakers", "dry_run": false } ``` Связь с безопасным применением: [§13](#13-снижение-рисков-меры-и-процессы) (двухфазный deploy, LKG). ### 7.10. Задачи (job_audit) | Метод | Путь | Описание | | ------ | -------------------------- | --------------------------------------------- | | `GET` | `/v1/jobs` | Список задач (`?status=`, `?kind=`, cursor). | | `GET` | `/v1/jobs/{job_id}` | Статус, прогресс, ошибка, связанные сущности. | | `POST` | `/v1/jobs/{job_id}/cancel` | Запрос отмены (best-effort). | **Пример ответа `GET /v1/jobs/{id}`** ```json { "job_id": "01JQXYZ...", "kind": "module_refresh", "status": "running", "idempotency_key": "client-abc-123", "created_at": "2026-04-03T10:00:00Z", "started_at": "2026-04-03T10:00:01Z", "finished_at": null, "error": null, "meta": { "module_id": "01JQM..." } } ``` ### 7.11. Реплики evobgp-node: бандлы Вызываются **нодой** с отдельным ключом / mTLS (роль `node`). См. также [§10](#10-реплика-evobgp-node). | Метод | Путь | Описание | | ------ | ------------------------------------------------ | -------------------------------------------------------------------------------------- | | `GET` | `/v1/speakers/{speaker_id}/revisions/latest` | Указатель на последнюю опубликованную ревизию для ноды. | | `GET` | `/v1/speakers/{speaker_id}/bundle/{revision_id}` | Скачивание подписанного бандла (архив + `manifest.json` + подпись). | | `POST` | `/v1/nodes/enroll` | Регистрация ноды (обмен ключами, привязка к `speaker_id`) — детали протокола отдельно. | Заголовки для бандла: `Content-Type: application/octet-stream` или multipart; целостность по `manifest` (SHA-256) и подписи (например Ed25519) — обязательно на ноде в production ([§13](#13-снижение-рисков-меры-и-процессы)). ### 7.12. Глобальные настройки (опционально) | Метод | Путь | Описание | | ------- | -------------- | ------------------------------------------------------- | | `GET` | `/v1/settings` | KV вроде `global_settings` (лимиты CDN, feature flags). | | `PATCH` | `/v1/settings` | Частичное обновление (только роль operator). | ### 7.13. Матрица прав (роли) | Ресурс | `viewer` | `editor` | `operator` | `node` | | -------------------------- | -------- | -------- | ---------- | ------ | | GET модули, ревизии, peers | да | да | да | нет* | | PATCH модули, peers | нет | да | да | нет | | apply, rollback | нет | нет | да | нет | | bundle / enroll | нет | нет | нет | да | Нода не ходит в общий CRUD; только [§7.11](#711-реплики-evobgp-node-бандлы). ### 7.14. Следующие итерации API - Полная **OpenAPI 3.1** в `docs/openapi.yaml` по этому разделу. - Webhooks: `POST` на URL клиента по завершении `job` (опционально). - SSE/WebSocket для стрима статуса долгих jobs. - Rate limits по ключу и по tenant в ответах (`RateLimit-*` заголовки). ### 7.15. Связь API с разделами плана | Тема плана | Подраздел §7 | | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | REST, jobs, идемпотентность | [7.1](#71-общие-соглашения), [7.10](#710-задачи-job_audit) | | refresh, apply, rollback, preview, `IP_RANGES` | [7.3](#73-модули-префиксов-module), [7.8](#78-ревизии-конфигурации-config_revision), [7.9](#79-применение-конфигурации-deploy-bird) | | peers, speakers, communities, DoH | [7.4](#74-doh-профили-doh_profile)–[7.7](#77-спикеры-bird-bgp_speaker) | | bundle API, нода | [7.11](#711-реплики-evobgp-node-бандлы) | | Контрактные тесты HTTP | [§14](#14-тестирование-bird2-и-матрица-сценариев) + `openapi.yaml` | Черновик в репозитории [docs/evobgp-api-sketches.md](docs/evobgp-api-sketches.md) держать **синхронным** с §7 при согласовании изменений (или пометить файл указателем на план как единый источник). --- ## 8. Генерация BIRD и ревизии - Фрагменты `bird.d/*.conf`, `include`, фильтры, `peers.conf`, community из справочника. - Ревизия: хэш набора префиксов, артефакты, откат = новая ревизия со старым содержимым. --- ## 9. Эксплуатация и масштаб - До **~20+** клиентских пиров — строки `bgp_peer`, не отдельные хосты EvoBGP. - Rate-limit CDN, per-module cooldown. - Canary: несколько `bgp_speaker` или подмножество пиров по фильтру. - Метрики: размер префикс-сета, ошибки DoH/CDN, `birdc show protocols`. - Секреты BGP — Vault / K8s secrets, не plaintext в БД. - Last-known-good конфиг на **volume** / bind-mount, общий для **bird2** и **evobgp-agent** (или снимок на хосте при bind-mount). --- ## 10. Реплика evobgp-node 1. Render на мастере упаковывает **бандл** (как для мастерского BIRD) + **manifest** (SHA-256) + **подпись** (например Ed25519). 2. **evobgp-node**: fetch → проверка → распаковка → `birdc configure`. 3. Общие фильтры/префиксы **идентичны**; `local.conf` на ноде (router id, source) — вне бандла. 4. Риски: дубли анонсов, отставание ревизий, компрометация без подписи. --- ## 11. Выбор СУБД > **Решение по умолчанию:** **PostgreSQL** в **обоих** профилях — один тип миграций, один SQL-диалект в коде (`pgx` / `database/sql`), проще сопровождение и перенос с microVPS на reference без смены БД. | Движок | Роль в плане | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | **PostgreSQL** | **Основной** для `reference` и `microVPS` (различаются только ресурсы и `postgresql.conf`). | | **SQLite** | **Опция** `microVPS_sqlite`: один контейнер без PG — только при жёстком лимите «ровно один контейнер приложения+БД в одном процессе». | | **MySQL / MariaDB** | **Опционально** по требованию заказчика/хостинга; тот же слой DAO через второй драйвер — вне дефолтного пути. | | **rqlite** | HA без отдельного DBA Postgres; **+контейнеры**; на 1 ГиБ тесно. | ### Почему PG «не много» и всё же два контейнера на microVPS При **узкой** конфигурации (`shared_buffers` **64–128 МиБ**, `max_connections` **15–30**, **один** пул коннектов в `evobgp-all`, без сотен долгоживущих backend’ов) суммарный вклад PG **сопоставим** с практичным использованием SQLite в том же объёме RAM, зато: - нет отличий DDL/типов между профилями; - нормальные **advisory locks** / `SKIP LOCKED` для очереди `job_audit`; - проще подключить **внешний** managed Postgres при росте. На **microVPS** отдельные контейнеры `**postgres`** и стек **bird2 + evobgp-agent** — **осознанная плата** за единообразие с **reference** (минимум: `evobgp-all` + `postgres` + `bird2` + `evobgp-agent`). ### PostgreSQL: память и CPU (ориентиры) | Сценарий соединения | RAM на backend | | ---------------------- | -------------- | | Чистый idle | ~1.5 МиБ | | После простых запросов | ~10–11 МиБ | | После тяжёлых / temp | ~14.5 МиБ | Плюс `shared_buffers`, relation cache при огромной схеме, autovacuum. **reference:** при многих процессах Go — **PgBouncer** (transaction pooling). **microVPS:** пулер часто **не нужен**, если суммарно **≤10** реальных коннектов к PG. Таймауты: `idle_in_transaction_session_timeout`, `statement_timeout`, параметры `tcp_keepalives`_*; мониторинг `pg_stat_activity`. ### rqlite Raft, HTTP/`gorqlite`; для HA control-plane; не замена дефолтного PG в плане без отдельного решения. --- ## 12. Профиль microVPS — детализация > **Железо:** 1 vCPU · ~1024 МиБ RAM · **7–10 ГиБ** SSD под Docker + данные + логи · Ubuntu 24.04. Рекомендуется **swap 512 МиБ–1 ГиБ**, если его нет. ### Контейнеры | Вариант | Состав | | --------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | **A (целевой)** | **evobgp-all** + **postgres** + **bird2** + **evobgp-agent** (общий volume конфигов BIRD; см. [§4.1](#41-топология-docker-compose)) | | **B (опция)** | Без отдельного PG — профиль **microVPS_sqlite** + тот же стек **bird2** + **evobgp-agent** | | **C (legacy)** | BIRD только на хосте ОС — **не целевой путь**, только для отладки или жёстких ограничений Docker | ### Бюджет диска 7–10 ГиБ | Статья | Ориентир | | ---------------- | ------------------------------------- | | Образ evobgp-all | ~50–150 МиБ | | Образ PostgreSQL | ~80–200 МиБ (слои образа) | | Данные PG | ~200 МиБ – 2 ГиБ (политика ревизий) | | Конфиги / бандлы | ~10–200 МиБ | | Логи Docker | **max-size** / **max-file** в compose | | Prune | `docker system prune` по расписанию | | Резерв ОС/пики | ≥1–2 ГиБ | ### CPU и RAM - Worker pool ingest/render: **1–2**; без агрессивного параллелизма на одном ядре. - `deploy.resources`: лимиты на **evobgp-all**, **postgres**, **bird2**, **evobgp-agent** (на PG ориентир **256m–384m**, на пару **bird2+agent** заложить **128m–256m** — уточнить по `docker stats`). - Ориентир суммарно: **~400–750 МиБ** простой, **~550–900 МиБ** пик (**PG + Go + bird2 + система**) — на **1 ГиБ** обычно нужны **swap** и жёсткий тюнинг PG/`bird2`. ### Tuning PostgreSQL (microVPS) Пример направлений (не копировать слепо — проверить по мониторингу): - `shared_buffers` = **64–128 МиБ**; `max_connections` = **20–40**; `work_mem` умеренно низкий. - Отключить или минимизировать `parallel_workers` на 1 vCPU. - `effective_cache_size` подсказка планировщику без выделения RAM. ### Tuning PostgreSQL (reference) Обычные практики под размер ВМ; **PgBouncer** при многих сервисах Go; резерв под autovacuum и пики ingest. --- ## 13. Снижение рисков (меры и процессы) Дополняет [§9](#9-эксплуатация-и-масштаб) и [§10](#10-реплика-evobgp-node) конкретными обязательными практиками. ### Конфигурация BIRD: безопасное применение - **Двухфазный deploy:** (1) запись новой ревизии во **временный** каталог на общем volume и проверка `**bird -c -p`** (парсинг без запуска демона; см. `bird(8)`); (2) только при нулевом коде выхода — **атомарная** подмена активных файлов (rename) и `**birdc configure`**. Опционально перед фазой (2) — `preview`/diff в control-plane (REST). - **Last-known-good (LKG):** хранить на volume **предыдущую** применённую ревизию; при неуспехе `configure` или ненулевом exit **автоматически** восстановить файлы LKG, зафиксировать событие в логах/метриках и **не** оставлять BIRD в полусобранном состоянии. - **Canary в production:** перед полным apply — отдельный `bgp_speaker` или подмножество `bgp_peer` (см. [§9](#9-эксплуатация-и-масштаб)); полный выкат только после проверки сессий/префиксов на канареечном спикере. ### Данные, очередь, microVPS - **Миграции:** в основной ветке — только **вперёд**; откат схемы — явные down-миграции (если приняты в процессе) или восстановление БД из бэкапа; политика фиксируется в операторской документации. - **Jobs:** обязательные `**idempotency_key`** и уникальность в `job_audit` там, где это предотвращает двойной apply/reload. - **microVPS:** пороги мониторинга на рост таблиц ревизий/артефактов, **retention** старых ревизий, лимиты ротации логов Docker (см. [§12](#12-профиль-microvps-детализация)) — с **алертами** при приближении к лимиту диска и OOM. ### Безопасность - **evobgp-node:** в production **обязательна** проверка **подписи** бандла (отдельно от TLS транспорта); ключ подписи не смешивать с другими ролями. - Секреты BGP (пароли, ключи) — только secret store / Docker secrets; **не** логировать полные конфиги с секретами. ### Поставка и совместимость - В **Dockerfile** / Compose зафиксировать **версию образа BIRD 2** (тег minor или digest), совпадающую с образом, в котором выполняется `**bird -p`** в CI ([§15](#15-cicd-gitea-actions)). - **Статический каркас** (router id, локальные интерфейсы, операторские правки) — в файлах **вне** автогенерируемых фрагментов; сгенерированное — только в согласованных путях `bird.d/` (см. [§8](#8-генерация-bird-и-ревизии), [§10](#10-реплика-evobgp-node)). ### Порядок внедрения (уточнение) Перед полным набором микросервисов целесообразен параллельный этап: **контракт OpenAPI + каркас `internal/birdfmt` + golden-тесты + проверка `bird -p` в CI** ([§14](#14-тестирование-bird2-и-матрица-сценариев), [§15](#15-cicd-gitea-actions)), затем миграции PG и один вертикальный сценарий (например `IP_RANGES`). --- ## 14. Тестирование BIRD2 и матрица сценариев Цель — покрыть **поверхность генератора** EvoBGP, а не весь язык BIRD. Комбинации фиксируются **матрицей** и каталогом сценариев в репозитории (`internal/birdfmt/testdata/scenarios/`). ### Матрица покрытия (ориентир) | Измерение | Варианты для покрытия | | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | Роль спикера | master (полный набор фрагментов); реплика / **бандл** для `evobgp-node` (manifest + подмножество includes + локальный `local.conf`) | | Типы модулей (выход render) | `AS_PREFIXES`, `CDN_CIDRS`, `DOMAINS` (материализованные префиксы), `IP_RANGES` — минимум по одному сценарию; комбинация **2+ типов** в одной ревизии | | Пиры | при поддержке каркасом — только static; **1× IPv4**, **1× IPv6**, несколько пиров, смешанный v4/v6 | | Community | каждый поддерживаемый `kind` в справочнике + вариант без community (default) | | Фильтры | экспорт «разрешить анонс»; при генерации — отрицательные кейсы (reject) | | Граничные данные | пустой префикс-лист; один префикс; большой список (нагрузка на размер файла) | | Шаблоны include | каждый именуемый фрагмент из `internal/birdfmt` участвует хотя бы в одном интеграционном сценарии | По мере расширения генератора матрица **дополняется**; регрессия — новыми строками в таблице тестов и при необходимости новыми подкаталогами сценариев. ### Виды тестов 1. **Unit / snapshot (Go):** пакет `internal/birdfmt` — вход из фикстур, выход сравнивается с `*.golden` или `txtar`. 2. **Синтаксис BIRD в CI:** для каждого сценария с `bird.conf` выполняется `**bird -c … -p`** в контейнере с **той же major/minor версией BIRD 2**, что в production ([§13](#13-снижение-рисков-меры-и-процессы)). 3. **Позже:** контрактные тесты HTTP по `docs/openapi.yaml` и сценариям из [§7](#7-rest-api) после реализации `internal/httpapi`. --- ## 15. CI/CD (Gitea Actions) - Workflows: каталог `**.gitea/workflows/`** в корне репозитория; синтаксис совместим с GitHub Actions ([документация Gitea Actions](https://docs.gitea.com/usage/actions/quickstart/)). - Нужен зарегистрированный **act runner** с меткой `ubuntu-latest` (или согласованной с инсталляцией) и при job с Docker — доступ **Docker** на runner. - Рекомендуемый pipeline: **lint OpenAPI** (`npx @redocly/cli lint docs/openapi.yaml`), `**go vet` / `go test` / `go build ./...`**, **проверка всех `testdata/scenarios/*/bird.conf` через `bird -p`** (см. workflow в репозитории). --- ## 16. Риски и этапы внедрения ### Риски - Домены и устаревшие IP; DoH down; IRR/RIR vs локальные фильтры; community-ошибки; дубли при master+node; **microVPS:** переполнение диска логами/ревизиями, OOM без swap. ### Этапы 1. Monorepo Go, миграции **PostgreSQL** (основной путь), опционально SQLite для `microVPS_sqlite`, Compose `reference` и `microVPS`. 2. Один тип модуля end-to-end; render + BIRD. 3. Расписания CDN/модуля; deploy + agent. 4. Ревизии, rollback, bundle API. 5. **evobgp-node** на отдельной ВМ; observability. 6. Hardening, документация операторская. 7. **Web UI** в отдельном контейнере ([§17](#17-web-ui-svelte-shadcn-svelte)): полный UX по спискам, расписанию и мониторингу. --- ## 17. Web UI (Svelte, shadcn-svelte) Цель — **единая операторская поверхность** поверх уже описанного REST ([§7](#7-rest-api)) и observability ([§4](#4-сервисы-сравнение-профилей), этапы в [§16](#16-риски-и-этапы-внедрения)), без дублирования бизнес-логики на фронтенде: UI только вызывает API и визуализирует состояние. ### Стек и упаковка - **Svelte** (актуальная ветка проекта, SvelteKit при необходимости SSR/роутинга) и **shadcn-svelte** — доступные компоненты (формы, таблицы, диалоги, навигация), единый визуальный язык. - **Отдельный сервис в Docker Compose** (профиль **reference**): образ со статической сборкой или Node-сервером за reverse-proxy; **не** вшивать UI в `evobgp-api`. Контейнер получает только `VITE_*` / публичный **base URL API** и при необходимости URL метрик/health-прокси (см. ниже). - Сборка и публикация артефакта UI — отдельный job в CI ([§15](#15-cicd-gitea-actions)) по мере появления кода в репозитории (например `web/` или `ui/`). ### Покрытие user experience 1. **Списки и сущности** — модули префиксов, ревизии, пиры, community, DoH-профили, спикеры: просмотр, фильтрация, создание/редактирование там, где это отражено в OpenAPI; связь с jobs и аудитом ([§7.10](#710-задачи-job_audit)). 2. **Расписание** — триггеры и окна обновления модулей/ETL, ручной refresh, очередь задач и статусы без «чёрного ящика» ([§7](#7-rest-api), scheduler в [§1](#1-структура-репозитория-файлы-и-пакеты)). 3. **Мониторинг системы** — дашборд health API, агентов и нод; интеграция с метриками/алертами (Prometheus/Grafana или встроенные виджеты по публичным эндпоинтам); наглядное состояние BGP-сессий и последних deploy/revision там, где данные доступны через API или безопасный read-only прокси. ### Безопасность и эксплуатация - Аутентификация и роли — по [§7.13](#713-матрица-прав-роли); UI не хранит секреты вне согласованного потока (cookie/session или OIDC — на этапе проектирования конкретной инсталляции). - Для **microVPS** полноценный отдельный контейнер UI **опционален** (можно тот же образ с `profiles` или отключённый сервис), чтобы не раздувать single-node; приоритет — **reference** как эталон операторской панели. --- **Соответствие старым именам:** `full` ≈ `reference`, `target_vps` ≈ `microVPS`.