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.