Commit Graph
11 Commits
Author SHA1 Message Date
Claude 0a9eaa8c8e awg-ctrl: commands for the secret path, connection log, metrics, hub, check
The new features were only reachable from the web panel. awg-ctrl now has:

- panel [path new|off|<path>] — the panel address; a new random, own or no
  secret path (cli.env, panel restarted, VPN untouched);
- log [<search>] [-n N] and log seen — the connection log of this server and
  who was online when, with sen:// device and master key names from ui.db when
  the panel is on the same machine;
- metrics [token|revoke] — the metrics-only token for an external Prometheus,
  printed once with a ready prometheus.yml fragment that carries the path;
- hub [legacy on|off|auto] — nodes with and without the pre-upgrade signature
  and the HUB_LEGACY mode;
- check — what this server gives away: shared pre-randomization VPN values or
  no own I1, no secret path, plain-HTTP panel and subscription, the generic
  "sen"/"awg-node" certificate names, an open hub — each with the fix.

The interactive menu gets items 10–13: panel address and path, connection log,
who was online, and the check. Columns are padded by runes, not bytes, so
Cyrillic names line up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bNGYiDst62wH3ZGTRPFLJ
2026-10-28 12:55:39 +00:00
Claude ea07914249 awg-ctrl: update, reinstall and uninstall from the menu
The awg-ctrl menu only started, stopped and changed the panel login; updating
or removing an installation meant finding and running install.sh by hand. The
menu gets "Обновить", "Переустановить" and "Удалить" (7–9), and the same as
`awg-ctrl update | reinstall | uninstall`.

The work stays in install.sh, which now takes ACTION=update|reinstall|uninstall
to skip its own menu. The CLI fetches it — the latest from INSTALLER_URL
(GitHub by default) for update and reinstall, the copy shipped in the release
archive for uninstall, so removal works offline — writes it to a temp file and
execs bash on it in the same terminal. It must not run from /opt/awg-control or
via `bash -c`: kill_related kills every process whose command line holds the
install path, and would kill the installer itself. The CLI passes its original
environment, not the one with cli.env loaded, or the installer would take
UI_PORT, SERVER_NAME and the rest as explicit overrides. The installer deletes
its temp copy on start.

The release archive now includes install.sh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bNGYiDst62wH3ZGTRPFLJ
2026-10-28 12:50:00 +00:00
Claude 1169a0adc7 Tidy up the installer
Layout: the file now reads top to bottom as output helpers, constants,
functions and the scenario. Functions were scattered between executable code
(some defined inside steps), the previous role was computed twice, and the
firewall code sat in the middle of a step. The firewall is now a function and
a step of its own; installing the awg-ctrl command moved into the services
step, so there are still six steps.

Fixes:
- the shebang was on line 4, after the license comment, so it did nothing;
- a reinstall removed /opt/awg-control before the configuration questions, so
  answering "no" to "Continue?" left the server with no installation; it is
  now removed only after the confirmation;
- with kept data the panel port was asked again with a new random default,
  which broke bookmarks and the API address even though the secret path was
  kept; the panel port, login, password and JWT secret now default to the
  previous values;
- UI_PORT, UI_USER, UI_PASS and SERVER_NAME from the environment were
  overwritten by the prompts; they now skip them, as the README says;
- when the user database existed but was not kept, the server name was not
  asked and silently became "VPN";
- random ports are checked to be free and distinct from each other;
- the server IP from ifconfig.me is validated, with a fallback to the source
  address of the default route;
- prompts are all in Russian; a panel with VPN can pick a random node port
  with "+";
- comments no longer point to docs/node-protocol.md, which is not in the repo;
- shellcheck -S warning is clean.

An update now gives a secret path to an install that has none and prints the
new panel address (UI_PREFIX=off, or an empty UI_PREFIX= in cli.env, keeps it
without one). The summary shows whether the panel has a secret path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bNGYiDst62wH3ZGTRPFLJ
2026-10-28 12:27:08 +00:00
Claude ff2d71600c Rewrite the README
Sections were added one feature at a time and ended up in arrival order,
with the stealth notes inside "Installation". The README now has a feature
summary and contents, an architecture section with the ports each server
opens, installation with roles and a table of installer variables, update /
reinstall / uninstall, a section on several servers and moving the panel
(it pointed to docs/node-protocol.md, which is not in the repo), one
"protection against detection" section covering randomized parameters, the
quiet ports and what they do not cover, the connection log, monitoring,
nginx, the API with the secret path in the base URL and the new connlog and
metrics routes, CLI commands and the main cli.env settings.

Also fixed on the way: the subscription is HTTPS by default, and a plain
update does not add a secret path to an older install.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bNGYiDst62wH3ZGTRPFLJ
2026-10-28 12:13:40 +00:00
Claude 1a54d05cca VPN connection log: who connected and when
WireGuard has no connect events, only the last handshake and byte counters.
awg-ctrl now checks them every 30 s and writes conn_log in users.db: connect
at the first handshake, disconnect at the last activity (the last tick the
counters moved), with the session length and traffic, plus peer_seen, the
last time each peer was online. Open sessions are restored from the database,
so a restart adds no duplicates; a down interface is not a disconnect; a peer
removed mid-session is closed. Events keep the name, owner and VPN IP as of
that moment and live 90 days. Client public IPs are not recorded.

GET /api/connlog serves the events and the per-peer last-seen list. The panel
fetches it from the selected server at /ui/connlog and fills in master key
labels and device names for sen:// peers. A new "Подключения" tab shows the
event feed with search and paging, and "who was online when", including
those who never connected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bNGYiDst62wH3ZGTRPFLJ
2026-10-28 12:06:40 +00:00
Claude fd5e2cc761 Hub: node signature before the WebSocket upgrade
The hub accepted a WebSocket from anyone on /node/v1, a path in the open
code, and replied with a hello nonce: one connection was enough to tell a
scanner what it had found. The agent now signs the upgrade request itself
(X-Node-Id, X-Node-Ts, X-Node-Sig; on first binding also the join secret
and its key), and the hub checks it before Accept. Without a valid
signature it answers Go's plain "404 page not found". Inside the socket
the protocol is unchanged, and the auth frame must come from the node that
signed the request.

Older agents send no signature. The hub lets them in while some node has
never connected the new way (nodes.preauth), logs them as outdated, and
closes that door by itself once every node is updated; HUB_LEGACY=on/off
overrides. The timestamp window is a day: node clocks drift, and the
headers travel inside pinned TLS. The agent treats a 404 at upgrade as a
rejection and backs off for a minute.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bNGYiDst62wH3ZGTRPFLJ
2026-10-28 11:58:06 +00:00
Claude a3d4664214 Stealth sen:// port, random cert names, external Prometheus card
The subscription port answered every request with signed JSON: one probe
told a scanner what it had found, and the public key from any sen:// link
pinned the exact server. It now speaks the protocol only to those who prove
a secret — peek and register with the link's master secret, everything else
with a valid device signature — and gives Go's plain "404 page not found"
to the rest. A deleted device still gets its 410, but only when the request
is signed with its key, kept in the new revoked_devices table. The migration
freeze answers only proven clients too.

Self-signed certs for the subscription (SUB_TLS=on) and the node hub got
fixed names (sen, awg-node); new ones get a random host-like name. Clients
and nodes pin the key, so existing certs keep working.

The panel could already issue a metrics-only token, but nothing in the UI
used it. The Monitor tab gets an "External Prometheus" card: issue, reissue
and revoke the token, with a ready prometheus.yml fragment whose address is
taken from the page, so it carries the secret path or the nginx subfolder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bNGYiDst62wH3ZGTRPFLJ
2026-10-28 11:52:55 +00:00
Claude 619af21085 Full uninstall from the installer
On a server with an existing installation the menu gets a third choice,
«Удалить»; UNINSTALL=y runs it without the menu. After typing «удалить» it
offers a backup of /etc/amnezia and cli.env to /root, then removes the
services and the systemd unit, brings awg1 down, drops the shaping and
torrent nft tables, closes the ports it had opened in UFW, firewalld or
iptables, removes the Prometheus that `awg-ctrl monitor install` set up
(only ours), and deletes /opt/awg-control, /etc/amnezia, the awg-ctrl
command, the sysctl file and the install logs, which hold the panel
password. AmneziaWG packages, module and PPA go only on a separate yes.

kill_related moved above the menu so the uninstall can use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bNGYiDst62wH3ZGTRPFLJ
2026-10-28 11:45:48 +00:00
Claude 13da9a7200 Secret URL path for the panel
The panel port is open to the internet, and its login page and JS bundle
gave it away to any scanner in one GET. The installer now generates a
random UI_PREFIX: from outside the panel answers only under
http://IP:PORT/<prefix>/ and gives Go's plain "404 page not found" to
everything else. Loopback needs no prefix (CLI, local Prometheus, nginx on
the same host); while a move code is out, POST /migrate/v1/import is let
through without it, since the code carries no path.

A reinstall with kept data keeps the prefix; UI_PREFIX=off or a custom value
can be set before running the installer. A moved panel brings its prefix in
UI_PREFIX_MOVED, so old bookmarks and API clients keep working through the
relay. awg-ctrl status and credentials show the full panel address.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bNGYiDst62wH3ZGTRPFLJ
2026-10-28 11:41:09 +00:00
Claude aa848de31c Hide the brand from unauthenticated requests
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
2026-10-28 11:30:40 +00:00
Claude 63e84e0df7 Random I1 and MTU on install
I1 becomes a DNS query to a random popular domain (A/AAAA) instead of the
shared icloud.com response every key carried. The format sticks to tags both
the kernel module and amneziawg-go accept and is checked before awg1.conf is
written: a bad I1 fails the whole awg-quick up. With KEEP_DATA=y the old I1
is kept, or generated if missing — the server never checks it, old keys keep
their own.

MTU is picked from 1320–1376 (AWG_MTU overrides) and kept on reinstall.
awg-ctrl now reads MTU from awg1.conf for vpn:// keys instead of a constant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bNGYiDst62wH3ZGTRPFLJ
2026-10-28 11:26:07 +00:00