On a server running pivpn's WireGuard, a new install offers to take it
over: the server key, port, MTU, tunnel networks, endpoint, DNS,
AllowedIPs and keepalive, and every client with its public key,
preshared key and addresses. Devices keep their configs. Clients pivpn
switched off are imported switched off, with the note "Imported from
pivpn". Client private keys in /etc/wireguard/configs are not read.
- Install notes which peers are connected, stops wg-quick@wg0, starts
GHOSTWIRE on the same wg0 and waits up to 30 s for those peers. The
wait only reports; idle devices reconnect when they next send.
- If the service does not stay running, install removes what it set up,
including config.json, and starts pivpn's WireGuard again.
- Without a terminal the takeover needs -import-pivpn; install refuses
to run next to pivpn otherwise, and the flag is refused on an
existing install.
- Names GHOSTWIRE does not accept are renamed and listed in the
summary. An IPv6 address that differs from the mapped one is kept on
the peer until its config is issued again.
- uninstall without a config of its own (e.g. after a takeover was
undone) leaves the WireGuard interface alone and removes only the
firewall table.
- README: "Coming from pivpn?" under the intro, a Features entry and a
"Moving from pivpn" section.
Tested end to end on Ubuntu 24.04 with pivpn aa96de7.
- 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.
Once a day the server asks Gitea or GitHub, as picked under Settings ->
Updates, for the latest release. A newer one shows as a pill in the
sidebar, a banner on the Dashboard and in the Updates card with its
release notes and the commands to update this server. Drafts and
pre-releases are ignored, nothing about the server is sent, and the check
can be switched off. POST /updates/check checks now.
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.
The server pings a peer's tunnel address every 30 s and shows the median
of the last 5 minutes in the peer list (with a 1-hour sparkline) and a
24-hour chart on the peer page. Off by default; "active" pings only
while the device sends traffic, "always" keeps the tunnel up.
A config can now be handed over as a one-time link, valid for 1 h, 24 h or
7 days and protected by a PIN by default. Keys are made only when the link
is opened; the link works once and is revoked after 5 wrong PINs. Issuing a
new config offers the same choice, and the current config keeps working
until the link is used.
Remove the option to paste a client's public key, in the web UI, the API
and the iOS app.
- The stats sampler records sessions per peer: start, end, address and
traffic. A session ends when the peer goes quiet or is disabled; a new
one starts when the device changes networks. Stored in stats.json and
kept as long as the daily traffic history (max 1000 per peer).
- Country and network operator come from the free DB-IP Lite databases
(CC BY 4.0), downloaded monthly and looked up locally, so peer
addresses never leave the server. Settings → Data retention can switch
this off, which deletes the databases.
- API: GET /peers/{id}/sessions; peer stats include the current location;
settings include the database status.
- Web UI and iOS app: connection history card, location line, country
code in the peer list (web), switch in data retention.
- Settings → Data retention: log file size, number of old log files,
hourly and daily traffic history. Stored as log and stats in
config.json, validated, and applied without a restart; lowering a
limit deletes older log files and history after confirmation.
- Traffic history is now pruned by time instead of by bucket count.
- Server page DNS provider list offers only Quad9 and Custom.
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.