55 KiB
WireGuard Manager — implementation milestones
1. Purpose of this document
This file is the sequential implementation plan for the MVP described in MVP.md.
It is written so that a coding agent with limited project context can complete one milestone at a time without redesigning the project.
The source-of-truth order is:
AGENTS.md— engineering and safety rules.MVP.md— product scope and expected behavior.PLANS.md— implementation sequence and verification checkpoints.- Existing code and tests — current implementation state.
If these documents conflict, stop and resolve the conflict before changing production behavior.
2. Required Codex workflow
For every milestone, follow this loop:
- Read
AGENTS.md,MVP.md, and the complete current milestone. - Inspect the existing files and
git statusbefore editing. - Confirm that all prerequisite milestones are complete.
- Mark the current milestone as
IN PROGRESSin the progress table. - Follow the multi-agent protocol in section 2.2 and delegate only the scope listed under Changes to Luna.
- Ensure the Luna worker adds or updates tests in the same milestone as the behavior.
- Run every command under Verification.
- If any check fails, repair it and rerun the complete milestone verification.
- Review the diff for scope creep, unsafe paths, and leaked secrets.
- Perform the listed manual checks when the environment supports them.
- Mark the milestone
DONEonly after all acceptance criteria pass. - Add a short entry to Execution log with decisions, checks, and known limitations.
- Report the suggested commit message; do not create a commit unless the user asks.
Stop-and-fix rule:
- Never move to the next milestone while a required verification is failing.
- Never weaken or delete a test merely to make a milestone pass.
- If a command cannot run because an optional tool is absent, record
SKIPand run the documented fallback. - If a required dependency or safe test environment is missing, stop and report the exact blocker.
- Never test mutations against the host's real
/etc/wireguardconfiguration. - Run Git-specific checks only when the directory is already a Git worktree.
- If it is not a Git worktree, record those checks as
SKIP; do not initialize Git unless the user asks.
2.1. Development environment constraint
The development machine does not have a WireGuard server installed or configured. This is an expected project constraint, not a blocker.
Apply these rules to every milestone:
- Required verification must run without
wg,wg-quick, a WireGuard kernel interface, root access, systemd, firewall access, or/etc/wireguard. - Tests must use fixtures plus executable mocks placed first in a test-only
PATH. - Tests must use an isolated temporary root for configs, clients, metadata, backups, logs, locks, and archives.
- No required test may inspect or depend on the development machine's actual networking, routes, firewall, services, sysctl values, or journal.
- The absence of real WireGuard commands on the development machine is not grounds for marking a milestone
BLOCKED. - Mock behavior must cover successful output, malformed output, missing command, nonzero exit, timeout where relevant, apply failure, and rollback failure.
- Command-contract tests must assert the exact argument arrays passed to external tools.
- A test must fail if production code bypasses the mock directory and invokes a host networking command.
- Real WireGuard integration checks are deferred to the deployment server and are not required to mark development milestones complete.
- Deployment checks remain documented so they can be run manually after the project is copied to a disposable or non-production WireGuard environment.
- Real-server observations must never be fabricated from mock results.
- Optional smoke tests with a real
gum,qrencode, or ShellCheck may run when those tools are present, but their absence is not a product failure.
2.2. Required multi-agent milestone orchestration
Every milestone implementation uses a senior coordinator and lower-cost execution subagents.
Agent roles
- The primary coordinator uses
gpt-5.6-solwithhighreasoning. - Initial implementation is delegated to the project agent
milestone_worker. - Every implementation subagent must explicitly use
gpt-5.6-luna. - Corrections after review are delegated to
milestone_repairerongpt-5.6-luna. - The project defaults are stored in
.codex/config.toml. - The custom subagent definitions are stored in
.codex/agents/. - An explicit host or user model override can take precedence over project defaults; do not silently claim the configured model was used when the host reports otherwise.
- If a milestone starts in a primary session explicitly running a model other than
gpt-5.6-sol, stop before implementation and request a Sol session or explicit user direction.
Coordinator responsibilities
The Sol coordinator must:
- Read
AGENTS.md,MVP.md, the complete current milestone, current progress, and the current worktree state. - Confirm prerequisites and select exactly one milestone.
- Mark it
IN PROGRESSbefore delegation. - Break the milestone into bounded tasks with explicit files, behavior, constraints, acceptance criteria, and verification commands.
- Delegate implementation to
milestone_workerwithmodel = gpt-5.6-lunaexplicitly selected. - Use multiple Luna workers only when their work is independent and they will not edit the same files.
- Prefer sequential delegation for write-heavy work in
wg-manager.shto avoid conflicting edits. - Wait for every delegated task to finish before reviewing the combined result.
- Inspect the actual files and diff; never accept only the subagent's summary.
- Run or independently confirm every milestone verification command.
- Review behavior, tests, quoting, path safety, secret handling, rollback, mock isolation, and acceptance criteria.
- Decide whether the milestone passes, needs Luna repair, or is genuinely blocked.
- Keep the milestone
IN PROGRESSthroughout all repair cycles. - Mark it
DONEonly after the complete verification gate passes and no material review findings remain. - Update the execution log and report the suggested commit message.
The coordinator owns architecture, scope, review, and completion decisions. It must not outsource the final acceptance decision to Luna.
Initial Luna assignment contract
Each milestone_worker prompt must include:
- the exact milestone identifier and title;
- the milestone goal;
- prerequisites already satisfied;
- files the worker may edit;
- files or systems it must not touch;
- the required implementation steps;
- the development-machine constraint;
- required fixtures and mocks;
- exact verification commands;
- acceptance criteria relevant to the delegated task;
- the required return format: changed files, tests, command results, uncertainties, and blockers.
Do not delegate a vague request such as “implement the next milestone.” The Luna task must be concrete enough to execute without making architecture decisions.
Mandatory Sol review gate
After Luna finishes, the Sol coordinator must perform all of these checks:
- Read the returned summary, but treat it only as a navigation aid.
- Inspect every changed file and the complete diff.
- Confirm no unrelated file or user change was overwritten.
- Confirm the implementation matches the assigned milestone rather than only passing tests.
- Confirm tests cover success, expected failure, rollback, and unsafe input where applicable.
- Confirm all host interactions remain behind mocks in development verification.
- Confirm no private key, preshared key, client config, real endpoint, or secret appears in output or fixtures.
- Run the milestone's full required verification, not only the worker's focused tests.
- Inspect test output and exit codes rather than relying on a verbal “passed” claim.
- Check
git diff --checkwhen the directory is a Git worktree. - Compare the result against every acceptance criterion.
- Record concrete findings with file paths, observed behavior, expected behavior, and reproduction commands.
Luna repair loop
If the Sol review finds any material problem:
- Do not fix the implementation silently in the coordinator thread.
- Keep the milestone status
IN PROGRESS. - Delegate a repair task to
milestone_repairerwithmodel = gpt-5.6-lunaexplicitly selected. - Provide each finding in this format:
- location or affected behavior;
- what is wrong;
- expected result;
- reproduction or failing verification command;
- constraints that the repair must preserve.
- Require a regression test for every reproducible defect.
- Wait for the repairer to finish.
- Reinspect the actual diff and rerun the complete milestone verification as Sol.
- Repeat the Luna repair cycle for remaining or newly introduced findings.
- Stop only when the milestone passes or a genuine external blocker prevents further safe progress.
Do not impose an arbitrary retry count.
Repeated failures are handled by making the next repair prompt more specific with evidence from the latest review.
If the same blocker persists and cannot be solved within repository scope, record it as BLOCKED with exact evidence.
Parallelism rules
- Parallel Luna agents are encouraged for independent read-only exploration, fixture design, or review of disjoint concerns.
- Only one Luna agent may edit
wg-manager.shat a time. - Do not run implementation and repair agents concurrently on the same milestone.
- Do not let two agents edit the same test or fixture files concurrently.
- The Sol coordinator merges conclusions and resolves disagreements.
- All subagents share the repository state, so assignments must name file ownership explicitly.
Completion evidence
The execution-log entry for each completed milestone must include:
- coordinator model:
gpt-5.6-sol; - implementation subagent model:
gpt-5.6-luna; - custom agent names used;
- number of repair cycles;
- Sol review findings and their resolution;
- full verification commands and results;
- any optional checks recorded as
SKIP; - the suggested commit message.
3. Intended MVP architecture
The implementation remains a portable project directory with one production entry point:
wireguard-scripts/
├── AGENTS.md
├── MVP.md
├── PLANS.md
├── README.md
├── add-user.sh # legacy reference; not the final entry point
├── wg-manager.sh # production CLI and TUI entry point
├── wg-manager.conf.example # documented safe example
├── bin/
│ └── gum # optional operator-provided binary
├── backups/ # runtime data; ignored by version control
├── logs/ # runtime data; ignored by version control
├── state/
│ ├── clients/ # runtime client metadata
│ └── archive/ # recoverable removed-client files
└── tests/
├── run.sh
├── helpers.sh
├── fixtures/
└── mocks/bin/
For the MVP, production functions may remain in wg-manager.sh, grouped in this order:
- constants and exit codes;
- generic output and error helpers;
- path and dependency resolution;
- restricted configuration parsing and validation;
- WireGuard config parsing;
- client metadata;
- locking, backup, staging, apply, and rollback;
- client operations;
- diagnostics;
- CLI presentation;
gumTUI wrappers and screens;- argument parsing and
main.
The final guard must allow the script to be sourced by tests without running main:
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
main "$@"
fi
Do not introduce an installer, uninstaller, daemon, package manager, database, JSON parser, or second TUI framework.
4. Stable design decisions
These decisions are fixed for the MVP unless the user explicitly changes them:
- Bash is the implementation language.
wg-manager.shis the only production entry point.- The project is run locally and is not installed into
PATH. gumis the TUI framework.- The operator supplies or installs the
gumbinary. - CLI functions must work without
gum. gumlookup order isWG_MANAGER_GUM, localbin/gum, thenPATH.qrencode, systemd,nft, andiptablesare optional capabilities.- Runtime tests use fixtures and mocks, never the live host configuration.
- Configuration uses a restricted
KEY=VALUEparser, not unrestrictedsource. - Client identity is the WireGuard public key; the name is display metadata.
- Runtime metadata is stored as one root-readable file per managed client.
- Mutations use
flock, backup, staged config, atomic replacement, apply verification, and rollback. - Removed client files are archived by default rather than permanently deleted.
- User-facing messages are in Russian.
- Code identifiers and CLI command names are in English.
5. Progress tracker
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 | NOT STARTED | 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 M0 — Test harness and safe project skeleton
Goal
Create the minimal repository structure and a dependency-free Bash test harness so every later milestone can be implemented with fast, isolated verification.
Prerequisites
AGENTS.md,MVP.md, andPLANS.mdexist.- No implementation milestone has started.
Changes
- Create
wg-manager.shwith:- Bash shebang;
set -Eeuo pipefail;umask 077;- a no-op
mainthat supports onlyhelpinitially; - the source-safe
mainguard; - no root requirement for
help.
- Create
wg-manager.conf.examplewith placeholder values only. - Create
.gitignoreentries for:wg-manager.conf;backups/;logs/;state/;- generated client configs and key files;
- temporary test output.
- Create
tests/run.shas a pure Bash test runner. - Create
tests/helpers.shwith helpers for:- creating one isolated temporary test root;
- copying fixtures;
- prepending
tests/mocks/bintoPATH; - asserting equality, exit status, substring, file existence, and file mode;
- printing a final pass/fail summary;
- cleanup through
trap.
- Create fixture directories and one minimal valid
wg0.conffixture. - Create mock placeholders for
wgandwg-quickthat fail loudly on unexpected arguments. - Create mock placeholders for
ip,systemctl,journalctl,ss,sysctl,nft,iptables,qrencode, andgumas they become needed. - Add initial tests for:
- sourcing
wg-manager.shdoes not executemain; helpexits with status 0;- an unknown command exits nonzero;
- the test harness never references
/etc/wireguardfor writes.
- sourcing
- Add a guard that records every external mock invocation and fails on unexpected commands or arguments.
- Do not implement WireGuard operations yet.
Verification
Run:
bash -n wg-manager.sh
bash -n tests/run.sh
bash -n tests/helpers.sh
bash tests/run.sh
./wg-manager.sh help
If ShellCheck is installed, also run:
shellcheck wg-manager.sh tests/run.sh tests/helpers.sh tests/mocks/bin/*
Review:
git diff --check
git status --short
Acceptance criteria
- The test suite runs without root.
- The test suite creates all writable data below a temporary test directory.
- No test can alter
/etc/wireguard. - The test suite does not require real
wg,wg-quick, root, a network interface, or systemd. - Unexpected external command invocations fail loudly.
wg-manager.sh helpworks from outside the project directory.- No real key, endpoint, or public IP is committed.
- All verification commands pass.
Suggested commit message
test: add isolated Bash test harness and project skeleton
Milestone M1 — Runtime foundation, CLI shell, and configuration
Goal
Build the reusable runtime foundation: path discovery, errors, exit codes, dependency detection, restricted configuration loading, and predictable CLI dispatch.
Prerequisites
- M0 is
DONE.
Changes
- Define stable exit codes for:
- success;
- usage error;
- validation error;
- missing dependency;
- permission error;
- operation failure;
- rollback failure.
- Implement
die,warn,info, and debug helpers. - Keep stdout for command results and stderr for progress/errors.
- Resolve
SCRIPT_DIRwithout depending on the current directory. - Add overridable runtime paths derived from
SCRIPT_DIR:- config path;
- backup directory;
- log directory;
- metadata directory;
- archive directory;
- lock file.
- Implement a restricted parser for simple
KEY=VALUElines:- ignore blank lines and full-line comments;
- allow only documented keys;
- reject duplicate keys;
- reject command substitutions, shell syntax, and embedded newlines;
- trim only documented surrounding whitespace;
- do not call
sourceoreval.
- Validate required configuration:
- interface name;
- absolute server config path;
- absolute client directory path;
- endpoint and port;
- IPv4 prefix and host range;
- IPv6 settings when enabled;
- supported client mode;
- backup retention count;
- recent-handshake threshold.
- Implement command discovery helpers for required and optional commands.
- Implement
gumresolution in the fixed order:WG_MANAGER_GUM;${SCRIPT_DIR}/bin/gum;command -v gum.
- Add CLI dispatch for:
help;version;- placeholder known commands that report “not implemented” with a stable status;
- unknown command and extra argument errors.
- Require root only inside future mutating command paths, not globally.
- Add unit tests for all parsing, validation, path, and
gumresolution branches.
Verification
Run:
bash -n wg-manager.sh
bash tests/run.sh
./wg-manager.sh help
./wg-manager.sh version
Verify execution from a different directory:
(cd /tmp && /home/sasa/share/bash/wireguard-scripts/wg-manager.sh help)
Verify invalid configuration fixtures fail without executing embedded text:
bash tests/run.sh config
If available:
shellcheck wg-manager.sh tests/*.sh tests/mocks/bin/*
Then run:
git diff --check
Acceptance criteria
- No configuration content is executed as shell code.
- Running from another working directory resolves project-local paths correctly.
- CLI help and version do not require root, WireGuard, or
gum. - CLI operations can be tested with an alternate config and runtime root.
- Missing real WireGuard tools are handled through dependency checks and controlled mocks.
- All invalid configuration cases fail before any mutation.
gumresolution order is covered by tests.
Suggested commit message
feat(core): add validated configuration and CLI foundation
Milestone M2 — WireGuard parser, client model, status, and list
Goal
Read persistent and active WireGuard state without mutation, normalize peers into a client model, and expose reliable status and list commands.
Prerequisites
- M1 is
DONE.
Changes
- Add fixtures for:
- interface with no peers;
- one managed peer;
- multiple managed peers;
- a legacy
### Client NAMEpeer; - an unmanaged peer;
- duplicate public keys;
- duplicate and overlapping
AllowedIPs; - IPv4 and IPv6 peers.
- Implement parsing of
[Interface]and[Peer]sections without executing config text. - Preserve exact public keys and normalized address lists.
- Parse managed block markers and legacy client comments.
- Implement one metadata file per client under
state/clients. - Define and validate metadata fields:
NAME;PUBLIC_KEY;IPV4;- optional
IPV6; CREATED_AT;CONFIG_PATH;CLIENT_MODE;MANAGED.
- Never trust metadata over the public key and current config.
- Implement normalized client records combining:
- disk config;
- metadata;
wg show <interface> dumpoutput.
- Implement client states:
activewhen latest handshake is within the configured threshold;inactivewhen a nonzero handshake is older;neverwhen handshake is zero;unmanagedwhen no trusted managed identity exists.
- Implement
statuswith:- interface name;
- runtime availability;
- listening port;
- peer count;
- managed/unmanaged counts;
- active/recent count.
- Implement
listwith deterministic, tabular plain-text output. - Do not use ANSI decoration when stdout is not a terminal.
- Ensure read-only commands return useful results when systemd is absent.
- Add tests for parser errors and all state classifications.
Verification
Run:
bash -n wg-manager.sh
bash tests/run.sh parser
bash tests/run.sh status
bash tests/run.sh list
bash tests/run.sh
Verify redirected output entirely through fixtures and mocks:
bash tests/run.sh list-redirected
Using only test configuration and mocks, confirm deterministic output:
LC_ALL=C bash tests/run.sh list-deterministic
If available:
shellcheck wg-manager.sh tests/*.sh tests/mocks/bin/*
Finally:
git diff --check
Acceptance criteria
- Persistent config and runtime dump parsing are independently tested.
- A client name is never used as peer identity without a matching public key.
- Managed, legacy, and unmanaged peers are distinguishable.
- Empty and stopped interfaces produce useful output rather than crashes.
- No read-only command writes runtime state.
statusandlistwork withoutgum.- Required verification does not query the development host's actual interfaces or services.
Suggested commit message
feat(clients): add WireGuard state parsing and client listing
Milestone M3 — Transaction, backup, apply, and rollback engine
Goal
Provide one safe mutation primitive that all later add, remove, and restore operations must use.
Prerequisites
- M2 is
DONE.
Changes
- Add mocks for successful and failing variants of:
flockbehavior where practical;wg-quick strip;wg syncconf;- active-state verification.
- Implement exclusive locking with one documented lock path.
- Re-read source config and relevant metadata after locking.
- Implement timestamped backup creation with:
- UTC timestamp;
- interface name;
- original file mode and ownership information where available;
- copied metadata relevant to the operation;
- a small manifest.
- Implement backup retention without removing the only known-good backup.
- Create staging files on the same filesystem as the target config.
- Use
umask 077and explicitly enforce restrictive modes. - Validate a staged config with
wg-quick stripbefore replacement. - Atomically replace the server config.
- Apply runtime configuration using
wg syncconfwith stripped configuration. - Verify that expected public keys and
AllowedIPsare active. - On apply or verification failure:
- restore the prior disk config;
- restore prior runtime state;
- verify rollback;
- return a distinct rollback-failure status if recovery fails.
- Preserve the original config if staging validation fails.
- Ensure cleanup preserves the operation's final exit status.
- Expose the engine only through internal functions; do not add add/remove yet.
- Add interruption tests where feasible using a controlled mock.
- Assert exact
wg-quick stripandwg syncconfargument arrays in command-contract tests.
Verification
Run:
bash -n wg-manager.sh
bash tests/run.sh transaction
bash tests/run.sh rollback
bash tests/run.sh locking
bash tests/run.sh backups
bash tests/run.sh
Verify the original fixture remains byte-identical after each failure test:
bash tests/run.sh mutation-preserves-source
Inspect permissions in the test root:
bash tests/run.sh secret-modes
If available:
shellcheck wg-manager.sh tests/*.sh tests/mocks/bin/*
Then:
git diff --check
Acceptance criteria
- Concurrent mutations cannot both enter the critical section.
- Invalid staging content never replaces the live file.
- Apply failure restores disk and runtime state in tests.
- Rollback failure is visible and has a distinct status.
- Backups are root-readable only by default.
- No later mutation needs to reimplement locking or rollback.
- Transaction verification is complete using mocks; it does not claim a real WireGuard apply occurred.
Suggested commit message
feat(safety): add atomic WireGuard mutation and rollback engine
Milestone M4 — Safe client creation
Goal
Replace the unsafe mutation behavior of add-user.sh with a tested add command built on the transaction engine.
Prerequisites
- M3 is
DONE.
Changes
- Implement strict client-name validation.
- Reject names with:
- spaces;
- slashes;
- leading option markers when unsafe;
- control characters;
- newlines;
- glob characters;
- characters outside
[A-Za-z0-9._-].
- Check for conflicts in:
- managed metadata;
- server peer comments;
- public keys;
- client config and key files;
- IPv4 and IPv6
AllowedIPs.
- Implement deterministic next-free IPv4 selection within the configured range.
- Implement optional IPv6 allocation derived from the validated host identity.
- Fail cleanly when the address pool is exhausted.
- Generate private, public, and preshared keys using
wgwithout logging them. - Avoid placing key values in command-line arguments.
- Build the client config with:
- address;
- DNS;
- server public key;
- preshared key;
- endpoint and port;
- persistent keepalive;
- full-tunnel or split-tunnel
AllowedIPs.
- Build an explicit managed peer block in the server config.
- Keep the block format stable and covered by golden fixtures.
- Write client key and config files with mode
0600. - Persist metadata only when the server mutation succeeds.
- Remove newly generated files if the operation fails before commit.
- Use the M3 transaction engine for the server config.
- Add
add NAMECLI parsing and optional documented flags for mode or endpoint. - Require root for the actual add operation.
- Keep
add-user.shunchanged as a legacy reference during this milestone. - Add tests for successful add, collision cases, pool exhaustion, and rollback.
- Use deterministic fake key material from the mock
wg; do not require cryptographically valid WireGuard keys on the development machine.
Verification
Run:
bash -n wg-manager.sh
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
Inspect generated fixture output for exact sections:
bash tests/run.sh add-golden-config
Verify invalid names never reach the mocked wg genkey command:
bash tests/run.sh add-invalid-names
If available:
shellcheck wg-manager.sh tests/*.sh tests/mocks/bin/*
Then:
git diff --check
Acceptance criteria
- A client can be added through CLI using only mocked test state.
- All conflicts are detected before persistent mutation.
- Generated secret files have mode
0600. - Failed apply leaves no active peer, persistent peer, or orphaned metadata.
- Full-tunnel and split-tunnel configs are independently tested.
- No secret appears in captured stdout, stderr, or audit output.
- Tests verify the
wg genkey,wg pubkey, andwg genpskcommand contract without requiring the real binaries.
Suggested commit message
feat(clients): add transactional client creation
Milestone M5 — Client detail, config export, and QR display
Goal
Allow an administrator to inspect a client, locate its configuration, and display a QR code without exposing secrets accidentally.
Prerequisites
- M4 is
DONE.
Changes
- Implement
show NAMEwith non-secret fields:- name;
- public key;
- assigned addresses;
- client mode;
- creation date;
- endpoint;
- last handshake;
- transfer counters;
- managed/unmanaged state;
- client config path when available.
- Implement
config NAMEto print the validated config path by default. - Require an explicit flag or interactive confirmation before printing full config content.
- Never print separate private key or preshared key files.
- Implement
qr NAMEusingqrencode -t ansiutf8when available. - If
qrencodeis absent:- return the documented optional-capability status;
- show the config path and a clear message;
- do not fail unrelated commands.
- Reject unmanaged peers for config and QR operations unless a safe managed file association exists.
- Validate that the config path stays inside the configured client directory.
- Reject symlink or ownership surprises that could expose an unrelated file.
- Add tests for missing clients, missing config, unmanaged peers, and missing
qrencode.
Verification
Run:
bash -n wg-manager.sh
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
Confirm normal detail output contains no private material:
bash tests/run.sh no-secret-logs
If available:
shellcheck wg-manager.sh tests/*.sh tests/mocks/bin/*
Then:
git diff --check
Acceptance criteria
showis safe to use in routine administration.- Full config content requires an explicit action.
qrworks with a mockqrencodeand degrades clearly without it.- Unmanaged peers cannot cause arbitrary file reads.
- Config paths are validated before access.
Suggested commit message
feat(clients): add client details and secure config export
Milestone M6 — Safe client removal and archival
Goal
Remove a managed peer from persistent and active WireGuard state while preserving recoverable client artifacts.
Prerequisites
- M5 is
DONE.
Changes
- Implement
remove NAMEfor managed clients only. - Resolve the exact target through metadata and public key.
- Refuse ambiguous names, duplicate identities, and unmanaged peers.
- Display the exact name, public key suffix, and addresses before confirmation.
- For interactive CLI, require explicit confirmation.
- For noninteractive CLI, require a documented
--yesflag. - Remove only the exact managed peer block.
- Reject removal if block boundaries or public key do not match expectations.
- Use the transaction engine to apply the new server config.
- After successful server mutation, move client files and metadata into a timestamped archive directory.
- Do not follow unexpected symlinks while archiving.
- If artifact archival fails after peer removal:
- report the partial failure clearly;
- keep metadata sufficient for recovery;
- do not silently claim full success.
- Record the removal in the audit log without secrets.
- Do not implement permanent purge in the MVP.
- Add tests for cancellation, exact-block removal, rollback, and archival failure.
Verification
Run:
bash -n wg-manager.sh
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
Confirm a neighboring peer is byte-identical after removal:
bash tests/run.sh remove-preserves-neighbors
If available:
shellcheck wg-manager.sh tests/*.sh tests/mocks/bin/*
Then:
git diff --check
Acceptance criteria
- Only the selected public key is removed.
- Cancellation makes no changes.
- Apply failure restores the peer and leaves artifacts in place.
- Successful removal archives client files with restrictive permissions.
- Unmanaged peers cannot be removed through the managed-client command.
- The audit event contains no key material beyond a permitted shortened identifier.
Suggested commit message
feat(clients): add safe client removal and archival
Milestone M7 — Diagnostics engine
Goal
Provide an evidence-based diagnose command that identifies common WireGuard, routing, permission, and firewall problems without modifying the server.
Prerequisites
- M6 is
DONE.
Changes
- Define a diagnostic result record with:
- check identifier;
- status:
OK,WARN,FAIL, orSKIP; - concise Russian summary;
- optional remediation guidance.
- Keep checks independent so one failure does not stop the report.
- Implement checks for:
- required commands;
- readable server config;
- config and key permissions;
- interface existence;
- active WireGuard dump;
- systemd service state when systemd is present;
- config parsing and duplicate keys;
- duplicate or overlapping
AllowedIPs; - IPv4 forwarding;
- IPv6 forwarding when enabled;
- expected interface addresses and routes;
- configured UDP listening port using available tools;
- firewall/NAT visibility through
nftoriptableswhen available; - disk versus runtime peer differences;
- recent relevant journal entries when
journalctlis usable.
- Never claim a firewall is correct merely because one command exists.
- Use
SKIPfor unsupported init systems or missing optional tools. - Use
WARNfor uncertain or policy-dependent conditions. - Use
FAILonly for conditions that clearly prevent expected operation or violate a required invariant. - Return nonzero when any
FAILexists. - Add
diagnose --quietor equivalent machine-friendly summary if useful, but JSON is not required. - Add fixtures and mocks for every status branch.
- Ensure diagnostics are read-only.
- Force diagnostic tests to consume mock host state rather than the development machine's actual
/proc, service manager, routes, firewall, or journal.
Verification
Run:
bash -n wg-manager.sh
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
Verify exit behavior:
bash tests/run.sh diagnostics-exit-codes
If available:
shellcheck wg-manager.sh tests/*.sh tests/mocks/bin/*
Then:
git diff --check
Acceptance criteria
- Every check reports exactly one recognized status.
- Independent checks continue after failures.
- Missing optional tools produce
SKIP, not a crash. - At least one tested scenario produces each of
OK,WARN,FAIL, andSKIP. - The command exits nonzero when critical failures exist.
- No diagnostic check mutates config, runtime state, firewall, routes, or sysctl values.
- Results describe fixture state only and do not depend on the development host.
Suggested commit message
feat(diagnostics): add read-only WireGuard health checks
Milestone M8 — Backup restoration and audit log UX
Goal
Expose safe backup management and administrator-visible audit history using the primitives already created by mutations.
Prerequisites
- M7 is
DONE.
Changes
- Finalize structured, line-oriented audit events with:
- UTC timestamp;
- effective UID or operator identity;
- operation;
- safe target identifier;
- outcome;
- concise error code or summary.
- Ensure all existing add, remove, apply, rollback, and backup actions log their final outcome.
- Implement
logwith:- newest or oldest ordering documented;
- optional bounded number of entries;
- readable behavior when no log exists.
- Implement
backupto create an explicit manual backup. - Implement
backupsto list valid backup manifests only. - Treat malformed or incomplete backup directories as invalid and report them safely.
- Implement
restore BACKUPwith:- strict backup identifier validation;
- containment inside the configured backup directory;
- validation of the backup config;
- preview of timestamp and interface;
- explicit confirmation;
- a backup of current state before restoration;
- transaction-engine apply and rollback.
- Require
--yesfor noninteractive restoration. - Do not restore a backup for a different interface without explicit future support.
- Add tests for tampered manifests, traversal attempts, apply failure, and restoration rollback.
- Add tests that search all audit output for fixture secrets.
Verification
Run:
bash -n wg-manager.sh
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
Inspect permissions:
bash tests/run.sh runtime-file-modes
If available:
shellcheck wg-manager.sh tests/*.sh tests/mocks/bin/*
Then:
git diff --check
Acceptance criteria
- Audit logs cover every mutating path and rollback result.
- Fixture private and preshared keys are absent from logs.
- Backup listing ignores invalid entries without executing content.
- Restore cannot escape the configured backup directory.
- Restore validates before mutation and backs up current state first.
- Failed restore returns the host to its prior test state.
Suggested commit message
feat(backup): add audited backup listing and restoration
Milestone M9 — TUI foundation and read-only screens
Goal
Create a consistent gum TUI shell and expose the dashboard, client list, client detail, diagnostics, logs, and backups without duplicating backend logic.
Prerequisites
- M8 is
DONE.
Changes
- Implement small TUI wrapper functions for:
- style/header;
- choose;
- filter;
- input;
- confirm;
- table;
- pager;
- spinner.
- Keep all raw
gumflags inside wrappers. - Resolve and validate
gumbefore entering the TUI. - Detect noninteractive stdin/stdout and refuse TUI cleanly.
- Treat Escape and cancellation as normal navigation.
- Implement the main dashboard with:
- configured interface;
- runtime state;
- managed peer count;
- active/recent count;
- warning count where inexpensive to obtain.
- Implement a shallow main menu for:
- status;
- clients;
- add placeholder or action entry;
- remove placeholder or action entry;
- diagnostics;
- audit log;
- backups;
- exit.
- Implement searchable client selection using stable hidden identity, not only display text.
- Implement read-only screens by calling existing backend functions:
- status;
- list;
- show;
- diagnostics;
- log;
- backups.
- Use a pager for long output.
- Do not parse decorated TUI output back into backend data.
- Keep the layout usable at 80 columns.
- Do not rely on color alone for status.
- Add a mock
gumthat records arguments and returns scripted selections. - Add tests for menu routing, cancellation, missing
gum, resolution order, and non-TTY refusal.
Verification
Run:
bash -n wg-manager.sh
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
If a usable gum binary and interactive terminal are available, an optional smoke test may run with the test configuration and mocked system commands:
WG_MANAGER_GUM="$(command -v gum)" \
WG_MANAGER_CONFIG="$(pwd)/tests/fixtures/configs/valid.conf" \
PATH="$(pwd)/tests/mocks/bin:$PATH" \
./wg-manager.sh
Manual checks:
- open and exit every read-only screen;
- press Escape from each selection screen;
- verify long output is scrollable;
- verify no screen writes production state;
- resize the terminal to approximately 80 columns.
If real gum is unavailable, record the manual smoke test as SKIP; the scripted mock-TUI tests remain the required gate.
If available:
shellcheck wg-manager.sh tests/*.sh tests/mocks/bin/*
Then:
git diff --check
Acceptance criteria
- TUI startup fails clearly when
gumis absent or unusable. - CLI remains fully operational without
gum. - Every read-only screen uses existing backend data/functions.
- Cancellation returns to the expected previous screen.
- TUI tests do not require a real terminal or real WireGuard instance.
- No second TUI framework is introduced.
- Required TUI verification does not require real WireGuard or real
gum.
Suggested commit message
feat(tui): add gum dashboard and read-only workflows
Milestone M10 — Complete TUI mutation workflows
Goal
Expose add, remove, QR, manual backup, and restore workflows through gum while preserving all CLI safety guarantees.
Prerequisites
- M9 is
DONE.
Changes
- Implement the add-client TUI flow:
- prompt for name;
- validate immediately;
- choose full-tunnel or split-tunnel;
- collect split networks when required;
- optionally confirm configured endpoint;
- preview non-secret result;
- confirm creation;
- call the existing add backend;
- show final config path and optional QR action.
- Implement the remove-client TUI flow:
- select only managed clients;
- display exact target and addresses;
- require explicit confirmation;
- call the existing remove backend;
- display archive location.
- Implement client actions:
- details;
- show config after confirmation;
- show QR when available;
- remove.
- Implement manual backup from TUI.
- Implement restore flow:
- select a validated backup;
- show manifest details;
- use a stronger confirmation prompt;
- call the restore backend;
- show rollback outcome on failure.
- Use spinners only around backend operations whose errors are captured and shown.
- Never place secrets inside
gumcommand arguments. - Never use a spinner that discards actionable stderr.
- Return to a stable menu after success or cancellation.
- Stop returning to the menu after a critical rollback failure; show the error and exit nonzero.
- Add scripted mock-TUI tests for each success, cancel, validation failure, backend failure, and rollback-failure flow.
Verification
Run:
bash -n wg-manager.sh
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
With a usable gum and a test-only mocked backend, optionally verify:
- add flow success and cancellation;
- invalid name correction;
- split-tunnel input;
- removal confirmation defaults to safe cancellation;
- QR fallback when
qrencodeis missing; - restore requires deliberate confirmation;
- backend errors remain readable after a spinner.
If real gum is unavailable, record these manual checks as SKIP; all scripted mutation-flow tests remain mandatory.
If available:
shellcheck wg-manager.sh tests/*.sh tests/mocks/bin/*
Then:
git diff --check
Acceptance criteria
- TUI mutation paths call the same backend functions as CLI paths.
- Cancellation makes no changes.
- User input is validated before backend execution.
- Destructive actions show the exact target and require confirmation.
- Secrets are absent from recorded
gumarguments and logs. - Critical rollback failure exits instead of pretending the TUI can continue safely.
Suggested commit message
feat(tui): add safe client and backup workflows
Milestone M11 — Hardening, documentation, and MVP acceptance
Goal
Verify the complete MVP as one coherent tool, close safety gaps, document operation, and produce a release-ready baseline.
Prerequisites
- M0 through M10 are
DONE.
Changes
- Review every requirement in
MVP.mdand map it to code and tests. - Review every applicable rule in
AGENTS.md. - Add end-to-end fixture scenarios for:
- fresh server with no clients;
- existing managed clients;
- mixed managed and unmanaged peers;
- add, inspect, QR fallback, remove, and restore;
- failed apply and successful rollback;
- failed rollback;
- missing optional dependencies;
- stopped WireGuard interface;
- malformed configuration and metadata.
- Add a test that scans repository fixtures and test output for known secret markers.
- Add a test that prevents writes outside the isolated test root.
- Add a test that fails if production code invokes host networking or service commands instead of test mocks.
- Review all filesystem operations for traversal and symlink risks.
- Review every command invocation for quoting and option injection.
- Review every error path for secret leakage.
- Review exit codes for consistency.
- Decide the legacy
add-user.shoutcome:- keep it explicitly documented as deprecated reference; or
- replace it with a small compatibility wrapper;
- do not leave two independent mutation implementations.
- Create
README.mdwith:- purpose and limitations;
- supported environment;
- directory layout;
- required and optional dependencies;
- operator-managed
gumplacement; - configuration procedure;
- CLI examples;
- TUI launch;
- backup and rollback behavior;
- file permissions;
- safe test procedure;
- troubleshooting;
- warning that handshake data is not a full connection history.
- Document that development verification is mock-based and does not prove kernel, distribution, firewall, or live tunnel compatibility.
- Add a separate deployment-server verification checklist to README.
- Ensure examples contain placeholder endpoints and keys only.
- Update
MVP.mdonly if implemented behavior intentionally changes the approved scope. - Complete the execution log and decision log below.
Verification
Run the complete automated gate:
bash -n wg-manager.sh
bash -n tests/run.sh
bash -n tests/helpers.sh
bash tests/run.sh
If available, run:
shellcheck wg-manager.sh tests/*.sh tests/mocks/bin/*
Run repository checks:
git diff --check
git status --short
Run focused security checks:
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
The following deployment-server checklist is not run on the development machine and does not block M11. Run it later only on a disposable or explicitly authorized non-production WireGuard interface:
- Create or select a non-production test interface and config.
- Point
wg-manager.confonly to that interface and isolated client directory. - Run
status,list, anddiagnose. - Add a test client through CLI.
- Confirm disk config, runtime peer, metadata, file modes, and audit event.
- Inspect the client through TUI and display a QR code when available.
- Remove the test client through TUI.
- Confirm the peer is gone and artifacts are archived.
- Restore the pre-add backup.
- Confirm disk and runtime state match the initial state.
Additional deployment checks:
- Run
diagnoseand compare its claims with independently observed server state. - Confirm the actual
wg-quickand WireGuard versions are supported. - Confirm the supplied
gumbinary matches the server architecture and starts over SSH. - Confirm the server firewall backend is detected conservatively.
- Record distribution-specific differences as follow-up issues rather than weakening offline tests.
Do not run this checklist against a production interface as part of unattended verification. Do not mark mock results as evidence that this checklist passed.
Acceptance criteria
- Every MVP criterion has a corresponding passing check or documented manual verification.
- Full automated tests pass without root and without touching host WireGuard state.
- Shell syntax checks pass.
- ShellCheck has no unexplained findings when available.
- CLI works without
gum. - TUI behavior passes scripted
gummock tests; a real-gumsmoke test is optional on the development machine. - Add, remove, and restore are transactional and recoverable.
- No known secret is present in logs, diffs, or test artifacts.
- README instructions match observed behavior.
- No independent unsafe mutation path remains in
add-user.sh. git diff --checkpasses when the project is a Git worktree.- Required verification passes without real WireGuard commands, root access, systemd, or host networking access.
- The deferred deployment-server checklist is documented and clearly marked as not yet executed.
Suggested commit message
docs: finalize WireGuard Manager MVP and operations guide
Suggested MVP release commit message
Use this only if the completed milestones are squashed into one commit:
feat: deliver portable WireGuard management MVP
Add a Bash CLI and gum-based TUI for listing, creating, inspecting,
diagnosing, removing, backing up, and restoring WireGuard clients with
transactional configuration updates, rollback, and secret-safe auditing.
6. Decision log
Add entries only for decisions that affect later milestones. Do not record routine implementation details.
| Date | Milestone | Decision | Reason | Consequence |
|---|---|---|---|---|
| YYYY-MM-DD | Mx | Example decision | Why it was necessary | What later work must preserve |
7. Execution log
Append one concise entry after completing or blocking a milestone.
Use this template:
## YYYY-MM-DD — Mx — DONE|BLOCKED
- Implemented: ...
- Coordinator: `gpt-5.6-sol` (`high`)
- Subagents: `milestone_worker` / `milestone_repairer`, `gpt-5.6-luna` (`medium`)
- Repair cycles: 0 | number
- Sol review: findings and resolution | no material findings
- Verification: `command`, `command`
- Result: ...
- Decisions: none | link to decision-log entry
- Known limitations: none | ...
- Next milestone: My
2026-10-11 — M0 — DONE
- Implemented: strict source-safe
wg-manager.shhelp shell, placeholder configuration, runtime ignores, isolated pure-Bash test harness, minimal WireGuard fixture, and deny-by-default logged mocks for all planned external commands. - Coordinator:
gpt-5.6-sol(high) - Subagents:
milestone_worker/milestone_repairer,gpt-5.6-luna(medium) - Repair cycles: 2
- Sol review: replaced a tautological host-path check with a recursive pure-Bash guard; made mocks deny empty calls unless explicitly authorized and assert exact invocation logs; removed an unused production path variable, added a narrow ShellCheck source directive, and cleared unintended index staging.
- 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,./wg-manager.sh help,(cd /tmp && /home/sasa/share/bash/wireguard-scripts/wg-manager.sh help),git diff --check,git diff --no-index --check /dev/null <new-file>,git status --short - Result: all required checks passed; 13 test assertions passed; unknown commands and extra arguments returned status 64; executable modes and isolated mock behavior were verified. Optional
shellcheck wg-manager.sh tests/run.sh tests/helpers.sh tests/mocks/bin/*wasSKIPbecause ShellCheck is unavailable. - Decisions: none
- Known limitations: real WireGuard, networking, service, firewall, route, sysctl, journal, and deployment-server checks were not run; M0 verification is fixture- and mock-based as required.
- Suggested commit message:
test: add isolated Bash test harness and project skeleton - Next milestone: M1
2026-10-11 — M1 — DONE
- Implemented: stable exit codes and output helpers, caller-independent project paths with scoped runtime overrides, restricted non-executing configuration parsing and semantic validation, dependency discovery, fixed-order
gumresolution, version/help and known-command CLI dispatch, focused configuration tests, and M1 usage documentation. - Coordinator:
gpt-5.6-sol(high) - Subagents:
milestone_worker/milestone_repairer,gpt-5.6-luna(medium) - Repair cycles: 3
- Sol review: replaced duplicate-masked semantic tests with single-cause fixtures; added real focused-test dispatch and missing parser/validator/gum/dependency/CLI branches; made disabled debug strict-mode safe; fixed server-address range containment, empty/trailing DNS elements, numeric endpoint and hostname validation, gum regular-file checks, malformed IPv6 compression, and bracket-glob paths; removed unintended staging from two test helpers.
- Verification:
bash -n wg-manager.sh,bash -n tests/*.sh,bash -n tests/mocks/bin/*,bash tests/run.sh,./wg-manager.sh help,./wg-manager.sh version,(cd /tmp && /home/sasa/share/bash/wireguard-scripts/wg-manager.sh help),bash tests/run.sh config,git diff --check,git diff --no-index --check /dev/null <new-file>, redirected-output checks, and independent IPv6/path/debug boundary probes. - Result: all required checks passed; the full suite passed 158 assertions and the focused configuration suite passed 109 assertions. Help and version required no configuration, root, WireGuard, or
gum; malicious configuration remained inert; alternate runtime paths and gum precedence passed through isolated fixtures and mocks. Optionalshellcheck wg-manager.sh tests/*.sh tests/mocks/bin/*wasSKIPbecause ShellCheck is unavailable. - Decisions: none
- Known limitations: real WireGuard, networking, service, firewall, route, sysctl, journal, and deployment-server checks were not run; M1 verification is fixture- and mock-based as required.
- Suggested commit message:
feat(core): add validated configuration and CLI foundation - Next milestone: M2
8. Final completion rule
The MVP is complete only when:
- M0 through M11 are marked
DONE; - the complete M11 verification passes;
- all known critical failures are fixed;
- optional skipped checks are documented;
- the execution log describes the delivered state;
- the user receives the final verification summary and suggested release commit message.
Do not mark the project complete merely because the implementation exists. Completion requires evidence from tests, static checks, diff review, and the documented acceptance flow.