- The server endpoint must be a plain host name or IP address. It is
written into client configs as is, so a newline could add lines such
as PreUp, which wg-quick runs as root on the client.
- Listen addresses and the session length (1–720 hours) are checked.
Before web settings or a restore are saved, the server tries the new
listen addresses and certificate files, so a value it cannot start
with is refused instead of stopping the service at the next restart.
- Kernel applies run one at a time and read the config once it is
their turn, so an older config can no longer be applied last.
- Pending passkey sign-ins are capped: 10 per address, 1000 in total.
- Behind a local proxy, the last X-Forwarded-For entry is the client;
earlier ones come from the client and are ignored.
- With LAN access off, peers are also kept from the IPv6 networks on
the uplink, not only from its private IPv4 networks.
- A change that leaves no user with a password is refused, and so is a
backup without one or from a newer version.
Every argon2 run takes 64 MiB and nothing limited how many ran at once,
so parallel sign-in attempts could run the server out of memory (8 at
once used about 600 MB). At most two now run at once; at most 16
sign-ins wait for one, more get HTTP 429. 30 parallel sign-ins peaked
at 275 MB.
A sign-in attempt now counts toward the lockout before its password is
checked, so parallel attempts cannot get past it; a right password
takes its own attempt back. IPv6 addresses are locked out by /64.
The signed-in user's profile, password and own app tokens move from the
Settings card to their own page at #/account, ready for more user
functions. The sidebar footer links to it with the user's name. Users
can now set their own note; the page header shows when and from where
the session started, which the server now records. Settings keeps the
Users table, where your own row links to My account.
The single admin account becomes a list of users; config.json moves to
version 2 and the old admin is migrated on first start. Every user is an
admin. Sessions are tied to a user and their password, so deleting a user
or resetting a password signs them out at once. API tokens belong to the
user who made them and go away with that user.
Admins add users with a temporary password and choose whether it must be
changed at first sign-in; until then the API refuses everything but the
password change. Settings gets My account and Users cards, and the token
table shows each token's owner. 'GHOSTWIRE passwd [username]' resets any
user's password. A failed update now also restores config.json, since the
new version may have upgraded it.
Single Go binary that manages a WireGuard server based on pivpn's defaults:
- config.json as the single source of truth, reconciled to the kernel via
netlink, wgctrl and its own nftables table (NAT, forward, input)
- web interface (dashboard, peers, peer detail, add peer, server, settings)
and a JSON API for the future iOS app, with session and API-token auth
- client private keys are never stored; configs and QR codes shown once
- per-peer traffic statistics in stats.json, logs in GHOSTWIRE.jsonl
- HTTPS via Let's Encrypt, self-signed, certificate files or off
- self-managing: install, update (restores the old binary on failure),
uninstall and passwd subcommands; systemd unit generated by the binary
Tested end to end on Ubuntu 26.04 (kernel 7.0) at dev.redetzke.aero.