store — хранилище состояния (issue #20)
SQLite (WAL) в /data/awg-dash.db: правила маршрутизации, исключения, режим,
расписание GeoIP, метаданные конфигов, устройства, журнал аудита и снимки
состояния. Путь переопределяется аргументом или переменной AWG_DASH_DB.
| Модуль | Назначение |
|---|---|
db.py |
открытие соединения, PRAGMA (WAL, synchronous=NORMAL, foreign_keys, busy_timeout), транзакции BEGIN IMMEDIATE |
migrations.py |
версионированные миграции, schema_migrations, проверка схемы при старте (verify_schema) |
models.py |
dataclass-модели сущностей и перечисления (Mode, Entity, Rule, …) |
validation.py |
чистые функции валидации (domain/CIDR/ipset, MAC, cron, режим, политика) |
repository.py |
Store: чтения + аудируемые мутации «кто/когда/что/old→new» |
audit.py |
запись и чтение audit_log (мутация и её аудит — одна транзакция) |
state.py |
снимки состояния (state_snapshots), capture_state, restore_state, rollback_to_snapshot |
apply.py |
ApplyEngine: validate → stage → apply → rollback и протокол Applier (awg/iptables) |
service.py |
open_storage() — сборка Database + Store + ApplyEngine для старта приложения |
errors.py |
StorageError, SchemaError, ValidationError, ApplyError(restored=…) |
Гарантии
- Пустой
/data— каталог создаётся, миграции применяются, схема v1 появляется без ручных действий. - Версия схемы — при старте схема проверяется: новее ожидаемой или не мигрированная →
SchemaError(без «автоотката»). - Аудит — каждая мутация пишет
actor/ts/action/entity/old_value/new_valueвaudit_logв той же транзакции. - Атомарность — применяемое состояние проходит
validate → stage → apply; падение внешнего применителя откатывает транзакцию БД и вызываетApplier.revert(previous),ApplyError.restored=True. - Откат —
ApplyEngine.rollback(plan_id)возвращает состояние из снимка.
Пример
from awg_api.store import DesiredState, Rule, open_storage
with open_storage() as storage: # /data/awg-dash.db
storage.store.set_geoip_schedule(cron="0 3 * * *", actor="ui:admin")
plan = storage.engine.stage(
DesiredState(
mode=storage.store.get_mode(), rules=(Rule("yt", "domain", "youtube.com", "tun0"),)
),
reason="ui: split-tunnel для YouTube",
)
result = storage.engine.apply(plan)
Тесты
backend/tests/test_store_*.py — миграции, аудит, репозиторий, apply/rollback,
персистентность после рестарта. Порог покрытия — 70 % (pyproject.toml).