# wireguard-scripts Безопасный копируемый Bash-инструмент для управления одним WireGuard-интерфейсом на Linux. Скрипт не устанавливается в `PATH`, не запускает установщики и не изменяет систему вне явно настроенных путей. Операции изменения выполняются под `flock`, с резервной копией, атомарной заменой, проверкой runtime и откатом. ## Поддерживаемая среда и ограничения Требуются Linux, Bash 4.3+, `wg`, `wg-quick`, `ip`, `flock`, стандартные coreutils и util-linux. Минимальная заявленная архитектура — x86_64. Другие архитектуры не считаются протестированными; `gum` и `qrencode` должны соответствовать архитектуре самостоятельно установленного оператором бинарника. Обязательные capabilities: `bash`, `wg`, `wg-quick`, `ip`, `flock`, `awk`, `grep`, `sed`, `sort`, `stat`, `mktemp`, `sha256sum`, `cp`, `mv`, `chmod` и прочие обычные coreutils, используемые системой. Необязательные: `gum` (TUI), `qrencode` (QR), `systemctl`/`journalctl` (systemd), `ss` (UDP), `nft` и `iptables` (наблюдение firewall). Отсутствие optional capability даёт `SKIP`/fallback, а не скрытый вызов host-команды. Проект можно копировать как каталог: ```text wireguard-scripts/ ├── wg-manager.sh ├── wg-manager.conf.example ├── add-user.sh ├── bin/gum # optional, если оператор его положил сюда ├── tests/ │ ├── fixtures/ # placeholder-only fixtures │ └── mocks/bin/ # deny-by-default test commands ├── backups/ logs/ state/ # runtime paths, создаются при настройке └── README.md ``` ## Настройка Скопируйте `wg-manager.conf.example` в `wg-manager.conf`, замените только placeholder-значения и задайте владельца root и режим `0600`. Все пути должны быть абсолютными, без symlink-компонентов, а каталоги client/metadata/backup/ archive/log — `0700`; конфигурации, metadata, key и log — `0600`. | Ключ | Назначение и ограничения | |---|---| | `WG_INTERFACE` | имя интерфейса, например `wg0` | | `WG_CONF` | абсолютный серверный конфиг | | `CLIENT_DIR` | каталог клиентских `.conf/.key/.pub/.psk` | | `SERVER_ENDPOINT` | проверяемое имя/IP сервера без порта | | `SERVER_PORT` | UDP-порт 1..65535 | | `SERVER_VPN_IP` | адрес сервера IPv4 | | `CLIENT_DNS` | непустой comma-separated список IPv4/IPv6 | | `VPN_SUBNET_PREFIX` | IPv4 prefix из трёх октетов, например `192.0.2` | | `START_HOST` | начало пула host-частей | | `END_HOST` | конец пула host-частей | | `USE_IPV6` | `0` или `1` | | `IPV6_PREFIX` | prefix, оканчивающийся `::`, если IPv6 включён | | `DEFAULT_CLIENT_MODE` | `full-tunnel` или `split-tunnel` | | `RECENT_HANDSHAKE_SECONDS` | неотрицательное окно active-state | | `BACKUP_RETENTION` | bounded число automatic backups | Окружение может изолированно переопределить расположение: `WG_MANAGER_CONFIG`, `WG_MANAGER_BACKUP_DIR`, `WG_MANAGER_LOG_DIR`, `WG_MANAGER_METADATA_DIR`, `WG_MANAGER_ARCHIVE_DIR`, `WG_MANAGER_LOCK_FILE`. `WG_MANAGER_GUM` имеет приоритет над локальным `bin/gum`, затем `PATH`. `WG_MANAGER_NOW` — только документированный deterministic clock override; перед боевым запуском эти переменные следует убрать. Test-only hooks принимаются лишь в confined `WG_MANAGER_TEST_MODE` под `TEST_ROOT` и не являются production API. ## Команды и коды | Команда | Поведение, stdout и подтверждение | |---|---| | `help`, `version` | безопасная справка/версия, без mutation | | `status` | disk/runtime summary, без secrets | | `list` | tab-separated peer rows с `state`/`origin`, без secrets | | `add NAME [--mode MODE] [--split-networks CIDRS] [--endpoint HOST]` | явный add под lock | | `show NAME` | identity-safe details и safe path, если он доступен | | `config NAME` | печатает только валидированный путь | | `config NAME --show` | единственная форма полного client config | | `qr NAME` | QR через stdin; без `qrencode` печатает путь и возвращает `69` | | `remove NAME [--yes]` | managed peer, archive вместо purge; noninteractive требует `--yes` | | `diagnose [--quiet]` | read-only checks; quiet даёт `id=STATUS` | | `backup` | ручная backup текущего server config под lock | | `backups` | только валидные backups current interface, newest-first | | `restore BACKUP [--yes]` | CLI: без `--yes` нужен обычный интерактивный `y/N`; без terminal команда отклоняется. В TUI требуется ввод полного backup ID; сначала создаётся current-state backup | | `log [LIMIT]` | validated audit records newest-first, `LIMIT` 1..1000 | | `tui` или запуск без аргументов | gum TUI, требует terminal stdin и stdout | Коды: `0` — успех или безопасная отмена; `64` — usage/flags/arity; `65` — validation или unsafe identity/path; `69` — отсутствующая optional capability; `70` — operation/backend failure; `71` — critical rollback/recovery failure; `77` — недостаточные права. Ошибка audit после mutation сообщается как partial operation и не маскирует исходный status. `FAIL` в diagnose даёт `70`, `WARN`/`SKIP` сами по себе не дают failure. Примеры используют только документационные адреса и имена: ```bash ./wg-manager.sh status ./wg-manager.sh list ./wg-manager.sh add alice --mode full-tunnel ./wg-manager.sh add bob --mode split-tunnel --split-networks '192.0.2.0/24,2001:db8:40::/48' --endpoint vpn.example.invalid ./wg-manager.sh show alice ./wg-manager.sh config alice ./wg-manager.sh config alice --show ./wg-manager.sh qr alice ./wg-manager.sh remove alice --yes ./wg-manager.sh diagnose --quiet ./wg-manager.sh log 25 ./wg-manager.sh backup ./wg-manager.sh backups ./wg-manager.sh restore 20231114T221500Z-wg0-001 --yes ./wg-manager.sh tui ./add-user.sh alice vpn.example.invalid ``` `add-user.sh` — source-safe compatibility wrapper: он только передаёт `add NAME [--endpoint HOST]`, не исполняет аргументы и не содержит второй реализации backend. ## Транзакции, backups, archive и audit Каждая mutation повторно читает ресурсы после lock, валидирует staged config, создаёт backup, делает atomic replace, выполняет `wg syncconf`, затем сравнивает runtime dump. При ошибке восстанавливаются disk и runtime; если восстановление не доказано, возвращается `71` и сообщение `КРИТИЧНО`. Add публикует client artifacts только после успешной server transaction. Remove архивирует связанные файлы в новый restrictive каталог и не удаляет архивы автоматически. Restore перед применением создаёт backup текущего состояния, не восстанавливает client metadata/key files и принимает только validated backup своего интерфейса. Audit line содержит только UTC timestamp, actor UID, safe target, outcome и короткую error category. PrivateKey, PresharedKey, полный config, команды и окружение туда не попадают. `log` не source-ит записи и пропускает malformed/hostile lines. Handshake — текущее значение dump, не история. `firewall=OK` означает только видимое ожидаемое NAT-свидетельство, не полную проверку политики firewall. ## TUI TUI требует пригодный `gum` и оба terminal descriptors. Main screen показывает interface/runtime/managed/active/warning counts; Escape возвращает на предыдущий экран, отмена mutation ничего не меняет. Клиент выбирается по стабильной trusted identity, а не по decorated name. Config/QR требуют отдельного действия; missing `qrencode` — безопасный fallback. Critical rollback немедленно выходит с `71`; обычная ошибка сохраняет диагностический текст и возвращается в меню ограниченное число раз. Backend functions не вызывают gum напрямую. ## Offline-проверка Тесты используют только temporary `TEST_ROOT`, deny-by-default mocks и placeholder keys. Они не читают реальный WireGuard/network/service/firewall/sysctl state: ```bash bash -n wg-manager.sh add-user.sh tests/run.sh tests/helpers.sh bash tests/run.sh bash tests/run.sh m11-e2e bash tests/run.sh secret-scan bash tests/run.sh no-host-writes bash tests/run.sh no-host-command-bypass bash tests/run.sh symlink-safety git diff --check ``` Тестовые mocks не заменяют kernel WireGuard, distribution-specific systemd, firewall policy или архитектурный smoke-test на disposable interface. ## Troubleshooting - `65`: проверьте root-owned `0600` config, абсолютные пути, отсутствие symlink и соответствие интерфейса. - `69` для TUI/QR: установите совместимый gum/qrencode самостоятельно или используйте CLI без capability; TUI не скачивает бинарники. - `70`: проверьте stderr, lock и доступность `wg-quick`/`wg`; исходный config должен оставаться восстановленным. - `71 КРИТИЧНО`: остановите mutation, сохраните backup/archive и вручную проверьте disk/runtime перед повтором. - `diagnose` `SKIP`: capability отсутствует или systemd не является active init. - Для gum проверьте архитектуру, `WG_MANAGER_GUM`, локальный `bin/gum` и `PATH`. ## M11 evidence matrix | Требование | Evidence | |---|---| | status / list | `status`, `list`, `m11-e2e` — машинный вывод и смешанные origins | | add и allocation | `add-client`, `tui-add`, `m11-e2e` — полная/выборочная сеть и rollback | | show / config export | `show-client`, `config-export`, `m11-e2e` | | QR и fallback | `qr`, `tui-client-actions`, `secret-scan` | | remove / archive | `remove-client`, `archive-client`, `tui-remove`, `m11-e2e` | | diagnose | `diagnostics`, `diagnostics-exit-codes`, `m11-e2e` | | backup / backups | `backup-list`, `runtime-file-modes`, `tui-backup` | | restore | `restore`, `restore-rollback`, `tui-restore`, `m11-e2e` | | audit / log | `audit`, `audit-no-secrets`, `secret-scan` | | public-key identity | `parser`, `show-client`, `remove-client`, `tui-client-actions` | | transaction, apply и rollback | `transaction`, `rollback`, `add-rollback`, `m11-e2e` | | paths, symlinks и modes | `path-safety`, `symlink-safety`, `backup-path-safety`, `runtime-file-modes` | | secret redaction | `no-secret-logs`, `secret-scan`, `tui-no-secret-args` | | host write/command isolation | `no-host-writes`, `no-host-command-bypass` | | exit/status semantics | `config`, `diagnostics-exit-codes`, `rollback`, CLI-focused groups | | docs и deployment | этот README и disposable checklist; live-server deployment остаётся ручным шагом | Это offline evidence, а не обещание, что live server уже настроен или что deployment checklist выполнен. ## Disposable deployment checklist (не выполнен) 1. Создать отдельный non-production interface и disposable host. 2. Скопировать каталог проекта, не устанавливая его в `PATH`. 3. Проверить Bash, WireGuard tools, coreutils/util-linux и архитектуру gum. 4. Скопировать example config и заменить только placeholder values. 5. Проверить owner root, config `0600` и каталоги `0700`. 6. Выполнить `help`, `version`, `status` и `list`. 7. Выполнить `diagnose` и сохранить безопасный отчёт. 8. Добавить disposable client и проверить modes/metadata/audit. 9. Проверить `show`, safe `config` path и QR fallback. 10. Удалить client с явным подтверждением и проверить archive. 11. Создать backup и проверить newest-first listing. 12. Восстановить backup с typed confirmation на disposable config. 13. Проверить lock contention и rollback на тестовом runtime. 14. Сверить SSH/noninteractive, firewall visibility и systemd limitations. 15. Только после human review решить, допустим ли production rollout. Этот список намеренно не выполняется агентом и не является разрешением менять реальный host state без отдельной проверки оператором.