From 63a2df893d614d2e63842a1791b789fe3be48869 Mon Sep 17 00:00:00 2001 From: embeddevio Date: Wed, 21 Oct 2026 23:29:24 +0300 Subject: [PATCH] =?UTF-8?q?docs(#16):=20ADR-003=20(reconcile)=20=D0=B8=20A?= =?UTF-8?q?DR-004=20(GeoIP=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B0=D0=B9=D0=B4?= =?UTF-8?q?=D0=B5=D1=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/adr/003-reconcile-desired-state.md | 28 +++++++++++++++++++++++++ docs/adr/004-geoip-ripe-provider.md | 28 +++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 docs/adr/003-reconcile-desired-state.md create mode 100644 docs/adr/004-geoip-ripe-provider.md diff --git a/docs/adr/003-reconcile-desired-state.md b/docs/adr/003-reconcile-desired-state.md new file mode 100644 index 0000000..82f1e47 --- /dev/null +++ b/docs/adr/003-reconcile-desired-state.md @@ -0,0 +1,28 @@ +# 003. Декларативный reconcile вместо императивных скриптов + +- **Статус:** принято +- **Контекст/issue:** #15, #24, #27 + +## Контекст + +PoC (`entrypoint.sh`) применяет правила императивно и только при старте: изменения не откатываются, +GeoIP не обновляется без рестарта, частичная ошибка оставляет систему в неопределённом состоянии. + +## Решение + +Единый reconcile-цикл: desired state (SQLite) -> diff -> идемпотентное применение -> проверка -> +rollback при ошибке. Операции netfilter идемпотентны (проверка `-C` перед `-A`), замена больших +наборов атомарна (`ipset create tmp`, `swap`, `destroy`), применение конфига транзакционно +(validate -> stage -> apply -> rollback). + +## Альтернативы + +- **Скрипты по событию** (как в PoC) — быстро написать, но невозможно гарантировать сходимость после сбоя. +- **Готовый IaC-инструмент** — избыточен, требует внешнего рантайма в образе и не покрывает ipset/dnsmasq. + +## Последствия + +- Старт контейнера = reconcile с нуля: состояние всегда восстановимо. +- Нужны тесты на идемпотентность (повторный apply не создаёт дублей) — #41. +- Сложнее скрипта: оправдано требованиями FR-2 (смена режимов и правил без рестарта). +- Diff reconcile логируется и отдаётся в UI (`/logs`) и audit log. diff --git a/docs/adr/004-geoip-ripe-provider.md b/docs/adr/004-geoip-ripe-provider.md new file mode 100644 index 0000000..e656736 --- /dev/null +++ b/docs/adr/004-geoip-ripe-provider.md @@ -0,0 +1,28 @@ +# 004. GeoIP: RIPE Stat + локальный кэш + абстракция провайдера + +- **Статус:** принято +- **Контекст/issue:** #15, #25 + +## Контекст + +Для split-tunnel нужны списки префиксов страны (в PoC — около 11 400 записей RIPE). +Варианты: RIPE Stat (бесплатно), ip2location и MaxMind (точнее, но лицензия и платный доступ). + +## Решение + +Основной источник — **RIPE Stat** (`country-resource-list`, IPv4 и IPv6): бесплатно и уже проверено в PoC. +Обязательные элементы: локальный кэш-снапшот в `/data/geoip/`, расписание обновления, ручное +обновление, явный статус источника (API или кэш с датой). Внутри — интерфейс `GeoProvider`, чтобы +добавить ip2location/MaxMind без переделки маршрутизации. + +## Альтернативы + +- **MaxMind GeoLite2** — точнее по ASN, но требует регистрации и обновления лицензии; оставляем как опцию. +- **Собственный список в репозитории** — устаревает и не покрывает смену маршрутной политики. + +## Последствия + +- Старт не зависит от внешней сети: при недоступности API работаем на кэше (#25, тест #41). +- Fallback не должен молча менять политику split-tunnel: неявные переходы в режим «всё в VPN» или + «всё напрямую» запрещены, поведение задаётся явно (#23). +- Загрузка 11k+ префиксов — измеримая операция; время старта и swap — часть AC #24 и #25.