Port awg-ui to Go: awg-control ui

internal/ui reimplements the awg-ui backend with the same HTTP surface; the
React frontend is unchanged and served from awg-ui/dist.

- web.go: Express-like router (express.json semantics, mounts, :params,
  HTML 400/404/413 pages, SPA fallback)
- server.go: JWT login with rate limit, API keys and their server sets,
  awg-ctrl client and proxy, /api/v1/users, delete hooks
- nodes.go: WSS hub for agents (pin, challenge-response, join strings),
  RPC with timeouts, health, /ui/nodes
- sub.go, audit.go, sub_admin.go: sen:// master keys and devices,
  per-server reconcile with tombstones and /api/peers/sync, signed /sub/v1
  listener, audit with two-pass confirmation and cleaning, panel and
  /api/v1/masterkeys routers
- metrics.go, monitor.go: /metrics exposition with tokens, /ui/monitor
- sen.go: link codec and protocol signatures, tested byte for byte against
  fixtures produced by awg-ui/sen.ts (tools/golden/sen.ts)

Differential bench (tools/diffbench/local-ui.sh, scenario-ui.ts,
compare-ui.mjs): 158 steps over every route group, including signed device
requests and a Go agent joining as a node, plus the resulting ui.db and
awg-ctrl state. TS and Go are identical after masking random values, across
repeated runs and with the race detector on.
This commit is contained in:
2026-10-14 13:27:24 +00:00
parent c2e9636f31
commit 70bb587216
18 changed files with 7017 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
// Состояние после сценария awg-ui: строки ui.db и пиры/пользователи awg-ctrl.
import fs from "fs";
import { execSync } from "child_process";
const out = process.argv[2];
const t = JSON.parse(fs.readFileSync(out, "utf8"));
const q = (db, sql) => JSON.parse(execSync(`sqlite3 -json ${db} "${sql}"`).toString() || "[]");
const UI = "/run/bench/ui.db", CT = "/etc/amnezia/amneziawg/users.db";
const tables = ["api_keys", "api_key_servers", "nodes", "master_keys", "master_servers", "devices", "device_peers",
"peer_tombstones", "server_profiles", "sub_meta", "metrics_token"];
t.state = {
ui: Object.fromEntries(tables.map(x => [x, q(UI, `select * from ${x} order by rowid`)])),
uiSchema: execSync(`sqlite3 ${UI} .schema`).toString(),
ctrlUsers: q(CT, "select name, ip, key_gen from users order by rowid"),
ctrlPeers: q(CT, "select pub_key, ip, owner from peers order by rowid"),
};
fs.writeFileSync(out, JSON.stringify(t, null, 1));
console.log("collect-ui: done");