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
+77
View File
@@ -0,0 +1,77 @@
import { useState, type FormEvent } from "react";
import { api } from "../api";
import { errorMessage, useAuth } from "../state";
import { Field } from "../components/ui";
export function Login() {
const { refresh } = useAuth();
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [code, setCode] = useState("");
const [stage, setStage] = useState<"password" | "totp">("password");
const [error, setError] = useState("");
const [busy, setBusy] = useState(false);
async function submit(e: FormEvent) {
e.preventDefault();
setError("");
setBusy(true);
try {
if (stage === "password") {
const r = await api.login(username, password);
if (r.totpRequired) {
setStage("totp");
return;
}
} else {
await api.loginTotp(code);
}
await refresh();
} catch (err) {
setError(errorMessage(err));
} finally {
setBusy(false);
}
}
return (
<div className="auth">
<form className="card" onSubmit={submit}>
<div className="card-body">
<div className="brand">
<div className="brand-mark" aria-hidden="true">
<svg width="18" height="18" viewBox="0 0 32 32">
<path d="M7 10l4 12 5-9 5 9 4-12" fill="none" stroke="currentColor" strokeWidth="3.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="brand-name">WGX</div>
</div>
<h1>{stage === "password" ? "Sign in" : "Second factor"}</h1>
{error && <div className="error">{error}</div>}
{stage === "password" ? (
<>
<Field label="Username">
<input className="input" autoFocus autoComplete="username" value={username} onChange={(e) => setUsername(e.target.value)} required />
</Field>
<Field label="Password">
<input className="input" type="password" autoComplete="current-password" value={password} onChange={(e) => setPassword(e.target.value)} required />
</Field>
</>
) : (
<Field label="Authenticator code" hint="Or one of your recovery codes.">
<input className="input" autoFocus autoComplete="one-time-code" inputMode="numeric" value={code} onChange={(e) => setCode(e.target.value)} required />
</Field>
)}
<button className="btn primary" type="submit" disabled={busy} style={{ width: "100%", justifyContent: "center" }}>
{busy ? "…" : stage === "password" ? "Sign in" : "Verify"}
</button>
<p className="small faint" style={{ textAlign: "center", margin: "14px 0 0" }}>
<a href="https://github.com/Coffey-Labs/WGX" target="_blank" rel="noreferrer">
AGPL-3.0 source
</a>
</p>
</div>
</form>
</div>
);
}