Status, priority/torrent chips and IP columns like the users tab; the API
returns each device's IP per server. The key row sums its devices (online,
priority, traffic); key UUID and device ID sit under the name, copy on click.
- awg-ctrl: optional nft table awg_torrent (GET/PUT /api/torrent, off by
default). A connection is judged by its first packets, like Xray's
sniffing: DHT, UDP trackers, uTP SYN, and with @ih support the plain
TCP handshake and HTTP announce. A match marks the connection in
conntrack and drops it whole; without @ih only the UDP part is used
- devices that tried are kept in a 10-minute set and flagged as
"torrent" in user and device stats; with shaping on they sit in the
background class while the flag lasts
- metrics: awg_torrent_block_enabled/active, awg_torrent_blocked_total,
awg_torrent_devices and per-peer awg_peer_torrent
- panel: "Торренты" section in server settings, "торренты" chip on users
and devices; Save sends only what changed
- ranges 7d (30 min step) and 30d (2 h step); Prometheus keeps 31d
- rate() window is at least the step, so long views show averages
instead of a 2-minute sample; "now" values still use a 2m window
- overview returns used_down/used_up: increase() of the interface
counters over the whole range
- tiles merged: traffic (with volume for the period), devices, load
Baseline TypeScript version before the Go rewrite: CAKE fair sharing with
automatic deprioritisation of heavy devices, per-key priority, /metrics with
a metrics token, the built-in Prometheus install/remove, the Monitor tab,
grouped sidebar tabs and the dead-peer audit.
Lowering device_limit below the number of bound devices now forgets the
extra ones right away, newest first, so the devices that took their
slots first keep them. Their peers are removed and they get 410 like a
revoke from the panel. PATCH returns the unbound ids; the panel names
the devices that will be dropped and asks before saving.
- master_keys.uuid (UUID v4, unique index): new keys get one on create,
existing keys once on start. It survives rotate (new secret) and
relabel, so it identifies the key itself rather than its link.
- /ui/masterkeys* and /api/v1/masterkeys* return uuid, and :id in those
paths accepts the numeric id or the uuid.
- /sub/v1 register, peek and devices return key_uuid, so a client can
recognise the same key behind a reissued link. It is not in the sen://
link (that would need a new format) and not in config, so rev and
existing clients are unaffected - checked against a 0.3.1 ui.db.
- Panel: the uuid sits in the key's row like a device ID (full value in
the tooltip, click copies) and in the key settings with a copy button;
on mobile it is shown under the key's name.
- README: uuid in the master key API.
- awg-agent sends its install's VERSION (from the CLI's env, else the
project's .env) in the auth frame; the hub keeps it in nodes.version
(added by migration), so an offline node still shows the last one.
- GET /ui/nodes returns version per server (0 is the panel's own) and the
panel's version at the top.
- ServerBar: a vX.Y.Z chip next to the AWG generation. A node older or
newer than the panel, or one that sends no version (agent before
0.3.4, shown as v?.?.?), gets a warm chip with an "update it" tooltip.
- App.css: tertiary-container tokens from design.md (light) and a warm
dark pair; they did not exist yet.
- Mock server: panel 0.3.3, Frankfurt behind on 0.3.2, Amsterdam with no
version.
API keys now see a set of servers instead of one, and /api/v1 gains the
master-key operations the panel already had.
- api_key_servers holds each key's servers (migrated from server_id,
which stays the default server for /api/v1/users). /users follows
X-Server-Id within the set; a server outside it is 404. PATCH
/ui/apikeys/:id changes the set without reissuing the key.
- /api/v1/masterkeys: create (returns the sen:// link), list, get with
link and devices, patch label/limit/servers, rotate, rekey, delete, and
nested device routes (unlink, rekey, PSK). A key sees a master key only
if all of its servers are in the key's set, so it cannot delete or
re-point service on a server it cannot see; anything else is 404,
servers outside the set are 403. Creating refuses with 503 before
writing anything if the subscription cannot issue links.
- sub.ts: the panel routes and the API share one service layer, the
panel's behaviour is unchanged.
- Node deletion: owners check in beforeDelete (no writes, a later hook
may still veto) and clean up in afterDelete. Previously a forced delete
could release a waiting master key and then be vetoed. A node that is
the only server of an API key is refused; other keys just lose it.
- Panel: server checkboxes are a shared ServerPicker, used by the master
key settings and the new API key settings row; the API keys table shows
each key's servers.
- README: the API section covers server sets and master keys.
A deleted master key could keep letting its devices in: ui.db learns what
is on a server only from awg-ctrl's reply, and a node that executed a
request whose 504/502 reply got lost, or a device deleted while a
reconcile pass was in flight, left a peer nothing remembered to remove.
- awg-ctrl: PUT /api/peers/sync takes a panel's full list of wanted keys
and removes every other peer of that panel's area. Removal is driven
only by such an explicit list, never by time or inactivity: a panel
that is offline sends nothing, so nothing is deleted. Registered before
PUT /api/peers/:pub, with its own 512 kB body limit.
- Every panel has a stable id (ui.db, mirrored to awg-ui/panel.id) and
adds peers as "<panel>/m<key>/d<device>", so two panels on one awg-ctrl
never touch each other's peers. Legacy "m1/d2" peers are claimed only
when their key is in the panel's list.
- Each reconcile pass ends with the sync, built from ui.db at that moment.
Its reply also self-heals: a device whose key is missing on a server
(a rekey rolled back after a lost reply) is added again.
- Deleting a master key stops it at once but keeps it listed as
"Удаляется — ждёт: …" until every server confirms, instead of saying
"deleted" while an offline node still lets the devices in.
- Deleting a node reconciles it first; if it is offline or has not
confirmed a deleted key, it takes ?force=1 and a warning. Tombstones
are no longer dropped silently.
- /sub/v1: an unknown device gets a signed 410 revoked that does not count
towards the failure limit (dead clients behind one node NAT used to
lock everyone out), every response echoes the request's ts and device,
and 401 reasons and the first 429 per window are logged. config and rev
are unchanged, byte-identical on one server.
- CLI: `awg-ctrl peers [drop <area|legacy>]` lists and removes
subscription peers by area; `join --no-panel` asks whether to keep or
drop the panel's own peers (NODE_PEERS=keep|drop).
A master key now has a set of servers (the panel's own and/or nodes). A
device's single WireGuard key becomes a peer on every server of its key,
each with its own IP and PSK, and config.servers[] lists them all.
- ui.db: master_servers (the key's set, never empty), device_peers (which
key is on which server, with the ip/psk that server issued),
peer_tombstones (peers to delete) and server_profiles (last known
profile of each server).
- Nothing is pushed blindly: reconcile() brings one server to the desired
state - deletes tombstones, adds peers, rekeys with PUT, fills ip/psk,
rotates a pending PSK - one pass per server at a time. It runs right
after every action, when a node connects and once a minute, so a node
that was offline catches up on rekeys, PSK changes and removals.
- config is built from ui.db, so an offline node stays in it and rev does
not flap; a server is listed once its peer carries the device's current
key. An empty config is never sent (502 instead); register and rekey
roll back if no server accepted the key.
- Existing keys and devices migrate on start. With one server the config
and its rev are byte-identical to 0.3.1 (checked by running both
versions on the same ui.db), so existing clients notice nothing.
- Panel: server checkboxes in the key settings instead of the placeholder
(the last one cannot be removed), "на N из M серверов" on devices that
have not reached every server yet, device stats summed over servers.
- SUB_HOST: the panel's public address for sen:// links and endpoints;
install.sh writes it. Without it a panel with no VPN of its own used
the address it was opened at.
- A node referenced by a key's server set cannot be deleted.
A node is a VPN server without a panel. awg-agent on it dials the panel
(core) over an outbound WSS connection and runs the panel's requests on its
own awg-ctrl, which stays on 127.0.0.1 and is unchanged. No inbound port on
the node, works behind NAT.
- awg-ui/nodes.ts: node registry in ui.db, WSS hub on NODE_PORT with a
self-signed cert whose SPKI pin goes into the join string, RPC with
timeouts, health polling, /ui/nodes routes.
- Nodes enroll with a one-time awgjoin:// string (secret stored hashed,
1 hour) and then authenticate by their own Ed25519 key. The agent checks
the pin before sending anything and only forwards /health, /api/*, /awg/*.
- server_id 0 is the panel's own awg-ctrl; LOCAL_NODE=off makes the panel
run without a VPN of its own. The panel picks a server with X-Server-Id,
API keys use their server_id. sen:// still serves one server.
- UI: a card per server in the server bar, switching remounts the tab,
«Добавить сервер» gives the join command.
- CLI: agent service, roles from cli.env, join / unjoin commands.
- Release archives include awg-agent.
A master key is a sen:// link with a device limit. Each device
generates its own WireGuard keypair and registers only the public key,
then polls a signed /sub/v1/config, so params and generation changes
reach users without re-importing a key.
- awg-ctrl: peers table and /api/peers*, /api/profile; nextIp and
rebuildConf take peers into account (output unchanged without them)
- awg-ui: link codec and signatures (sen.ts), registry, panel routes
and the public listener on SUB_PORT (sub.ts)
- responses are always Ed25519-signed; TLS with a pinned self-signed
certificate is optional
- devices report a client version, shown as platform-version
- "Мастер-ключи" tab: a table with the devices nested under each key,
a device-limit meter, per-key settings and a placeholder for the
servers a key serves; cards on mobile
- install.sh/CLI: subscription signing key, optional certificate, port;
kept across reinstalls with KEEP_DATA
- add the missing mock-server.ts used by npm run dev:mock