Compare commits

...

7 Commits

14 changed files with 3751 additions and 325 deletions
+98 -14
View File
@@ -258,20 +258,20 @@ These decisions are fixed for the MVP unless the user explicitly changes them:
Allowed status values: `NOT STARTED`, `IN PROGRESS`, `BLOCKED`, `DONE`.
| Milestone | Status | Outcome |
| --------- | ----------- | ---------------------------------------------------- |
| M0 | DONE | Test harness and safe project skeleton |
| M1 | DONE | Runtime foundation, CLI shell, and config loading |
| M2 | DONE | WireGuard parser, client model, `status`, and `list` |
| M3 | DONE | Locking, backups, atomic apply, and rollback engine |
| M4 | DONE | Safe client creation |
| M5 | DONE | Client detail, config export, and QR display |
| M6 | NOT STARTED | Safe client removal and archival |
| M7 | NOT STARTED | Diagnostics engine and report |
| M8 | NOT STARTED | Backup listing/restoration and audit log UX |
| M9 | NOT STARTED | TUI foundation, dashboard, and read-only screens |
| M10 | NOT STARTED | Complete TUI mutation workflows |
| M11 | NOT STARTED | Hardening, documentation, and MVP acceptance |
| Milestone | Status | Outcome |
| --------- | ------ | ---------------------------------------------------- |
| M0 | DONE | Test harness and safe project skeleton |
| M1 | DONE | Runtime foundation, CLI shell, and config loading |
| M2 | DONE | WireGuard parser, client model, `status`, and `list` |
| M3 | DONE | Locking, backups, atomic apply, and rollback engine |
| M4 | DONE | Safe client creation |
| M5 | DONE | Client detail, config export, and QR display |
| M6 | DONE | Safe client removal and archival |
| M7 | DONE | Diagnostics engine and report |
| M8 | DONE | Backup listing/restoration and audit log UX |
| M9 | DONE | TUI foundation, dashboard, and read-only screens |
| M10 | DONE | Complete TUI mutation workflows |
| M11 | DONE | Hardening, documentation, and MVP acceptance |
---
@@ -1635,6 +1635,90 @@ Use this template:
- Suggested commit message: `feat(clients): add client details and secure config export`
- Next milestone: M6
## 2026-10-13 — M6 — DONE
- Implemented: managed-only `remove NAME [--yes]`; safe interactive and noninteractive confirmation; exact public-key/marker/address block removal with post-lock identity revalidation; M3 transactional apply and rollback; restrictive timestamped archival of associated client artifacts; recovery handling for partial archival; and identity-protected, secret-free removal audit events.
- Coordinator: `gpt-5.6-sol` (`high`)
- Subagents: `milestone_worker` / `milestone_repairer`, `gpt-5.6-luna` (`medium`)
- Repair cycles: 5
- Sol review: replaced aliased and tautological focused tests with exact neighbor preservation and isolated identity cases; added privilege and confirmation gates; hardened archive source, owner, mode, hardlink, symlink, collision, partial-copy, partial-delete, and recovery handling; added post-lock metadata revalidation; protected audit files with restrictive ownership and opened-FD identity checks; preserved critical artifact-recovery status through server rollback; corrected test contamination, false original-path assertions, deterministic timestamps, and documentation ordering.
- Verification: `bash -n wg-manager.sh`, `bash -n tests/run.sh`, `bash -n tests/helpers.sh`, `bash -n tests/mocks/bin/*`, `bash tests/run.sh remove-client`, `bash tests/run.sh remove-cancel`, `bash tests/run.sh remove-exact-block`, `bash tests/run.sh remove-rollback`, `bash tests/run.sh archive-client`, `bash tests/run.sh archive-path-safety`, `bash tests/run.sh remove-preserves-neighbors`, `bash tests/run.sh no-secret-logs`, `bash tests/run.sh`, `git diff --check`, permission, scope, and secret-pattern review.
- Result: all required checks passed; remove-client/remove-cancel/remove-exact-block/remove-rollback/archive-client/archive-path-safety/remove-preserves-neighbors/no-secret-logs passed 17/16/5/7/58/31/5/41 assertions, and the full regression suite passed 461 assertions. Optional `shellcheck wg-manager.sh tests/*.sh tests/mocks/bin/*` was `SKIP` because ShellCheck is unavailable.
- Decisions: none
- Known limitations: real WireGuard, `/etc/wireguard`, networking, systemd, firewall, routes, sysctl, journal, real terminal confirmation, and deployment-server behavior were not accessed or tested; M6 verification is fixture- and mock-based as required. Permanent purge is intentionally not implemented.
- Suggested commit message: `feat(clients): add safe client removal and archival`
- Next milestone: M7
## 2026-10-13 — M7 — DONE
- Implemented: read-only `diagnose [--quiet]` with stable `OK`, `WARN`, `FAIL`, and `SKIP` records; required-command, configuration and key permission, parser invariant, interface/runtime, systemd, forwarding, IPv4/IPv6 address and route, UDP listener, NAT visibility, disk/runtime peer, and bounded journal checks; plus fixture-controlled mocks and machine-friendly output.
- Coordinator: `gpt-5.6-sol` (`high`)
- Subagents: `milestone_worker` / `milestone_repairer`, `gpt-5.6-luna` (`medium`)
- Repair cycles: 6
- Sol review: replaced aliased shallow tests and diagnostic stubs; separated independent checks; corrected fresh-process configuration loading, exact command contracts, systemd capability detection, IPv4/IPv6 and UDP parsing, exact NAT evidence, config/key symlink and permission safety, duplicate/overlap classification, disk/runtime AllowedIPs comparison, exit codes, and read-only proof; added regression coverage for every material finding.
- Verification: `bash -n wg-manager.sh`, `bash -n tests/run.sh`, `bash -n tests/mocks/bin/*`, `bash tests/run.sh diagnostics`, `bash tests/run.sh diagnostics-systemd`, `bash tests/run.sh diagnostics-firewall`, `bash tests/run.sh diagnostics-forwarding`, `bash tests/run.sh diagnostics-runtime-diff`, `bash tests/run.sh diagnostics-read-only`, `bash tests/run.sh diagnostics-exit-codes`, `bash tests/run.sh`, `git diff --check`, permission, scope, command-contract, and secret-pattern review.
- Result: all required checks passed; diagnostics/systemd/firewall/forwarding/runtime-diff/read-only/exit-code suites passed 20/12/11/14/31/42/43 assertions, and the full regression suite passed 634 assertions. Optional `shellcheck wg-manager.sh tests/*.sh tests/mocks/bin/*` was `SKIP` because ShellCheck is unavailable.
- Decisions: none
- Known limitations: real WireGuard, `/etc/wireguard`, networking, systemd, firewall, routes, sysctl, journal, and deployment-server behavior were not accessed or tested; M7 verification is fixture- and mock-based as required. Firewall `OK` confirms only expected NAT evidence visibility, and journal/handshake data is not a complete activity history.
- Suggested commit message: `feat(diagnostics): add read-only WireGuard health checks`
- Next milestone: M8
## 2026-10-13 — M8 — DONE
- Implemented: structured secret-safe audit events for mutations, apply, backup, and rollback outcomes; bounded newest-first `log`; serialized explicit `backup`; validated `backups` listing; and confirmed, interface-bound `restore BACKUP [--yes]` with pinned source identity, current-state backup, transactional apply, and disk/runtime rollback.
- Coordinator: `gpt-5.6-sol` (`high`)
- Subagents: `milestone_worker` / `milestone_repairer`, `gpt-5.6-luna` (`medium`)
- Repair cycles: 11
- Sol review: replaced shallow aliased M8 tests; corrected literal and false audit outcomes; added truthful transaction/backup/apply/rollback events; hardened concurrent audit creation, locking, identity checks, filtering, and limits; fixed backup-list pipeline, ownership, link, containment, malformed-entry, and manifest-swap handling; added manual-backup post-lock revalidation, retention, failure audit, and replacement detection; and made restore identifiers strict, source snapshots identity-pinned, confirmations explicit, and apply/rollback failures fully covered.
- Verification: `bash -n wg-manager.sh`, `bash -n tests/run.sh`, `bash -n tests/mocks/bin/flock`, `bash tests/run.sh audit`, `bash tests/run.sh audit-no-secrets`, `bash tests/run.sh backup-list`, `bash tests/run.sh restore`, `bash tests/run.sh restore-invalid`, `bash tests/run.sh restore-rollback`, `bash tests/run.sh backup-path-safety`, `bash tests/run.sh runtime-file-modes`, `bash tests/run.sh`, `git diff --check`, permission, scope, opened-file identity, retention, concurrency, rollback, and secret-pattern review.
- Result: all required checks passed; audit/audit-no-secrets/backup-list/restore/restore-invalid/restore-rollback/backup-path-safety/runtime-file-modes passed 17/5/11/5/8/5/7/14 assertions, and the full regression suite passed 706 assertions. Optional `shellcheck wg-manager.sh tests/*.sh tests/mocks/bin/*` was `SKIP` because ShellCheck is unavailable.
- Decisions: none
- Known limitations: real WireGuard, `/etc/wireguard`, networking, systemd, firewall, routes, sysctl, journal, real terminal confirmation, and deployment-server behavior were not accessed or tested; M8 verification is fixture- and mock-based as required. Restore intentionally covers only the server configuration, not archived client artifacts.
- Suggested commit message: `feat(backup): add audited backup listing and restoration`
- Next milestone: M9
## 2026-10-14 — M9 — DONE
- Implemented: validated fixed-order `gum` resolution and capability checks; isolated wrappers for style, choose, filter, input, confirm, table, pager, and spinner; interactive-terminal gating; a Russian dashboard and shallow read-only menu; backend-routed status, client list/detail, diagnostics, audit, and backup screens; stable client selection; normal Escape handling; and non-mutating add/remove placeholders.
- Coordinator: `gpt-5.6-sol` (`high`)
- Subagents: `milestone_worker` / `milestone_repairer`, `gpt-5.6-luna` (`medium`)
- Repair cycles: 5
- Sol review: replaced the worker's aliased M1-only coverage with real M9 implementation and tests; fixed production-directory temporary writes, ambiguous name/index selection, colliding display identities, literal dashboard newlines, English menu tokens, mock stdin consumption, cancellation false positives, TTY and gum-path cleanup, incomplete route/read-only evidence, 80-column labels, and fixture environment contamination; added regression coverage for every material finding.
- Verification: `bash -n wg-manager.sh tests/run.sh tests/mocks/bin/gum`, `bash tests/run.sh gum-resolution`, `bash tests/run.sh tui-routing`, `bash tests/run.sh tui-cancel`, `bash tests/run.sh tui-read-only`, `bash tests/run.sh tui-no-gum`, `bash tests/run.sh tui-non-tty`, `bash tests/run.sh`, `git diff --check`, full changed-file and secret-pattern review.
- Result: all required checks passed; gum-resolution/tui-routing/tui-cancel/tui-read-only/tui-no-gum/tui-non-tty passed 21/18/9/15/4/8 assertions, and the full regression suite passed 781 assertions. Optional ShellCheck and real-`gum` interactive smoke were `SKIP` because neither ShellCheck nor a usable `gum` binary is installed.
- Decisions: none
- Known limitations: real WireGuard, `/etc/wireguard`, networking, systemd, firewall, routes, sysctl, journal, and production terminal state were not accessed; M9 verification is fixture- and mock-based as required. Add/remove remain read-only placeholders until M10.
- Suggested commit message: `feat(tui): add gum dashboard and read-only workflows`
- Next milestone: M10
## 2026-10-14 — M10 — DONE
- Implemented: shared-backend `gum` workflows for full- and split-tunnel client creation, exact managed-client actions and archival removal, explicit config and QR confirmation, manual backup, validated deliberate restore, bounded backend failures, and immediate nonzero exit after critical rollback failure; added custom split-network CLI support needed by the TUI.
- Coordinator: senior Sol coordinator
- Subagents: `milestone_worker` / `milestone_repairer`, `gpt-5.6-luna` (`medium`)
- Repair cycles: 15
- Sol review: replaced aliased and stubbed M10 coverage with real isolated backend scenarios; fixed exact-identity revalidation, archive-result transport from transaction subshells, QR status loss and missing-dependency fallback, overlong destructive prompts, invalid failed-audit categories, fixture runtime sequencing and cross-group contamination, secret-bearing output boundaries, and critical rollback propagation; every material finding received regression coverage.
- Verification: `bash -n wg-manager.sh tests/run.sh tests/mocks/bin/gum`, `bash tests/run.sh tui-add`, `bash tests/run.sh tui-remove`, `bash tests/run.sh tui-client-actions`, `bash tests/run.sh tui-backup`, `bash tests/run.sh tui-restore`, `bash tests/run.sh tui-backend-failure`, `bash tests/run.sh tui-no-secret-args`, `bash tests/run.sh remove-rollback`, `bash tests/run.sh restore-rollback`, `bash tests/run.sh`, `git diff --check`, full changed-file, permission, rollback, audit, and secret-pattern review.
- Result: all required checks passed; tui-add/tui-remove/tui-client-actions/tui-backup/tui-restore/tui-backend-failure/tui-no-secret-args passed 52/40/28/6/20/9/11 assertions, and the full regression suite passed 947 assertions. Optional ShellCheck and real-`gum` interactive smoke were `SKIP` because neither ShellCheck nor a usable production `gum` environment is available.
- Decisions: none
- Known limitations: real WireGuard, `/etc/wireguard`, networking, systemd, firewall, routes, sysctl, journal, and production terminal state were not accessed; M10 verification is fixture- and mock-based as required.
- Suggested commit message: `feat(tui): add safe client and backup workflows`
- Next milestone: M11
## 2026-10-15 — M11 — DONE
- Implemented: complete MVP/AGENTS evidence mapping; mock-only end-to-end coverage for fresh, managed, mixed, stopped, malformed, add/inspect/QR fallback/remove/restore, successful rollback, failed rollback, and missing optional capabilities; repository/lifecycle secret scans; host-write and host-command-bypass guards; symlink hardening coverage; an early Bash 4.3 guard; a source-safe `add-user.sh` compatibility wrapper; and a cohesive Russian operations README with the explicitly deferred disposable deployment checklist.
- Coordinator: `gpt-5.6-sol` (`high`)
- Subagents: `milestone_worker` / `milestone_repairer`, `gpt-5.6-luna` (`medium`)
- Repair cycles: 4
- Sol review: replaced shallow acceptance aliases with real shared-backend lifecycle scenarios; added exact add/remove/restore and rollback evidence; strengthened secret, host-write, command-resolution, path, symlink, permission, and wrapper-source-safety checks; reconciled help, Bash-version, dependency, restore-confirmation, and deployment documentation with observable behavior; no material findings remained after the fourth repair.
- Verification: `bash -n wg-manager.sh`, `bash -n add-user.sh`, `bash -n tests/run.sh`, `bash -n tests/helpers.sh`, `bash tests/run.sh no-secret-logs`, `bash tests/run.sh path-safety`, `bash tests/run.sh symlink-safety`, `bash tests/run.sh no-host-writes`, `bash tests/run.sh no-host-command-bypass`, `bash tests/run.sh rollback`, `bash tests/run.sh m11-e2e`, `bash tests/run.sh secret-scan`, `bash tests/run.sh`, `git diff --check`, `git status --short`, executable-mode and full-diff secret review.
- Result: all required automated and repository checks passed; the full regression suite passed 1051 assertions, and focused no-secret-logs/path-safety/symlink-safety/no-host-writes/no-host-command-bypass/rollback/M11-E2E/secret-scan suites passed 41/31/4/8/18/65/60/11 assertions. Optional ShellCheck was `SKIP` because ShellCheck is unavailable; the real-`gum` smoke test and deployment-server checklist remain explicitly deferred.
- Decisions: none
- Known limitations: real WireGuard, `/etc/wireguard`, networking, systemd, firewall, routes, sysctl, journal, kernel tunnel behavior, and deployment-server state were not accessed; all M11 verification used fixtures and deny-by-default mocks and therefore does not prove live-server compatibility.
- Suggested commit message: `docs: finalize WireGuard Manager MVP and operations guide`
- Next milestone: none; MVP acceptance sequence M0–M11 is complete.
# 8. Final completion rule
The MVP is complete only when:
+203 -64
View File
@@ -1,86 +1,225 @@
# wireguard-scripts
## M1 runtime foundation
Безопасный копируемый Bash-инструмент для управления одним WireGuard-интерфейсом
на Linux. Скрипт не устанавливается в `PATH`, не запускает установщики и не
изменяет систему вне явно настроенных путей. Операции изменения выполняются под
`flock`, с резервной копией, атомарной заменой, проверкой runtime и откатом.
The current entry point provides dependency-free `help` and `version` commands. The
planned operational commands are recognized but remain unavailable until later
milestones.
## Поддерживаемая среда и ограничения
Runtime locations default to the project directory and can be redirected for an
isolated run with absolute paths using these environment variables:
Требуются 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`, and `WG_MANAGER_LOCK_FILE`.
`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.
`WG_MANAGER_GUM` selects an executable `gum` explicitly; otherwise the bundled
`bin/gum` and then `PATH` are checked in that order. Configuration is parsed as
restricted `KEY=VALUE` data and is never executed as shell code. Blank lines and
full-line `#` comments are ignored; spaces around the key, `=`, and value are
trimmed. Values are either one unquoted token or a double-quoted value without
escapes, and only documented keys are accepted.
## Команды и коды
For deterministic read-only tests, `WG_MANAGER_NOW` may override the current
epoch when set to decimal seconds; invalid values are rejected.
| Команда | Поведение, 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 |
M2 adds read-only `status` and `list`. They parse the configured WireGuard file
and, when available, `wg show <interface> dump`; a stopped or unavailable
runtime is reported as unavailable and disk-only peers remain visible. `list`
uses stable tab-separated output with English machine fields `state` and
`origin` (`managed`, `legacy`, or `unmanaged`) and never prints private or
preshared keys.
Коды: `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.
The explicit peer comment marker is `# wg-manager-client: NAME`; it is only a
display candidate. A peer is considered managed only when its exact public key
and configured addresses match a valid metadata record with `MANAGED=1`.
Примеры используют только документационные адреса и имена:
## M3 transaction foundation
```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
```
The internal `run_transaction OPERATION TARGET BUILDER [METADATA_FILE...]`
primitive acquires `WG_MANAGER_LOCK_FILE`, then
reloads and validates the live configuration before calling its builder. It
creates a restrictive, timestamped backup, validates a same-filesystem staged
configuration with `wg-quick strip`, replaces the live file atomically, applies
it with `wg syncconf`, and verifies the active peer set. Apply or verification
failures restore both disk and runtime; a failed restoration is reported as a
critical rollback error. No M3 mutation command is exposed yet.
`add-user.sh` — source-safe compatibility wrapper: он только передаёт
`add NAME [--endpoint HOST]`, не исполняет аргументы и не содержит второй
реализации backend.
M3 tests use only isolated overrides and deny-by-default command mocks; they do
not inspect or modify host WireGuard state.
## Транзакции, backups, archive и audit
## M4 client creation
Каждая 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 своего интерфейса.
`add NAME` creates a client transactionally. The optional forms are
`--mode full-tunnel|split-tunnel` and `--endpoint HOST`; unknown or extra
arguments are rejected. Names are restricted to ASCII letters, digits, `.`,
`_`, and `-` (without a leading `-`). Root is required for production use;
tests enable only an isolated `WG_MANAGER_TEST_MODE` under `TEST_ROOT`.
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.
Private/public/PSK files, client configuration, and metadata use restrictive
permissions and are removed if the server transaction or rollback fails.
Generated key material is supplied to `wg` through stdin or stdout and is not
placed in command arguments or diagnostic output.
## TUI
## M5 inspection and export
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 напрямую.
`show NAME` displays only non-secret peer details and a config path after
identity, ownership, mode, and path validation. `config NAME` prints only that
validated path. Full client content requires the explicit noninteractive
`config NAME --show` form; it never reads separate `.key` or `.psk` files.
## Offline-проверка
`qr NAME` feeds the validated config to `qrencode -t ansiutf8` through stdin.
If the optional `qrencode` dependency is unavailable, it prints the safe config
path and returns status `69`; QR failures return the operation status. Routine
show/export/path/error output is secret-free.
Тесты используют только temporary `TEST_ROOT`, deny-by-default mocks и placeholder
keys. Они не читают реальный WireGuard/network/service/firewall/sysctl state:
The inspection commands do not require `gum` or `qrencode` for `show` and
`config`. `qrencode` is the only optional QR dependency; when it is absent,
`qr NAME` writes the validated configuration path to stdout and an actionable
warning to stderr, then returns status `69`. A nonzero `qrencode` result
returns status `70` without printing configuration content in the diagnostic.
```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
```
Common command statuses are `64` for usage errors, `65` for invalid or unsafe
client identity/path data, `69` for a missing optional dependency, and `70`
for an operational read/export/QR failure. `config NAME --show` is the only
inspection form that writes the complete client configuration to stdout;
ordinary `show`, `config`, and `qr` output remains secret-safe.
Тестовые 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 без отдельной проверки оператором.
+23 -192
View File
@@ -1,200 +1,31 @@
#!/usr/bin/env bash
set -euo pipefail
WG_INTERFACE="wg0"
WG_CONF="/etc/wireguard/${WG_INTERFACE}.conf"
WG_DIR="/etc/wireguard"
CLIENT_DIR="${WG_DIR}/clients"
SERVER_PORT="51820"
DEFAULT_ENDPOINT="37.26.136.232"
SERVER_VPN_IP="10.9.0.1"
VPN_SUBNET_PREFIX="10.9.0"
START_HOST="2"
END_HOST="254"
USE_IPV6="0"
IPV6_PREFIX="fd05:13:53::"
set -Eeuo pipefail
umask 077
die() {
echo "Ошибка: $*" >&2
exit 1
# Compatibility entry point. Client mutation has one implementation in
# wg-manager.sh; this wrapper deliberately contains no WireGuard operations.
ADD_USER_SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
readonly ADD_USER_SCRIPT_DIR
add_user_usage() {
printf 'Использование: %s NAME [ENDPOINT]\n' "${ADD_USER_SCRIPT_DIR}/add-user.sh" >&2
printf 'Устаревший wrapper: используйте %s add NAME [--endpoint HOST]\n' \
"${ADD_USER_SCRIPT_DIR}/wg-manager.sh" >&2
}
need_cmd() {
command -v "$1" >/dev/null 2>&1 || die "Не найдена команда: $1"
}
usage() {
cat <<EOF
Использование:
sudo $0 CLIENT_NAME [ENDPOINT]
Примеры:
sudo $0 android-phone
sudo $0 work-laptop vpn.example.com
sudo $0 user5 37.26.136.232
Что делает:
- создаёт ключи клиента
- подбирает следующий свободный IP
- добавляет peer в ${WG_CONF}
- создаёт клиентский конфиг в ${CLIENT_DIR}
- применяет конфиг WireGuard
EOF
}
[[ $# -ge 1 ]] || { usage; exit 1; }
CLIENT_NAME="$1"
ENDPOINT="${2:-$DEFAULT_ENDPOINT}"
[[ "$EUID" -eq 0 ]] || die "Скрипт нужно запускать через sudo/root"
[[ -f "$WG_CONF" ]] || die "Не найден конфиг ${WG_CONF}"
need_cmd wg
need_cmd awk
need_cmd grep
need_cmd sed
need_cmd sort
need_cmd tee
need_cmd mkdir
need_cmd chmod
mkdir -p "$CLIENT_DIR"
if [[ ! "$CLIENT_NAME" =~ ^[a-zA-Z0-9._-]+$ ]]; then
die "Имя клиента может содержать только буквы, цифры, точку, подчёркивание и дефис"
fi
CLIENT_CONF="${CLIENT_DIR}/${WG_INTERFACE}-client-${CLIENT_NAME}.conf"
CLIENT_PRIVKEY_FILE="${CLIENT_DIR}/${CLIENT_NAME}.key"
CLIENT_PUBKEY_FILE="${CLIENT_DIR}/${CLIENT_NAME}.pub"
CLIENT_PSK_FILE="${CLIENT_DIR}/${CLIENT_NAME}.psk"
if grep -qE "^### Client ${CLIENT_NAME}$" "$WG_CONF"; then
die "Клиент с именем '${CLIENT_NAME}' уже есть в ${WG_CONF}"
fi
if [[ -e "$CLIENT_CONF" || -e "$CLIENT_PRIVKEY_FILE" || -e "$CLIENT_PUBKEY_FILE" || -e "$CLIENT_PSK_FILE" ]]; then
die "Файлы клиента '${CLIENT_NAME}' уже существуют в ${CLIENT_DIR}"
fi
find_next_ipv4() {
local used host
used="$(
grep -oE "${VPN_SUBNET_PREFIX//./\\.}\.[0-9]+/32" "$WG_CONF" \
| sed 's#/32##' \
| awk -F. '{print $4}' \
| sort -n \
| uniq
)"
for host in $(seq "$START_HOST" "$END_HOST"); do
if ! grep -qx "$host" <<<"$used"; then
echo "${VPN_SUBNET_PREFIX}.${host}"
return 0
fi
done
return 1
}
CLIENT_IPV4="$(find_next_ipv4)" || die "Не удалось найти свободный IPv4 адрес"
CLIENT_HOST_OCTET="${CLIENT_IPV4##*.}"
if [[ "$USE_IPV6" == "1" ]]; then
CLIENT_IPV6="${IPV6_PREFIX}${CLIENT_HOST_OCTET}"
fi
echo "Создаю клиента: ${CLIENT_NAME}"
echo "IPv4 клиента: ${CLIENT_IPV4}"
if [[ "$USE_IPV6" == "1" ]]; then
echo "IPv6 клиента: ${CLIENT_IPV6}"
fi
wg genkey | tee "$CLIENT_PRIVKEY_FILE" | wg pubkey > "$CLIENT_PUBKEY_FILE"
wg genpsk > "$CLIENT_PSK_FILE"
chmod 600 "$CLIENT_PRIVKEY_FILE" "$CLIENT_PUBKEY_FILE" "$CLIENT_PSK_FILE"
CLIENT_PRIVATE_KEY="$(cat "$CLIENT_PRIVKEY_FILE")"
CLIENT_PUBLIC_KEY="$(cat "$CLIENT_PUBKEY_FILE")"
CLIENT_PRESHARED_KEY="$(cat "$CLIENT_PSK_FILE")"
if command -v wg >/dev/null 2>&1 && wg show "$WG_INTERFACE" >/dev/null 2>&1; then
SERVER_PUBLIC_KEY="$(wg show "$WG_INTERFACE" public-key)"
else
SERVER_PUBLIC_KEY="$(wg pubkey < <(grep '^PrivateKey *= *' "$WG_CONF" | head -n1 | awk -F'=' '{gsub(/ /,"",$2); print $2}'))"
fi
TMP_PEER_FILE="$(mktemp)"
trap 'rm -f "$TMP_PEER_FILE"' EXIT
{
echo
echo "### Client ${CLIENT_NAME}"
echo "[Peer]"
echo "PublicKey = ${CLIENT_PUBLIC_KEY}"
echo "PresharedKey = ${CLIENT_PRESHARED_KEY}"
if [[ "$USE_IPV6" == "1" ]]; then
echo "AllowedIPs = ${CLIENT_IPV4}/32,${CLIENT_IPV6}/128"
else
echo "AllowedIPs = ${CLIENT_IPV4}/32"
add_user_main() {
local -a args
if (( $# < 1 || $# > 2 )); then
add_user_usage
return 64
fi
} > "$TMP_PEER_FILE"
args=(add "$1")
if (( $# == 2 )); then
args+=(--endpoint "$2")
fi
exec "${ADD_USER_SCRIPT_DIR}/wg-manager.sh" "${args[@]}"
}
cat "$TMP_PEER_FILE" >> "$WG_CONF"
if [[ "$USE_IPV6" == "1" ]]; then
CLIENT_ADDRESS="${CLIENT_IPV4}/32,${CLIENT_IPV6}/128"
CLIENT_ALLOWED_IPS="0.0.0.0/0,::/0"
else
CLIENT_ADDRESS="${CLIENT_IPV4}/32"
CLIENT_ALLOWED_IPS="0.0.0.0/0"
if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
add_user_main "$@"
fi
cat > "$CLIENT_CONF" <<EOF
[Interface]
PrivateKey = ${CLIENT_PRIVATE_KEY}
Address = ${CLIENT_ADDRESS}
DNS = ${SERVER_VPN_IP},192.168.0.200
[Peer]
PublicKey = ${SERVER_PUBLIC_KEY}
PresharedKey = ${CLIENT_PRESHARED_KEY}
Endpoint = ${ENDPOINT}:${SERVER_PORT}
AllowedIPs = ${CLIENT_ALLOWED_IPS}
PersistentKeepalive = 25
EOF
chmod 600 "$CLIENT_CONF"
echo "Применяю конфиг WireGuard..."
wg syncconf "$WG_INTERFACE" <(wg-quick strip "$WG_INTERFACE")
echo
echo "Готово."
echo "Клиент: ${CLIENT_NAME}"
echo "IP: ${CLIENT_IPV4}"
echo "Конфиг: ${CLIENT_CONF}"
echo
if command -v qrencode >/dev/null 2>&1; then
echo "QR-код для клиента:"
qrencode -t ansiutf8 < "$CLIENT_CONF"
echo
else
echo "Пакет qrencode не установлен, QR-код не напечатан."
echo "Установить можно так:"
echo " sudo apt update && sudo apt install -y qrencode"
echo
fi
echo "Проверка:"
echo " sudo wg show ${WG_INTERFACE}"
echo
echo "Передать клиенту файл:"
echo " ${CLIENT_CONF}"
+1 -1
View File
@@ -1,2 +1,2 @@
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= 51820 0
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB= (none) vpn.example.invalid:51820 192.0.2.2/32 1700000100 100 200 25
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB= (none) vpn.example.invalid:51820 192.0.2.2/32 1700000100 100 200 off
+37 -4
View File
@@ -5,11 +5,44 @@ command_name=${0##*/}
printf '%q' "$command_name" >>"$TEST_MOCK_LOG"
for argument in "$@"; do printf ' %q' "$argument" >>"$TEST_MOCK_LOG"; done
printf '\n' >>"$TEST_MOCK_LOG"
[[ $# == 2 && $1 == -n && $2 =~ ^[0-9]+$ ]] || exit 99
if [[ -n ${MOCK_FLOCK_CHANGE_TO:-} && -z ${MOCK_FLOCK_CHANGED_MARKER:-} && -f $MOCK_FLOCK_CHANGE_TO && -n ${WG_MANAGER_CONFIG:-} ]]; then
cp -- "$MOCK_FLOCK_CHANGE_TO" "$WG_MANAGER_CONFIG"
export MOCK_FLOCK_CHANGED_MARKER=1
fi
if [[ ${MOCK_FLOCK_BUSY:-0} == 1 ]]; then exit 1; fi
if [[ ${MOCK_FLOCK_FAIL:-0} == 1 ]]; then exit 97; fi
exit 0
if [[ $# == 2 && $1 == -n && $2 =~ ^[0-9]+$ ]]; then
[[ ${MOCK_FLOCK_BUSY:-0} == 1 ]] && exit 1
[[ ${MOCK_FLOCK_FAIL:-0} == 1 ]] && exit 97
exit 0
fi
if [[ $# -ge 3 && $1 == -n && $2 =~ ^[0-9]+$ && ( $3 == printf || $3 == cat ) ]]; then
[[ ${MOCK_FLOCK_BUSY:-0} == 1 ]] && exit 1
[[ ${MOCK_FLOCK_FAIL:-0} == 1 ]] && exit 97
shift 2
command "$@"
exit $?
fi
if [[ $# == 1 && $1 =~ ^[0-9]+$ || $# == 2 && $1 == -s && $2 =~ ^[0-9]+$ ]]; then
[[ ${MOCK_FLOCK_BUSY:-0} == 1 ]] && exit 1
[[ ${MOCK_FLOCK_FAIL:-0} == 1 ]] && exit 97
real_flock=${REAL_FLOCK:-}
if [[ -z $real_flock ]]; then
for candidate in /usr/bin/flock /bin/flock; do
if [[ -x $candidate ]]; then real_flock=$candidate; break; fi
done
fi
[[ -n $real_flock ]] || exit 98
exec "$real_flock" "$@"
fi
if [[ $# -ge 2 && ( $1 == -s || $1 != -* ) && ${1#-} =~ ^[0-9]+$ || $1 == -s && ${2:-} =~ ^[0-9]+$ ]]; then
[[ ${MOCK_FLOCK_BUSY:-0} == 1 ]] && exit 1
[[ ${MOCK_FLOCK_FAIL:-0} == 1 ]] && exit 97
real_flock=${REAL_FLOCK:-}
if [[ -z $real_flock ]]; then
for candidate in /usr/bin/flock /bin/flock; do
if [[ -x $candidate ]]; then real_flock=$candidate; break; fi
done
fi
[[ -n $real_flock ]] || exit 98
exec "$real_flock" "$@"
fi
exit 99
+44 -9
View File
@@ -2,14 +2,49 @@
set -Eeuo pipefail
command_name=${0##*/}
: "${TEST_MOCK_LOG:?TEST_MOCK_LOG must identify the isolated test log}"
allow_empty=${MOCK_ALLOW_EMPTY_INVOCATION:-}
printf '%q' "$command_name" >>"$TEST_MOCK_LOG"; for argument in "$@"; do printf ' %q' "$argument" >>"$TEST_MOCK_LOG"; done; printf '\n' >>"$TEST_MOCK_LOG"
if [[ -n "$allow_empty" && "$allow_empty" != "$command_name" ]]; then
printf 'Unexpected mock invocation: %s does not allow empty calls for %s\n' "$command_name" "$allow_empty" >&2
exit 99
fi
if [[ $# == 0 && "$allow_empty" == "$command_name" ]]; then
printf '%q' "$command_name" >>"$TEST_MOCK_LOG"
for argument in "$@"; do printf ' %q' "$argument" >>"$TEST_MOCK_LOG"; done
printf '\n' >>"$TEST_MOCK_LOG"
if (($# == 1)) && [[ $1 == --version ]]; then printf 'gum version 0.14.0\n'; exit 0; fi
subcommand=${1:-}
if [[ $subcommand == --help || $subcommand == '' ]]; then exit 0; fi
if (($# >= 2)) && [[ ${!#} == --help ]]; then
[[ ${GUM_FAIL_CAPABILITY:-} == "$subcommand" ]] && exit 98
exit 0
fi
printf 'Unexpected mock invocation: %s received arguments\n' "$command_name" >&2
exit 99
case $subcommand in
style)
[[ $# -gt 1 ]] && [[ ${!#} != *'\n'* ]] || exit 98
if [[ -n ${GUM_STYLE_CAPTURE:-} ]]; then printf '%s' "${!#}" >"$GUM_STYLE_CAPTURE"; fi
printf '%s\n' "${!#}"
exit 0
;;
table|pager)
if [[ -n ${GUM_PAGER_CAPTURE:-} ]]; then cat >"$GUM_PAGER_CAPTURE"; else cat >/dev/null; fi
exit 0
;;
spin) exit 0 ;;
choose|filter|input|confirm)
if [[ -n ${GUM_CHOICES_CAPTURE:-} && ( $subcommand == choose || $subcommand == filter ) ]]; then
: >"$GUM_CHOICES_CAPTURE"
if [[ $subcommand == filter ]]; then
shift 3
else
shift
fi
printf '%s\n' "$@" >>"$GUM_CHOICES_CAPTURE"
fi
script=${GUM_SCRIPT_FILE:-}; answer=''
if [[ -n $script && -f $script ]]; then
IFS= read -r answer <"$script" || answer=''
tail -n +2 -- "$script" >"${script}.next"
mv -- "${script}.next" "$script"
fi
case $subcommand in
confirm) [[ $answer == y || $answer == Y ]] || exit 1; exit 0 ;;
input) [[ -n $answer ]] || exit 1; printf '%s\n' "$answer"; exit 0 ;;
*) [[ -n $answer ]] && printf '%s\n' "$answer" || exit 1; exit 0 ;;
esac
;;
*) printf 'Unexpected gum subcommand: %s\n' "$subcommand" >&2; exit 99 ;;
esac
+15
View File
@@ -11,5 +11,20 @@ fi
if [[ $# == 0 && "$allow_empty" == "$command_name" ]]; then
exit 0
fi
if [[ $# == 4 && $1 == link && $2 == show && $3 == dev ]]; then
[[ $4 == ${WG_MOCK_INTERFACE:-wg0} ]] || exit 99
[[ ${DIAG_IP_LINK_FAIL:-0} == 1 ]] && exit 98
printf '%s\n' "${DIAG_IP_LINK_OUTPUT:-$4: <UP> mtu 1420}"; exit 0
fi
if [[ $# == 5 && $1 == -o && $2 == addr && $3 == show && $4 == dev ]]; then
[[ $5 == ${WG_MOCK_INTERFACE:-wg0} ]] || exit 99
[[ ${DIAG_IP_ADDR_FAIL:-0} == 1 ]] && exit 98
printf '%s\n' "${DIAG_IP_ADDR_OUTPUT:-}"; exit 0
fi
if [[ $# == 6 && $1 == -o && $2 == route && $3 == show && $5 == dev ]]; then
[[ $6 == ${WG_MOCK_INTERFACE:-wg0} ]] || exit 99
[[ ${DIAG_IP_ROUTE_FAIL:-0} == 1 ]] && exit 98
printf '%s\n' "${DIAG_IP_ROUTE_OUTPUT:-}"; exit 0
fi
printf 'Unexpected mock invocation: %s received arguments\n' "$command_name" >&2
exit 99
+4
View File
@@ -11,5 +11,9 @@ fi
if [[ $# == 0 && "$allow_empty" == "$command_name" ]]; then
exit 0
fi
if [[ $# == 3 && $1 == -t && $2 == nat && $3 == -S ]]; then
[[ ${DIAG_IPTABLES_FAIL:-0} == 1 ]] && exit 98
printf '%s\n' "${DIAG_IPTABLES_OUTPUT:-}"; exit 0
fi
printf 'Unexpected mock invocation: %s received arguments\n' "$command_name" >&2
exit 99
+4
View File
@@ -11,5 +11,9 @@ fi
if [[ $# == 0 && "$allow_empty" == "$command_name" ]]; then
exit 0
fi
if [[ $# == 5 && $1 == -u && $2 == wg-quick@${WG_MOCK_INTERFACE:-wg0} && $3 == --since && $4 == '24 hours ago' && $5 == --no-pager ]]; then
[[ ${DIAG_JOURNAL_FAIL:-0} == 1 ]] && exit 98
printf '%s\n' "${DIAG_JOURNAL_OUTPUT:-}"; exit 0
fi
printf 'Unexpected mock invocation: %s received arguments\n' "$command_name" >&2
exit 99
+4
View File
@@ -11,5 +11,9 @@ fi
if [[ $# == 0 && "$allow_empty" == "$command_name" ]]; then
exit 0
fi
if [[ $# == 2 && $1 == list && $2 == ruleset ]]; then
[[ ${DIAG_NFT_FAIL:-0} == 1 ]] && exit 98
printf '%s\n' "${DIAG_NFT_OUTPUT:-}"; exit 0
fi
printf 'Unexpected mock invocation: %s received arguments\n' "$command_name" >&2
exit 99
+4
View File
@@ -11,5 +11,9 @@ fi
if [[ $# == 0 && "$allow_empty" == "$command_name" ]]; then
exit 0
fi
if [[ $# == 1 && $1 == -lun ]]; then
[[ ${DIAG_SS_FAIL:-0} == 1 ]] && exit 98
printf '%s\n' "${DIAG_SS_OUTPUT:-}"; exit 0
fi
printf 'Unexpected mock invocation: %s received arguments\n' "$command_name" >&2
exit 99
+12
View File
@@ -11,5 +11,17 @@ fi
if [[ $# == 0 && "$allow_empty" == "$command_name" ]]; then
exit 0
fi
if [[ $# == 3 && $1 == is-active && $2 == -- && $3 == wg-quick@${WG_MOCK_INTERFACE:-wg0} ]]; then
case ${SYSTEMCTL_STATE:-missing} in
active|inactive|failed|unknown) printf '%s\n' "$SYSTEMCTL_STATE"; [[ $SYSTEMCTL_STATE == active || $SYSTEMCTL_STATE == unknown ]] && exit 0 || exit 3 ;;
esac
fi
if [[ $# == 1 && $1 == is-system-running ]]; then
case ${SYSTEMCTL_INIT:-missing} in
running) printf '%s\n' "$SYSTEMCTL_INIT"; exit 0 ;;
degraded) printf '%s\n' "$SYSTEMCTL_INIT"; exit 1 ;;
*) printf '%s\n' "${SYSTEMCTL_INIT:-offline}"; exit 3 ;;
esac
fi
printf 'Unexpected mock invocation: %s received arguments\n' "$command_name" >&2
exit 99
+2124 -8
View File
File diff suppressed because it is too large Load Diff
+1178 -33
View File
File diff suppressed because it is too large Load Diff