WireGuard server with an embedded admin console

Go backend that drives kernel WireGuard over netlink (wireguard-go as the
fallback), nftables NAT with MSS clamping, forwarding and buffer sysctls,
SQLite for peers, users, sessions, traffic history and the audit log.

React console: dashboard with live rates and usage history, peer management
with QR codes and .conf downloads, disconnect, session reset, key rotation,
expiry, client-supplied keys, settings, users with admin and viewer roles,
two-factor authentication with recovery codes, audit log.

Docker image on Alpine with compose files for bridged and host networking,
CI and GHCR publish workflows, performance notes.
This commit is contained in:
jcoffey
2026-09-12 19:56:08 -07:00
commit 6c006e1d4d
72 changed files with 11675 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
# WGX on the host network: the fastest way to run it.
#
# With `network_mode: host` the WireGuard socket sits directly on the host's
# interfaces. There is no port mapping, no conntrack entry per client packet
# and no second NAT hop, which is worth a few percent of throughput and a
# little latency on a busy server. The trade-offs: wg0 is created in the
# host's namespace (you will see it in `ip link` and it is removed on
# shutdown), the NAT rules land in the host's nftables as a table named
# `wgx`, and the admin UI listens on the host directly -- so it is bound to
# localhost below. Put a reverse proxy in front of it or set
# WGX_TLS_SELF_SIGNED to reach it from elsewhere.
services:
wgx:
image: ghcr.io/coffey-labs/wgx:latest
container_name: wgx
restart: unless-stopped
network_mode: host
cap_add:
- NET_ADMIN
environment:
WGX_ENDPOINT: vpn.example.com
WGX_PORT: "51820"
WGX_SUBNET: 10.8.0.0/24
WGX_DNS: 1.1.1.1, 1.0.0.1
WGX_HTTP_LISTEN: "127.0.0.1:51821"
# In host mode the forwarding sysctls are the host's own; WGX sets
# them itself since it has NET_ADMIN, but if you prefer to own them
# add `net.ipv4.ip_forward = 1` to /etc/sysctl.d/ and turn this off.
# WGX_MANAGE_SYSCTL: "false"
# Pick the interface to masquerade on if auto-detection picks the
# wrong one (it uses the default route).
# WGX_EGRESS_INTERFACE: eth0
volumes:
- wgx-data:/data
volumes:
wgx-data: