GET /ui/brand answered without auth with {"brand":"Forgetting",…}: one
request was enough for a scanner to tell a found port is this panel. It now
requires the panel JWT (Go and the Node reference alike). The login screen
says just «Вход», the page title is neutral until login, and the wordmark
loads after sign-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bNGYiDst62wH3ZGTRPFLJ
Releases now carry one archive per architecture with a static awg-control
binary (services and CLI), the built frontend and .env; the installer no
longer installs Node.js or runs npm.
- tools/release/package.sh: CGO-free builds for linux/amd64 and arm64,
version injected into `awg-control version`; used by both the GitHub and
Gitea release workflows
- install.sh: architecture check, per-arch archive, /usr/local/bin/awg-ctrl
is a symlink to the binary, systemd unit without node in PATH, steps
renumbered 1/6..6/6
- update from a Node install: the old services are stopped through the old
CLI, then TS sources, node_modules and the old dist are removed; cli.env,
panel.id and logs stay
- README: one binary, its roles, architecture requirement
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.
The differential bench (GEN 2 and 3.1, with I1, S4=0, upgrade with a 3.x
module) now shows the Go awg-ctrl identical to the TS one. The only real
difference it found: host.cpu was a Go map, so its keys came out sorted
instead of in /proc/stat order.
- tools/diffbench/compare.mjs: decodes vpn:// (Node and Go zlib differ), masks
live host numbers, Express dev-mode stack traces, temp file names and the
order of calls from concurrent background work
- tools/diffbench/local.sh: the same run without Docker, stubs only on the
run's own PATH (for disposable machines)
- run.sh: wait for the background shaping apply before stopping the service,
otherwise the stub log is cut short
- internal/jsjson: JSON.stringify-compatible encoder and order-preserving decoder,
tested against fixtures produced by Node itself
- internal/ctrl: awg-ctrl in Go (params, vpn:// keys, users, peers, sync, 3.1
upgrade, CAKE shaping, metrics, monitor job, Express-compatible HTTP layer)
- internal/iauth, internal/logx: internal Ed25519 tokens and logging
- tools/diffbench: Docker bench with stubbed awg/ip/tc/nft that runs one scenario
against the TS and Go implementations for comparison