Compare commits
10 Commits
e5e3064885
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b0cfad98fd | |||
| a4f530d7a5 | |||
| 06c7171e91 | |||
| 8b40b8cd65 | |||
| 72c0d21977 | |||
| 67a9e3fe03 | |||
| 75f7547add | |||
| 2bec75aa9d | |||
| 2e40d5bd1a | |||
| 4e3c5347f2 |
@@ -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 | NOT STARTED | Locking, backups, atomic apply, and rollback engine |
|
||||
| M4 | NOT STARTED | Safe client creation |
|
||||
| M5 | NOT STARTED | 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 |
|
||||
|
||||
---
|
||||
|
||||
@@ -1593,6 +1593,132 @@ Use this template:
|
||||
- Suggested commit message: `feat(clients): add WireGuard state parsing and client listing`
|
||||
- Next milestone: M3
|
||||
|
||||
## 2026-10-12 — M3 — DONE
|
||||
|
||||
- Implemented: reusable internal mutation transaction with nonblocking `flock` serialization, post-lock state revalidation, same-filesystem restrictive staging, `wg-quick strip` validation, timestamped manifest backups with bounded safe retention, atomic config replacement, `wg syncconf` apply verification, and disk/runtime rollback with a distinct critical recovery status.
|
||||
- Coordinator: `gpt-5.6-sol` (`high`)
|
||||
- Subagents: `milestone_worker` / `milestone_repairer`, `gpt-5.6-luna` (`medium`)
|
||||
- Repair cycles: 11
|
||||
- Sol review: fixed incomplete rollback verification, unsafe temporary-file allocation and trap handling, false-positive lock/mode tests, missing failure and interruption coverage, permissive mock contracts, backup-manifest and retention gaps, runtime-dump verification gaps, unconditional pruning after failed mutations, unchecked backup writes, and backup-counter exhaustion that could reuse `-999`; all defects received regression coverage.
|
||||
- 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 transaction`, `bash tests/run.sh rollback`, `bash tests/run.sh locking`, `bash tests/run.sh backups`, `bash tests/run.sh`, `bash tests/run.sh mutation-preserves-source`, `bash tests/run.sh secret-modes`, `git diff --check`, per-file `git diff --no-index --check /dev/null <new-file>`, permission and secret-pattern review.
|
||||
- Result: all required checks passed; transaction/rollback/locking/backups/full/mutation-preserves-source/secret-modes passed 51/65/19/34/297/47/27 assertions. Concurrent mutation, INT/TERM cleanup, exact command order, source preservation, restrictive active temp and backup modes, apply rollback, critical rollback failures, hostile runtime dumps, safe pruning, and counter exhaustion were verified exclusively with fixtures and mocks. Optional `shellcheck wg-manager.sh tests/*.sh tests/mocks/bin/*` was `SKIP` because ShellCheck is unavailable.
|
||||
- Decisions: none
|
||||
- Known limitations: real WireGuard, networking, systemd, firewall, route, sysctl, journal, and deployment-server behavior was not accessed or tested; M3 verification is fixture- and mock-based as required. No add/remove/restore CLI command is exposed until later milestones.
|
||||
- Suggested commit message: `feat(safety): add atomic WireGuard mutation and rollback engine`
|
||||
- Next milestone: M4
|
||||
|
||||
## 2026-10-13 — M4 — DONE
|
||||
|
||||
- Implemented: strict `add NAME` CLI parsing; deterministic IPv4 and optional IPv6 allocation; managed peer, full-tunnel/split-tunnel client configuration, key, PSK, and metadata generation; restrictive artifact publication integrated with the M3 transaction; collision, rollback, interruption, test-hook confinement, and secret-safety coverage using only fixtures and mocks.
|
||||
- Coordinator: `gpt-5.6-sol` (`high`)
|
||||
- Subagents: `milestone_worker` / `milestone_repairer`, `gpt-5.6-luna` (`medium`)
|
||||
- Repair cycles: 11
|
||||
- Sol review: fixed configuration-load ordering, missing PSK persistence, pre-commit artifact publication, publish-race and INT/TERM consistency, incomplete rollback and staging cleanup, unsafe test-hook confinement, multiline key-output acceptance, mixed IPv6 collision handling, missing allocation/collision/key-contract matrices, silent expected failures, misleading fixtures, and insufficient secret/mode evidence; every material defect received focused regression coverage.
|
||||
- Verification: `bash -n wg-manager.sh`, `bash -n tests/run.sh`, `bash -n tests/mocks/bin/wg`, `bash tests/run.sh add-client`, `bash tests/run.sh address-allocation`, `bash tests/run.sh add-collisions`, `bash tests/run.sh add-rollback`, `bash tests/run.sh secret-modes`, `bash tests/run.sh no-secret-logs`, `bash tests/run.sh add-golden-config`, `bash tests/run.sh add-invalid-names`, `bash tests/run.sh transaction`, `bash tests/run.sh rollback`, `bash tests/run.sh locking`, `bash tests/run.sh config`, `bash tests/run.sh`, `git diff --check`, permission, scope, and secret-pattern review.
|
||||
- Result: all required checks passed; add-client/address-allocation/add-collisions/add-rollback/secret-modes/no-secret-logs/golden/invalid-name suites passed 39/65/104/78/66/41/44/12 assertions, transaction/rollback/locking/config passed 51/65/19/128 assertions, and the full suite passed 355 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; M4 verification is fixture- and mock-based as required. Audit-log creation remains scheduled for M8, so M4 secret checks cover captured stdout, stderr, xtrace, mock logs, generated artifacts, and fixtures.
|
||||
- Suggested commit message: `feat(clients): add transactional client creation`
|
||||
- Next milestone: M5
|
||||
|
||||
## 2026-10-13 — M5 — DONE
|
||||
|
||||
- Implemented: non-secret `show NAME` client details; validated path-only `config NAME` output with explicit `--show` full-config export; stdin-only `qrencode -t ansiutf8` integration with a clear missing-dependency fallback; exact managed identity checks; and config, metadata, ownership, mode, hardlink, symlink, and open-time identity validation.
|
||||
- Coordinator: `gpt-5.6-sol` (`high`)
|
||||
- Subagents: `milestone_worker` / `milestone_repairer`, `gpt-5.6-luna` (`medium`)
|
||||
- Repair cycles: 5
|
||||
- Sol review: restored the full M0–M4 regression dispatch; fixed failure-status capture and caller `errexit` preservation; required persistent-disk identity for managed association; added open-time file-identity verification and actionable `stat` diagnostics; strengthened missing-client, unmanaged, ambiguous, mismatched, runtime-only, CLI, QR, path, permission, ownership, hardlink, symlink, and secret-safety tests; and removed a misleading unused fixture.
|
||||
- Verification: `bash -n wg-manager.sh`, `bash -n tests/run.sh`, `bash -n tests/mocks/bin/qrencode`, `bash tests/run.sh show-client`, `bash tests/run.sh config-export`, `bash tests/run.sh qr`, `bash tests/run.sh path-safety`, `bash tests/run.sh no-secret-default-output`, `bash tests/run.sh`, `bash tests/run.sh no-secret-logs`, `git diff --check`, permission, scope, and secret-pattern review.
|
||||
- Result: all required checks passed; show-client/config-export/qr/path-safety/no-secret-default-output/no-secret-logs suites passed 44/16/13/31/45/41 assertions, and the full regression suite passed 399 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; M5 verification is fixture- and mock-based as required. Full config output is deliberately available only through the explicit noninteractive `config NAME --show` action.
|
||||
- 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:
|
||||
|
||||
@@ -1,35 +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`.
|
||||
Примеры используют только документационные адреса и имена:
|
||||
|
||||
```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 без отдельной проверки оператором.
|
||||
|
||||
+23
-192
@@ -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}"
|
||||
|
||||
Vendored
+1
-1
@@ -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
|
||||
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
[Interface]
|
||||
Address = 192.0.2.1/24
|
||||
ListenPort = 51820
|
||||
PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
|
||||
[Peer]
|
||||
PublicKey = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=
|
||||
AllowedIPs = 192.0.2.2/32; do-not-execute
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= 51820 0
|
||||
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB= (none) vpn.example.invalid:51820 192.0.2.2/32 1700000100 100 200 25
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
[Interface]
|
||||
Address = 192.0.2.1/24
|
||||
ListenPort = 51820
|
||||
PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
|
||||
[Peer]
|
||||
# wg-manager-client: fixture
|
||||
PublicKey = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=
|
||||
AllowedIPs = 192.0.2.2/32
|
||||
Endpoint = vpn.example.invalid:51820
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
[Interface]
|
||||
PrivateKey = EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE=
|
||||
Address = 192.0.2.3/32
|
||||
DNS = 192.0.2.53,2001:db8::53
|
||||
|
||||
[Peer]
|
||||
PublicKey = DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD=
|
||||
PresharedKey = GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG=
|
||||
Endpoint = vpn.example.invalid:51820
|
||||
AllowedIPs = 0.0.0.0/0,::/0
|
||||
PersistentKeepalive = 25
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
[Interface]
|
||||
PrivateKey = EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE=
|
||||
Address = 192.0.2.3/32
|
||||
DNS = 192.0.2.53,2001:db8::53
|
||||
|
||||
[Peer]
|
||||
PublicKey = DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD=
|
||||
PresharedKey = GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG=
|
||||
Endpoint = vpn.example.invalid:51820
|
||||
AllowedIPs = 192.0.2.0/24
|
||||
PersistentKeepalive = 25
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
NAME=alice
|
||||
PUBLIC_KEY=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=
|
||||
IPV4=192.0.2.3
|
||||
CREATED_AT=1700000100
|
||||
CONFIG_PATH=CLIENT_DIR/alice.conf
|
||||
CLIENT_MODE=full-tunnel
|
||||
MANAGED=1
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
[Interface]
|
||||
Address = 192.0.2.1/24
|
||||
ListenPort = 51820
|
||||
PrivateKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
|
||||
[Peer]
|
||||
# wg-manager-client: fixture
|
||||
PublicKey = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=
|
||||
AllowedIPs = 192.0.2.2/32
|
||||
Endpoint = vpn.example.invalid:51820
|
||||
|
||||
# wg-manager-client: alice
|
||||
[Peer]
|
||||
PublicKey = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=
|
||||
PresharedKey = GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG=
|
||||
AllowedIPs = 192.0.2.3/32
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
[Interface]
|
||||
PrivateKey = EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE=
|
||||
Address = 192.0.2.3/32
|
||||
DNS = 192.0.2.53,2001:db8::53
|
||||
|
||||
[Peer]
|
||||
PublicKey = DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD=
|
||||
PresharedKey = GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG=
|
||||
Endpoint = vpn.example.invalid:51820
|
||||
AllowedIPs = 0.0.0.0/0,::/0
|
||||
PersistentKeepalive = 25
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
command_name=${0##*/}
|
||||
: "${TEST_MOCK_LOG:?TEST_MOCK_LOG must identify the isolated test log}"
|
||||
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 ${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 [[ $# == 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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+13
-12
@@ -1,15 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
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
|
||||
: "${TEST_MOCK_LOG:?TEST_MOCK_LOG must identify isolated log}"
|
||||
printf 'qrencode' >>"$TEST_MOCK_LOG"
|
||||
for argument in "$@"; do printf ' %q' "$argument" >>"$TEST_MOCK_LOG"; done
|
||||
printf '\n' >>"$TEST_MOCK_LOG"
|
||||
[[ $# == 2 && $1 == -t && $2 == ansiutf8 ]] || { printf 'Unexpected qrencode arguments\n' >&2; exit 99; }
|
||||
if [[ -n ${QR_MOCK_EXPECTED_FILE:-} ]]; then
|
||||
[[ -n ${TEST_ROOT:-} && ${QR_MOCK_EXPECTED_FILE} == "${TEST_ROOT}"/* && -f ${QR_MOCK_EXPECTED_FILE} && ! -L ${QR_MOCK_EXPECTED_FILE} ]] || { printf 'Unexpected QR fixture path\n' >&2; exit 98; }
|
||||
cmp -s -- "$QR_MOCK_EXPECTED_FILE" - || { printf 'Unexpected QR input\n' >&2; exit 98; }
|
||||
else
|
||||
printf 'QR expected fixture is required\n' >&2
|
||||
exit 98
|
||||
fi
|
||||
if [[ $# == 0 && "$allow_empty" == "$command_name" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
printf 'Unexpected mock invocation: %s received arguments\n' "$command_name" >&2
|
||||
exit 99
|
||||
[[ ${QR_MOCK_FAIL:-0} != 1 ]] || { printf 'Mock qrencode failure\n' >&2; exit 97; }
|
||||
printf 'MOCK-QR\n'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+68
-1
@@ -6,10 +6,77 @@ 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 [[ $# == 3 && $1 == show && -n ${WG_MOCK_INTERFACE:-} && $2 == "$WG_MOCK_INTERFACE" && $3 == dump && -n ${WG_MOCK_DUMP_FILE:-} && -f $WG_MOCK_DUMP_FILE ]]; then
|
||||
if [[ $# == 3 && $1 == show && -n ${WG_MOCK_INTERFACE:-} && $2 == "$WG_MOCK_INTERFACE" && $3 == public-key ]]; then
|
||||
[[ ${WG_MOCK_FAIL_SERVER_PUBLIC_KEY:-0} != 1 ]] || exit 97
|
||||
printf '%s\n' "${WG_MOCK_SERVER_PUBLIC_KEY_OUTPUT:-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD=}"
|
||||
exit 0
|
||||
fi
|
||||
if [[ $# == 1 && $1 == genkey ]]; then
|
||||
[[ ${WG_MOCK_FAIL_GENKEY:-0} != 1 ]] || exit 97
|
||||
printf '%s\n' "${WG_MOCK_PRIVATE_KEY:-EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE=}"
|
||||
exit 0
|
||||
fi
|
||||
if [[ $# == 1 && $1 == pubkey ]]; then
|
||||
IFS= read -r private_key
|
||||
[[ -n $private_key && $private_key =~ ^[A-Za-z0-9+/]{43}=$ ]] || exit 97
|
||||
if IFS= read -r extra_key; then exit 97; fi
|
||||
if [[ -n ${WG_MOCK_EXPECTED_PRIVATE_KEY:-} && $private_key != "$WG_MOCK_EXPECTED_PRIVATE_KEY" ]]; then exit 97; fi
|
||||
[[ ${WG_MOCK_FAIL_PUBKEY:-0} != 1 ]] || exit 97
|
||||
printf '%s\n' "${WG_MOCK_PUBLIC_KEY:-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=}"
|
||||
exit 0
|
||||
fi
|
||||
if [[ $# == 1 && $1 == genpsk ]]; then
|
||||
[[ ${WG_MOCK_FAIL_GENPSK:-0} != 1 ]] || exit 97
|
||||
printf '%s\n' "${WG_MOCK_PSK:-GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG=}"
|
||||
exit 0
|
||||
fi
|
||||
if [[ $# == 3 && $1 == show && -n ${WG_MOCK_INTERFACE:-} && $2 == "$WG_MOCK_INTERFACE" && $3 == dump && -z ${WG_MOCK_DUMP_SEQUENCE_DIR:-} && -n ${WG_MOCK_DUMP_FILE:-} && -f $WG_MOCK_DUMP_FILE ]]; then
|
||||
if [[ -n ${WG_MOCK_DUMP_FAIL_ON_CALL:-} ]]; then
|
||||
dump_counter=${WG_MOCK_DUMP_FAIL_COUNTER_FILE:-${TEST_ROOT}/dump-counter}
|
||||
dump_calls=0; [[ -f $dump_counter ]] && read -r dump_calls <"$dump_counter"; ((dump_calls += 1)); printf '%s\n' "$dump_calls" >"$dump_counter"
|
||||
[[ $dump_calls != "$WG_MOCK_DUMP_FAIL_ON_CALL" ]] || exit 98
|
||||
fi
|
||||
cat -- "$WG_MOCK_DUMP_FILE"
|
||||
exit 0
|
||||
fi
|
||||
if [[ $# == 3 && $1 == show && -n ${WG_MOCK_INTERFACE:-} && $2 == "$WG_MOCK_INTERFACE" && $3 == dump && -n ${WG_MOCK_DUMP_SEQUENCE_DIR:-} ]]; then
|
||||
[[ ${WG_MOCK_DUMP_SEQUENCE_DIR} == "${TEST_ROOT}"/* && -d ${WG_MOCK_DUMP_SEQUENCE_DIR} && ! -L ${WG_MOCK_DUMP_SEQUENCE_DIR} ]] || exit 99
|
||||
sequence_counter=${WG_MOCK_DUMP_SEQUENCE_DIR}/counter
|
||||
[[ ! -L "$sequence_counter" && ( -f "$sequence_counter" || ! -e "$sequence_counter" ) ]] || exit 99
|
||||
sequence_number=0
|
||||
[[ -f "$sequence_counter" ]] && read -r sequence_number <"$sequence_counter"
|
||||
((sequence_number += 1))
|
||||
printf '%s\n' "$sequence_number" >"$sequence_counter"
|
||||
sequence_file=${WG_MOCK_DUMP_SEQUENCE_DIR}/${sequence_number}
|
||||
[[ -f "$sequence_file" ]] && cat -- "$sequence_file" && exit 0
|
||||
printf 'Mock wg dump sequence exhausted\n' >&2
|
||||
exit 98
|
||||
fi
|
||||
if [[ $# == 3 && $1 == syncconf ]]; then
|
||||
[[ -n ${WG_MOCK_INTERFACE:-} && $2 == "$WG_MOCK_INTERFACE" ]] || { printf 'Mock wg wrong interface\n' >&2; exit 99; }
|
||||
if [[ ${WG_SYNC_FAIL_ONCE_FILE:-} && ! -e ${WG_SYNC_FAIL_ONCE_FILE} ]]; then
|
||||
: >"$WG_SYNC_FAIL_ONCE_FILE"
|
||||
printf 'Mock wg syncconf failure once\n' >&2
|
||||
exit 97
|
||||
fi
|
||||
if [[ -n ${WG_SYNC_FAIL_ON_CALL:-} ]]; then
|
||||
sync_counter=${WG_SYNC_FAIL_COUNTER_FILE:-${TEST_ROOT}/sync-counter}
|
||||
sync_calls=0; [[ -f $sync_counter ]] && read -r sync_calls <"$sync_counter"; ((sync_calls += 1)); printf '%s\n' "$sync_calls" >"$sync_counter"
|
||||
[[ $sync_calls != "$WG_SYNC_FAIL_ON_CALL" ]] || { printf 'Mock wg syncconf failure\n' >&2; exit 97; }
|
||||
fi
|
||||
[[ -f $3 && ! -L $3 && ${WG_SYNC_FAIL:-0} != 1 && -n ${TEST_ROOT:-} && $3 == "${TEST_ROOT}"/* ]] || { printf 'Mock wg syncconf failure\n' >&2; exit 97; }
|
||||
[[ $(stat -c '%a' -- "$3") == 600 ]] || { printf 'Mock wg syncconf unsafe mode\n' >&2; exit 97; }
|
||||
if [[ -n ${WG_SYNC_EXPECTED_STRIP:-} ]]; then
|
||||
[[ -f $WG_SYNC_EXPECTED_STRIP ]] || exit 97
|
||||
cmp -s -- "$3" "$WG_SYNC_EXPECTED_STRIP" || { printf 'Mock wg syncconf unexpected stripped content\n' >&2; exit 97; }
|
||||
fi
|
||||
if [[ -n ${WG_SYNC_READY_FIFO:-} ]]; then
|
||||
[[ ${WG_SYNC_READY_FIFO} == "${TEST_ROOT}"/* && -p ${WG_SYNC_READY_FIFO} && ${WG_SYNC_RELEASE_FIFO:-} == "${TEST_ROOT}"/* && -p ${WG_SYNC_RELEASE_FIFO} ]] || exit 99
|
||||
printf 'ready\n' >"$WG_SYNC_READY_FIFO"
|
||||
IFS= read -r _ <"${WG_SYNC_RELEASE_FIFO:?WG_SYNC_RELEASE_FIFO must be set}"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
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
|
||||
|
||||
@@ -6,6 +6,14 @@ 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 [[ $# == 2 && $1 == strip ]]; then
|
||||
[[ -f $2 && ! -L $2 && $(stat -c '%a' -- "$2") == 600 && -n ${TEST_ROOT:-} && $2 == "${TEST_ROOT}"/* ]] || { printf 'Mock wg-quick strip failure\n' >&2; exit 97; }
|
||||
case ${WG_QUICK_STRIP_FAIL:-0}:${2##*/} in
|
||||
1:*|staged:*.stage.*|live:wg0.conf) printf 'Mock wg-quick strip failure\n' >&2; exit 97 ;;
|
||||
esac
|
||||
if [[ -n ${WG_QUICK_STRIP_OUTPUT:-} && -f $WG_QUICK_STRIP_OUTPUT ]]; then cat -- "$WG_QUICK_STRIP_OUTPUT"; fi
|
||||
exit 0
|
||||
fi
|
||||
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
|
||||
|
||||
+3504
-8
File diff suppressed because it is too large
Load Diff
+2123
-19
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user