4.3 KiB
Архитектура
Границы ответственности
Hysteria2 URI содержит только переносимые реквизиты VPN:
- authentication;
- hostname и port/port ranges;
- SNI и
insecure; salamanderи obfs password.
policy.json содержит локальную инфраструктурную политику:
eth0— upstream;eth1— VPN LAN;tun-sb0,172.19.0.1/30, MTU 1400;- TUN routes, nftables marks и rule/table indexes;
- bootstrap DNS и DoH через
hy2-out; - bandwidth hints и healthcheck.
Renderer не патчит существующий JSON. Полная конфигурация каждый раз строится
из typed model. Это устраняет config drift и исторические endpoint /32.
Поток применения
URI + policy
│
├─ strict parsing / schema validation
├─ exact sing-box version and build-tag gate
├─ deterministic render
├─ secure candidate in /etc/sing-box
└─ sing-box check -c candidate
│
▼
redacted comparison
│
unchanged ─────── changed
│ │
▼ ├─ last-good backup
no restart ├─ atomic replace
├─ systemctl restart
└─ bounded local connectivity healthcheck
│
fail ┴ success
│ │
▼ ▼
rollback state.json
Все операции изменения сериализованы flock-совместимой блокировкой. Поэтому
ручной import и запоздалый event от systemd.path не могут применять два
candidate одновременно.
Почему endpoint IP не нужен
Outbound сохраняет DNS hostname и содержит bind_interface=eth0. В 1.13.19
route.auto_detect_interface не применяется к outbound с явным
bind_interface. Bootstrap DNS также привязан к eth0. Endpoint IP поэтому не
является частью TUN policy.
После смены A-записи следующая Hysteria2-сессия разрешает имя заново через
bootstrap-dns. doctor дополнительно проверяет, что текущие A-записи не
попали в exclusions.
Файловая модель
/etc/vpn-egress/ root:root 0700
├── policy.json root:root 0600
└── hysteria2.uri root:root 0600
/var/lib/vpn-egress/ root:root 0700
├── state.json без секретов, 0600
├── last-good.json содержит secrets, 0600
└── backups/ ограниченная история, 0700/0600
/etc/sing-box/
└── config.json root:root 0600
Service sing-box уже имеет CAP_DAC_READ_SEARCH, поэтому сохраняется текущая
рабочая модель root:root 0600.
Guard читает имена интерфейсов из того же policy и устанавливает всю nftables таблицу одной batch-транзакцией. Если новый ruleset некорректен, nft не оставляет систему с частично заменённой таблицей.
Package-managed drop-in добавляет для sing-box.service зависимости
Requires= и After= от guard. Поэтому при совместном запуске ошибка guard
блокирует старт sing-box. Уже активный oneshot не является watchdog: ручное
удаление nftables-таблицы обнаруживает doctor, но не systemd dependency.
Версионная граница
В коде существует только renderer_1_13_19.py. Renderer для 1.14 не является
пустой заготовкой: он появится только в отдельной миграции после аудита схемы,
маршрутизации и полных интеграционных тестов.