Metadata-Version: 2.4
Name: vpn-egressctl
Version: 0.1.0
Summary: Declarative control plane for the sing-box VPN egress gateway
Author: Flamy Studio
License-Expression: MIT
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: POSIX :: Linux
Requires-Python: >=3.13
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# singbox_glue / vpn-egressctl

`vpn-egressctl` — локальный декларативный control plane для шлюза `vpn-egress-gw`.
Он получает реквизиты подключения из защищённого Hysteria2 URI, объединяет их с
локальной инфраструктурной политикой и полностью генерирует конфигурацию
sing-box.

Проект намеренно поддерживает только **sing-box 1.13.19**. Версии 1.13.12,
другие patch-релизы и вся ветка 1.14 отклоняются до изменения файлов.

## Что решает проект

- IP Hysteria2-сервера отсутствует в `route_exclude_address` и nftables policy.
- Смена DNS A-записи не требует перегенерации конфигурации.
- Смена credential выполняется одной безопасной командой через stdin.
- Перед установкой candidate проверяется реальным `sing-box check`.
- Запись атомарна; после неуспешного restart/healthcheck выполняется rollback.
- Policy, URI, production config, state и backups имеют `root:root 0600`,
  защищённые каталоги — `root:root 0700`.
- Неизвестные URI/policy-параметры отклоняются, а не игнорируются.
- systemd следит за desired state без постоянно работающего Python-процесса.
- Отдельный nftables guard блокирует прямой forwarding `eth1 -> eth0`.
- `sing-box.service` требует успешного запуска guard через package-managed drop-in.

## Источники состояния

```text
/etc/vpn-egress/policy.json
/etc/vpn-egress/hysteria2.uri
             │
             ▼
      renderer 1.13.19
             │
             ▼
/etc/sing-box/config.json
```

`/etc/sing-box/config.json` является генерируемым артефактом. Редактировать его
вручную после миграции нельзя.

## Основные команды

```bash
# URI не попадает в argv и shell history.
sudo vpn-egressctl import --stdin

sudo vpn-egressctl check
sudo vpn-egressctl diff
sudo vpn-egressctl sync
sudo vpn-egressctl status
sudo vpn-egressctl doctor
sudo vpn-egressctl rollback
```

Позиционный `vpn-egressctl import 'hysteria2://...'` запрещён специально.

## Документация

- [Архитектура](docs/architecture.md)
- [Конфигурация](docs/configuration.md)
- [Установка и миграция](docs/migration.md)
- [Эксплуатация](docs/operations.md)
- [Безопасность](docs/security.md)
- [Диагностика](docs/troubleshooting.md)
- [Тестирование](docs/testing.md)
- [Почему не поддерживается 1.14](docs/sing-box-1.14.md)

## Локальная проверка

Проект не имеет runtime-зависимостей вне Python stdlib.

```powershell
E:\python-31312\python.exe -m compileall -q src tests
```

Изолированная Windows-сборка Python в указанном каталоге не добавляет cwd в
`sys.path`, поэтому полный тестовый запуск выполняется так:

```powershell
E:\python-31312\python.exe -c "import sys,unittest; sys.path[:0]=[r'F:\projects\singbox_glue\src',r'F:\projects\singbox_glue']; s=unittest.defaultTestLoader.discover(r'F:\projects\singbox_glue\tests'); r=unittest.TextTestRunner(verbosity=2).run(s); raise SystemExit(not r.wasSuccessful())"
```

На Linux достаточно `make check`.
