220 lines
12 KiB
Markdown
220 lines
12 KiB
Markdown
# wireguard-admin
|
||
|
||
This repository contains a root-only native terminal interface for one existing
|
||
WireGuard interface and configurable systemd service. It connects the validated
|
||
settings loader, byte-preserving server configuration editor, encrypted client
|
||
vault, recoverable saved-file persistence, backups, Commit, previewed restore,
|
||
explicit restart, read-only runtime statistics, and client file/QR delivery.
|
||
The interface keeps draft, saved, and applied states distinct. Commit saves
|
||
files; restore replaces saved state; only an explicit, verified restart applies
|
||
saved changes to the running interface.
|
||
|
||
The package requires Rust 1.97.1 or newer and uses the Rust 2024 edition. From
|
||
the repository root, run:
|
||
|
||
```sh
|
||
cargo run -- --help
|
||
cargo run -- --version
|
||
```
|
||
|
||
`--help` and `--version` work without root, settings, or a terminal. Starting
|
||
with no arguments requires root and a suitable interactive terminal. Settings
|
||
are read from `wireguard-admin.toml` beside the executable. If terminal checks
|
||
or saved-state recovery fail, startup exits before entering raw mode or
|
||
performing setup mutations.
|
||
|
||
## Settings
|
||
|
||
Settings use TOML in the fixed file `wireguard-admin.toml` beside the
|
||
executable. Start from [`config/wireguard-admin.example.toml`](config/wireguard-admin.example.toml).
|
||
The required fields are:
|
||
|
||
- `interface_name`: existing WireGuard interface name (1–15 ASCII characters).
|
||
- `server_config_path`: path to the existing server configuration.
|
||
- `client_source_directory`: directory containing existing client files.
|
||
- `server_endpoint`: hostname or IPv4 address followed by `:port`, or IPv6 in
|
||
brackets followed by `:port` (for example, `[2001:db8::2]:51820`).
|
||
- `ipv4_allocation_range` and `ipv6_allocation_range`: CIDR ranges of the
|
||
matching address family.
|
||
- `dns_addresses`: non-empty list of IPv4 and/or IPv6 literal addresses.
|
||
- `systemd_unit`: service unit name ending in `.service`.
|
||
- `restart_timeout_seconds`: positive integer.
|
||
|
||
`data_root` is optional. It defaults to `Backup` beside the executable;
|
||
current internal data and the encrypted vault are under `data`, and archives
|
||
are under `archives`. Relative configured paths, including an explicit
|
||
`data_root`, resolve from the executable directory. Absolute paths remain
|
||
absolute. Settings validation checks syntax and values only; it does not
|
||
require the referenced files, directories, or service to exist and does not
|
||
perform DNS lookups.
|
||
|
||
For installation, backup custody, restart failure handling and manual recovery,
|
||
see [`docs/OPERATIONS.md`](docs/OPERATIONS.md). The qualified evidence and the
|
||
current host release-candidate assessment are in
|
||
[`docs/RELEASE_READINESS.md`](docs/RELEASE_READINESS.md); those records do not
|
||
claim a completed systemd VM run or real IPv6 qualification.
|
||
|
||
## Terminal workflows
|
||
|
||
The bordered **Keys** panel at the bottom of the screen shows shortcuts for the
|
||
current screen and form. On narrow list and details screens it prioritizes
|
||
navigation and Help, and indicates when additional actions are omitted; resize
|
||
the terminal to see more. Forms, QR disclosure and progress show only their
|
||
applicable controls. Press `?` from the list or details screen to open Help,
|
||
which contains the complete shortcut reference. The client list supports
|
||
arrow-key selection, Enter for details, and `q` to exit. Exit warns when a draft
|
||
would be discarded; it never commits implicitly. Uppercase shortcuts are
|
||
distinct from lowercase shortcuts (`R` previews restart, while `r` renames;
|
||
`B` creates a backup, while `b` lists backups):
|
||
|
||
| Key | Action |
|
||
| --- | --- |
|
||
| `n` | Create a client with automatic or manual dual-stack addresses |
|
||
| `r` / `d` | Rename or delete the selected supported peer by full public key |
|
||
| `p` / `x` | Review or discard the draft |
|
||
| `c` | Commit saved files after the mandatory backup |
|
||
| `R` | Preview explicit restart; confirm only after reviewing the choice |
|
||
| `o` / `O` | First file delivery / password-per-action reissue |
|
||
| `b` / `B` | List and preview backups / create a manual backup |
|
||
| `s` / `i` | Initial vault setup / import one selected client file |
|
||
| `!` | Inspect document and peer anomalies |
|
||
|
||
The list header labels **Draft**, **Saved**, and **Applied** separately. Draft
|
||
reports pending changes even when only vault or managed metadata changed. Saved
|
||
counts peer sections in the saved server file, including peers this manager
|
||
cannot edit, and separately counts managed clients. Applied reports verification
|
||
of the saved configuration; a draft is excluded. “Not verified” does not claim
|
||
that a restart is pending when that divergence is unknown. A saved peer row does
|
||
not prove active access, and handshake or traffic counters are observations,
|
||
not proof that the saved configuration was applied. Details distinguish
|
||
document or peer problems from valid configuration shapes that are currently
|
||
outside the management model.
|
||
|
||
Forms use Enter to advance or submit, Tab to change fields/options, and Esc to
|
||
cancel. Backup restore previews identify the selected archive and require an
|
||
explicit confirmation. Restore does not prompt for the vault password and does
|
||
not restart WireGuard. Client configuration output is a disclosure: first
|
||
delivery requires a successful Commit in the same session; QR appears only
|
||
after file publication. Esc closes and clears a QR disclosure. Help in the
|
||
interface contains the full current shortcut list.
|
||
|
||
## Library configuration API
|
||
|
||
The library exposes `wireguard_admin::wireguard::ConfigDocument` for UTF-8
|
||
configuration bytes supplied by the caller. It retains the source and renders
|
||
unmodified input byte-for-byte, including line endings and a missing final
|
||
newline. Peer inspection exposes public identities, parsed `AllowedIPs`, spans,
|
||
and redacted issues; arbitrary directive values are not included in the view.
|
||
The edit methods `add_client`, `delete_client`, `rename_client`, and
|
||
`insert_legacy_metadata` return new in-memory documents. They do not read or
|
||
write files, save changes, or alter the running VPN. See
|
||
[`docs/CONFIG_FORMAT.md`](docs/CONFIG_FORMAT.md) for the managed metadata block
|
||
format.
|
||
|
||
Example:
|
||
|
||
```rust,ignore
|
||
use wireguard_admin::wireguard::ConfigDocument;
|
||
|
||
let document = ConfigDocument::parse(config_bytes)?;
|
||
let updated = document.rename_client(&public_key, "office laptop")?;
|
||
let bytes_to_review = updated.render(); // no persistence or VPN operation
|
||
```
|
||
|
||
Runtime and restart behavior has synthetic fixture and fake-adapter coverage;
|
||
the accepted Alpine milestone also exercised real IPv4 WireGuard traffic and
|
||
the Rustix reader. The Alpine fixture did not use systemd/D-Bus, and the
|
||
disposable systemd VM milestone has not been run. Real IPv6 capability and
|
||
traffic remain unverified.
|
||
|
||
## Saved state, Commit, and backups
|
||
|
||
wireguard_admin::app::Application opens saved files through
|
||
wireguard_admin::persistence::SavedStore, stages client and vault changes,
|
||
creates manual and mandatory operation backups, and previews/confirms a full
|
||
restore. Commit writes saved files only. Restore also changes saved files only;
|
||
neither operation reloads or restarts WireGuard. The server file is kept at its
|
||
trusted configured path, while internal state and the encrypted vault are kept
|
||
under Backup/data by default. Archives are stored under Backup/archives and
|
||
contain fixed logical members, not source client files or prior archives.
|
||
|
||
The persistence journal recovers interrupted file replacement on reopen and
|
||
returns a recovery error while earlier work remains unresolved. A confirmed
|
||
restart marks the saved generation applied only after a successful service job,
|
||
a matching complete runtime observation, and journaled verification-state write.
|
||
Restoring a backup does not restore runtime state: the running interface is
|
||
unknown until a later explicit restart and verification. Pending deleted-client
|
||
addresses remain reserved through Commit, reopen, and restore. See
|
||
[docs/BACKUP_FORMAT.md](docs/BACKUP_FORMAT.md) and
|
||
[docs/PERSISTENCE.md](docs/PERSISTENCE.md) for archive and recovery details,
|
||
and [docs/RUNTIME.md](docs/RUNTIME.md) for observation and restart boundaries.
|
||
|
||
## Vault and existing-client import library
|
||
|
||
`wireguard_admin::vault` provides WireGuard key handling, an encrypted versioned
|
||
vault bundle, caller-buffer client configuration parsing, and reconciliation
|
||
against a supplied server document. The source adapter can prepare an initial
|
||
encrypted bundle or explicitly import candidates into an existing prepared
|
||
bundle. These operations return in-memory data only: they do not install or save
|
||
a vault, edit source files or server configuration, Commit, or touch the VPN.
|
||
|
||
Directory scans use the resolved `client_source_directory` only. A candidate is
|
||
an immediate regular file whose name ends in `.conf` (case-insensitive); the
|
||
adapter does not recurse, follow symlinks, or read files larger than 1 MiB. A
|
||
scan is limited to 4,096 directory entries, 1,024 candidates, and 32 MiB total
|
||
input. Per-file failures are redacted and do not prevent other files from being
|
||
examined. Enumeration, read, or limit failures mark the scan incomplete. The
|
||
original files remain untouched. Adapter examples and behavior are covered with
|
||
synthetic temporary directories in `tests/vault_import.rs`.
|
||
|
||
## Client delivery and reissue
|
||
|
||
`Application::prepare_new_client` stages a dual-stack peer and encrypted key.
|
||
After a successful `commit`, `deliver_first_issue_file` can publish its first
|
||
configuration without a vault password during that same session; a failed file
|
||
publication can be retried to a newly selected destination. The first file is
|
||
mandatory. `deliver_first_issue_file_and_qr` optionally returns a QR matrix from
|
||
the same authorized bytes after file publication. Both success results carry an
|
||
activation warning unless this session has verified the exact installed saved
|
||
generation and its matching applied peer snapshot; historical verification and
|
||
unknown runtime state do not suppress it.
|
||
|
||
Later delivery uses `Application::reissue_client_file`, which takes the client
|
||
public key, a password for that action, a prepared destination, and an optional
|
||
QR choice. It freshly unlocks the installed saved vault each time, reads saved
|
||
server/settings bytes rather than draft state, preserves validated imported
|
||
parameters, and rejects blocked or stale records. It never reads original
|
||
plaintext client files as a fallback. The result reports a secret-free failure
|
||
stage and warns when the current saved peer set has not been verified as applied
|
||
in this live session. Historical verification timestamps do not suppress that
|
||
warning after reopen. File output uses mode `0600`, explicit overwrite
|
||
confirmation, and separate QR failure reporting. These APIs and the TUI
|
||
password prompt/terminal QR display have synthetic fixture coverage. No
|
||
production configuration or key is included in the repository.
|
||
|
||
## Test commands and integration limits
|
||
|
||
Run the default synthetic workflow and UI checks as an unprivileged user:
|
||
|
||
```sh
|
||
cargo test --locked --test tui_workflow
|
||
cargo test --locked tui::
|
||
```
|
||
|
||
The first exercises setup, client staging, Commit, delivery, backup listing,
|
||
and restore through the serialized worker with temporary files and a fake
|
||
statistics reader. The second covers navigation/forms/rendering, delayed worker
|
||
behavior, terminal lifecycle seams, and QR sizing. Neither command starts the
|
||
management UI or uses host service/runtime mutation. See
|
||
[`docs/TESTING.md`](docs/TESTING.md) for full gates and the isolated Alpine
|
||
(M8) and disposable systemd VM (M9) boundaries. An actual PTY smoke run using
|
||
synthetic application/runtime adapters is included in `tui_workflow`; it uses
|
||
`script` and a fake unavailable-statistics reader, without root or host settings.
|
||
It checks an 80×24 to 40×8 to 80×24 resize sequence and terminal cleanup after
|
||
`q`. The workflow also checks that Esc clears a returned QR disclosure; the
|
||
focused renderer test checks complete quiet-zone fit. Do not use the development
|
||
host's settings or VPN as a substitute.
|
||
M1–M8 qualification evidence and the remaining M9 limitations are summarized
|
||
in [`docs/RELEASE_READINESS.md`](docs/RELEASE_READINESS.md).
|
||
|