383 Commits

Author SHA1 Message Date
Public relations & Virtual technology 02c1182284 Update version 1.6.7 v1.6.7 2026-09-14 14:10:34 +03:00
Максимов Владимир Сергеевич e7d7a769fc test: guard cross-PR integration and all-locale rendered JavaScript 2026-09-14 12:45:53 +03:00
Максимов Владимир Сергеевич 3d30d4e3fb Merge PR #176: repair missing AmneziaWG tools 2026-09-14 12:40:34 +03:00
Максимов Владимир Сергеевич 4a3f2f50f8 Merge PR #175: kernel upgrades 2026-09-14 12:40:27 +03:00
Максимов Владимир Сергеевич cb0a5db3b5 Merge PR #174: nonblocking SSH and circuit breaker
# Conflicts:
#	translations/en.json
#	translations/fa.json
#	translations/fr.json
#	translations/ru.json
#	translations/zh.json
2026-09-14 12:40:26 +03:00
Максимов Владимир Сергеевич 0a32145b82 Merge PR #173: WireGuard client configuration 2026-09-14 12:40:19 +03:00
Максимов Владимир Сергеевич d526b02894 Merge PR #172: rename polling race and bulb click fix
# Conflicts:
#	templates/server.html
2026-09-14 12:40:19 +03:00
Максимов Владимир Сергеевич 64d8ad76ae Merge PR #170: peer editor user linking
# Conflicts:
#	translations/en.json
#	translations/fa.json
#	translations/fr.json
#	translations/ru.json
#	translations/zh.json
2026-09-14 12:39:22 +03:00
Максимов Владимир Сергеевич 7268c3fe61 Merge PR #169: rich user connection cards 2026-09-14 12:39:11 +03:00
Максимов Владимир Сергеевич 1a36478e28 Merge PR #168: WireGuard peer discovery and translations
# Conflicts:
#	translations/en.json
#	translations/fa.json
#	translations/fr.json
#	translations/ru.json
#	translations/zh.json
2026-09-14 12:39:11 +03:00
Максимов Владимир Сергеевич 826f6d4692 Merge PR #167: multi-select connection linking
# Conflicts:
#	translations/en.json
#	translations/fa.json
#	translations/fr.json
#	translations/ru.json
#	translations/zh.json
2026-09-14 12:39:01 +03:00
Максимов Владимир Сергеевич 80adfece00 Merge PR #166: unlink connections and peer deep links
# Conflicts:
#	translations/en.json
#	translations/fa.json
#	translations/fr.json
#	translations/ru.json
#	translations/zh.json
2026-09-14 12:38:53 +03:00
Public relations & Virtual technology 8f1e8a31a0 Merge pull request #165 from leonidorlov-hash/feat/dns6
feat(awg): configurable IPv6 DNS (DNS6) for dual-stack instances
2026-09-14 12:08:39 +03:00
Public relations & Virtual technology a0e15af3ac Merge pull request #163 from leonidorlov-hash/fix/sudo-password-stdin
fix(ssh): feed the sudo password via channel stdin, never the command line
2026-09-14 12:07:00 +03:00
Public relations & Virtual technology a1cb099531 Merge pull request #161 from helldweller/test/exit-aware-tests
test: restore the exit-node coverage stripped from two suites
2026-09-14 12:05:45 +03:00
leonidorlov-hash 36a5c15dff feat(awg): self-heal tools/kernel mismatch + sibling staleness warning
start.sh now detects an awg-tools vs kernel-module version mismatch
(after the tunnel fails to come up, e.g. module upgraded to 3.1 while
the image still ships old tools) and retries in userspace mode -
clients keep a working, if slower, tunnel instead of no internet.

During install the panel also warns about sibling AWG containers whose
image tools differ from the host kernel module, advising a reinstall to
rebuild the image and keep the fast kernel datapath.
2026-09-14 02:09:49 +03:00
leonidorlov-hash 7fde3295ba fix(awg): upgrade an outdated kernel module instead of skipping
setup_kernel_module used to report 'already present' for ANY installed
amneziawg version, so servers with the old 1.0.x module never received
AWG_MODULE_VERSION (3.1.20260812) when an AWG 3.1 instance was
installed. Now a version mismatch triggers an upgrade: unload the
module (deferred with a clear message while tunnels still use it),
dkms-remove the old version, then build and install the target one.
2026-09-14 00:49:59 +03:00
leonidorlov-hash 1248ce68c0 feat(servers): configurable SSH circuit-breaker base cooldown
Per-server setting ssh_cooldown_base (default 30s, range 5-300) for the
SSH circuit breaker introduced with the event-loop fix. Editable in the
server management modal with a 'leave it alone when healthy' hint, and
shown in the host tuning modal. get_ssh applies edits to the pooled
connection without dropping it.
2026-09-13 23:48:10 +03:00
leonidorlov-hash 53f908733c fix(ssh): lower transport keepalive 30s -> 15s
Long-lived pooled transports were dying between polls on servers behind
stricter NAT/firewall state timeouts, forcing a full SSH handshake on
nearly every poll cycle.
2026-09-13 23:48:10 +03:00
leonidorlov-hash 98be329b30 fix(api): keep blocking SSH work out of the event loop
One hung server (15s connect x retries) or the auto-backup sweep used to
freeze the whole panel for minutes, local endpoints included.

- 66 endpoints that only do blocking work converted from async def to
  def so Starlette runs them in the threadpool
- mixed endpoints (exit-link awaits, file uploads, DATA_LOCK sections)
  stay async; their blocking SSH calls now go through asyncio.to_thread
- get_ssh() (which blocks in ensure_connected) is no longer called from
  async code in app.py, ExitLinkService or ConnectionService
- background tasks were already threaded/to_thread; DATA_LOCK and
  TUNNEL_LOCK semantics untouched
2026-09-13 23:45:42 +03:00
leonidorlov-hash e7740ffbb0 fix(ssh): exponential-backoff circuit breaker per server
Consecutive connect failures now grow the cooldown 30s -> 60s -> 120s,
capped at 300s, and the first successful connect resets the streak.
connect() itself records failures, so the reconnect-and-retry path in
_run_command_locked feeds the breaker too; fail-fast 'backing off Ns'
behavior is unchanged.
2026-09-13 23:44:40 +03:00
leonidorlov-hash 9dfcf48ee1 fix(ui): silent connections refresh after peer save/link/unlink
Saving the peer editor (rename / speed / user link or unlink) triggered
a full blocking 'Loading connections...' overlay because it called
loadConnections() in foreground mode. The refresh now goes through the
poll path (silent) with a force flag that bypasses the
rename-editor-open guard only for this save flow.
2026-09-13 23:43:44 +03:00
leonidorlov-hash 37031761a1 fix(wireguard): get_client_config accepts port; route link flow via _manager_call
Linking an existing WireGuard peer to a user crashed with
'WireGuardManager.get_client_config() takes 3 positional arguments but
5 were given': the user-link endpoint called the manager directly,
bypassing the _manager_call shim, and passed the AWG-style argument
list (protocol, client_id, host, port).

- WireGuardManager.get_client_config now takes an optional port; when
  omitted it falls back to the listen port from the server config, so
  existing direct callers keep working.
- The link endpoint goes through _manager_call like every other call
  site, so each manager gets its own signature.

Adds tests/test_wireguard_get_client_config.py (3 cases).
2026-09-13 22:39:28 +03:00
leonidorlov-hash 098ea729ef fix(ui): poll no longer wipes a fresh rename editor; bulb toggle updates only its row; toast after re-render
- A poll already in flight when the rename editor opened returned and
  re-rendered the list, destroying the editor on first open (later
  polls are skipped, so a second open worked). Re-check for an open
  editor right before rendering poll results.
- toggleConnection always re-rendered the whole list after a toggle,
  except when a poll was in flight (the connLoading gate silently
  skipped it) — inconsistent UI. The row is now updated in place
  (opacity, bulb state, tooltip, stop badge, onclick); the 5s poll
  reconciles speeds and badges.
- Rename/save toast appeared before loadConnections() closed the
  editor; the list now re-renders first, toast after.
2026-09-13 21:22:52 +03:00
leonidorlov-hash 6d4800aaac fix(ui): action tooltips on the bulb; editor never force-links a user
- Bulb tooltip used the state key ('Stopped'); added
  stop_peer_title/start_peer_title action keys in all 5 locales.
- The user select defaulted to the first no-role user, so saving a
  rename silently linked the peer. '-- do not link --' now comes first
  and is selected unless the peer is already linked.
2026-09-13 21:19:53 +03:00
leonidorlov-hash 900a4de5db style(ui): wider connections modal, tighter server-list buttons, slim speed badge
- #userConnsModal .modal max-width 960px (was inline 600px)
- .page--servers .btn-sm padding 6px 12px
- dropped fixed min-width on .speed-badge so it hugs its content
2026-09-13 20:15:49 +03:00
leonidorlov-hash 182abe65bf feat(server): link/unlink a user right from the peer rename editor 2026-09-13 20:15:48 +03:00
leonidorlov-hash 3c4e1d9c37 Fix peer name source (userData.clientName); move highlight border to CSS class 2026-09-13 20:15:13 +03:00
leonidorlov-hash 50ebcec7a3 Fix enabled flag source for user connections (userData.enabled) 2026-09-13 20:15:13 +03:00
leonidorlov-hash fbff1d093a Users page: bulb toggle for users, live peer names, deep links to peers
- User enable/disable button is now a bulb (off = greyed, CSS class),
  with an in-flight spinner and the whole user card locked/dimmed.
- The connections modal shows the peer's live name from the instance
  (matched by client id) instead of the panel-side record name.
- Server name in the modal is a target=_blank deep link to
  /server/N?protocol=..&highlight=<client_id>: the server page switches
  to the right instance, frames the peer card (2px #7038da) and scrolls
  it to the viewport center; the frame survives the 5s live poll.
2026-09-13 20:15:13 +03:00
leonidorlov-hash 026714dc0e Rich connection cards in the /users connections modal
/api/users/{id}/connections now enriches each record with live peer
data (IP, enabled state, handshake/transfer totals, speed limit) via
one SSH session per (server, protocol); unreachable servers degrade
gracefully to DB-only fields.

The modal renders the same client-item layout as the server page:
avatar letter, name + IP, server/protocol/date/badges, and working
rename / config / toggle (spinner + locked row) / delete actions.
2026-09-13 20:15:13 +03:00
leonidorlov-hash 6ccff31436 feat(users): multi-select linking, remembered options, no full page reload
- Link-existing flow now supports selecting several peers at once
  (Ctrl); each link is applied independently with a summary toast,
  partial failures do not abort the rest.
- Add-connection modal remembers the last used mode/server/protocol
  (localStorage) and restores them on next open.
- Linking or creating a connection refreshes the users grid via AJAX
  and live-updates an open connections modal instead of reloading the
  whole page.
- Server-side guard against double-linking a peer: a peer already
  assigned to any panel user is rejected with a message naming the
  current owner (a peer is a single on/off entity on the server;
  sharing it between users made both cards control the same peer).
- Linking an existing peer no longer pops up the config modal.
- Translations for all 5 locales.
2026-09-13 20:05:38 +03:00
leonidorlov-hash 110b3e4882 feat(users): unlink-without-delete action + deep links to peers
- POST /api/users/{id}/connections/unlink: detaches a key from the user
  without deleting the peer on the server (admin-only, 404 if not found)
- users page: scissors button per linked connection, server/instance
  names open the server page in a new tab and highlight the peer row
- server page: ?protocol=&highlight= deep link handling, peer row
  gets .peer-highlight CSS class and scrolls into view
- translations for all 5 locales
2026-09-13 19:54:56 +03:00
leonidorlov-hash 155a0673dc fix(users): WireGuard existing-clients list + missing i18n keys
- api_get_server_clients called manager.get_clients(protocol) directly,
  but WireGuardManager.get_clients() takes no protocol argument, so the
  link-existing dropdown failed with a TypeError. Route through
  _manager_call like every other callsite.
- Add translation keys referenced by templates but absent from all
  language files (select_connection, name_required,
  no_servers_available) — the UI fell back to raw key names.
2026-09-13 15:59:32 +03:00
leonidorlov-hash b5284b8feb feat(awg): editable IPv6 DNS field in install modal and AWG settings 2026-09-13 14:44:27 +03:00
leonidorlov-hash dbacc3e4a8 Avoid duplicating the IPv6 resolver on config regeneration
A manually saved client config indexes its whole DNS line into
userData.dns — including the v6 resolver on dual-stack peers. The next
regeneration then appended it a second time. Now the v6 address is
added only if not already present in the DNS list.
2026-09-13 14:27:57 +03:00
leonidorlov-hash 27c6781dad Make the IPv6 DNS configurable instead of hardcoded
Dual-stack client configs used to always append Cloudflare's
2606:4700:4700::1111. Now the resolver is stored in the server config
as a commented '# DNS6 = ...' key at install (same pattern as MTU/DNS,
which awg-quick would otherwise choke on) and read back via
_get_dns6(): per-client override (userData.dns6) > config key >
built-in default. Existing instances without the key fall back to the
same Cloudflare default, so behavior is unchanged until edited.
2026-09-13 14:27:57 +03:00
leonidorlov-hash 42146681d1 fix(ssh): feed the sudo password via channel stdin, never the command line 2026-09-11 01:01:11 +03:00
hell 539ef92197 test: cover the exit-node paths these two suites already touch
Both files were submitted in #143 and #147, before the exit node existed here,
so their exit-specific halves had to be stripped to keep them importable. That
code is in the tree since #149, and the stripped copies now test less than they
could:

- test_ssh_error_text.py gains ExitLinkMessageTests: an SSH whose sudo call
  returns -1 with no output at all (a dropped transport) must make exit_link
  and exit_unlink raise a message that names the exit code, not
  "exit link apply failed: " with nothing after the colon. That is the failure
  the empty str(EOFError) produced, and it is the reason #147 exists.
- test_sudo_command_chains.py pins amnezia-exit, not amnezia-awg2, as the
  representative container that DNSManager attaches to amnezia-dns-net. Both
  are in vpn_containers, but only the exit one silently breaks DNS on an exit
  node when it falls out of the list.

No production code changes.
2026-09-11 01:39:13 +07:00
Public relations & Virtual technology dbccc1ec40 Merge pull request #157 from helldweller/fix/red-main-upstream
Fix v1.6.5: server page dies on a duplicated script block, plus six defects from the same merges
v1.6.6
2026-09-09 21:31:57 +03:00
hell 828425ebc9 fix(users): localise the invalid-role error too
Left over from the same handler: 'Invalid role' answered in English next to
_t('user_exists') and _t('password_required_for_role'). New key invalid_role
in all five translation files.
2026-09-10 00:17:23 +07:00
hell 2ed0bc6ab5 fix(users): initialise the password label, and localise the empty-username error
- The init call for toggleNewUserPasswordReq() had landed in the ucProtocol
  change handler, which is unrelated to the add-user modal: the label still
  said "required" for the default role "none" until something touched that
  dropdown. It runs on DOMContentLoaded now.
- api_update_user answered a hard-coded English "Username must not be empty"
  next to _t('user_exists'), so a Russian UI got one toast in each language.
  New key username_empty in all five translation files.
2026-09-10 00:17:22 +07:00
hell 5a06a713a1 fix(api): register the protocol rename route once
The merge left two handlers on POST /api/servers/{server_id}/protocol/rename.
Starlette matches in definition order, so the first one won and the copy that
caps the name at CUSTOM_PROTOCOL_NAME_MAX and strips the protocol key never
ran - the cap added in #144 was silently dead, and /docs showed the endpoint
twice. Kept the capped handler.

tests/test_route_uniqueness.py now fails on any duplicate (path, method).
2026-09-10 00:17:22 +07:00
hell 9ff488de42 fix: three follow-ups from the second review round
- CachedStaticFiles matched the substring `v=`, so `?rev=1` looked
  fingerprinted and earned the 180-day immutable lifetime. It parses the
  query now and looks for a parameter actually named `v`.
- saveRenameProtocol() restored the button label from a server-rendered
  `'{{ _("save") }}'` string literal; a locale whose translation carries a
  quote would break the whole inline script. Uses the runtime helper and
  textContent instead.
- The add-user modal kept the "password optional" hint up after the role
  changed to one that requires a password. It is hidden with the label now.
2026-09-10 00:17:21 +07:00
hell 95bfd42ff8 fix: four defects in the code that came with #150-#152
- app.py handed every /static response `max-age=15552000, immutable`, but
  only the assets built through static_version() carry ?v=<mtime>. The
  favicon, the icons, qrcode.min.js, searchable-select.js, the vendored
  CodeMirror bundles and ReDoc keep one URL forever, so a redeploy could not
  reach a browser that had already cached them. Immutable is now limited to
  fingerprinted URLs; the rest get an hour plus a revalidation.
- WireGuardManager._apply_bw_limits ran `docker cp ... && docker exec ...`
  through run_sudo_command, which only privileges the head of the chain, so
  the exec ran as the unprivileged SSH user. Wrapped in one `sh -c`, the
  pattern tests/test_sudo_command_chains.py enforces.
- toggleConnection() restored the row only in the error path. When
  loadConnections() fails after a successful toggle, the row it was going to
  rebuild stays disabled with a spinning bulb. Restore now runs in `finally`.
- The add-user modal starts on role "none", where the password is optional,
  but the label said "required" until the select fired its change event.

Tests: the bandwidth chain joins the sudo-chain suite, and a new
tests/test_static_cache.py pins both cache lifetimes.
2026-09-10 00:17:21 +07:00
hell a4d68d424e fix: repair main after the exit-node and UI merges
Two defects came in with the merges of #149-#152 and turn the test suite red:

- templates/server.html carries two copies of the rename-protocol block, so
  'let renameProtoTarget' is declared twice and the whole inline script dies
  with a SyntaxError - every button on the server page stops working. Kept
  the copy that restores the button label and re-enables the input.
- tests/test_host_tuning.py still expects the pre-exit-node grep. Since #145
  and #149 the probe greps '^amnezia-(awg|exit)'.
2026-09-10 00:17:20 +07:00
Public relations & Virtual technology c39ea0c9ce Update app.py v1.6.5 2026-09-09 14:11:07 +03:00
Public relations & Virtual technology 5b62d5385b Merge pull request #152 from leonidorlov-hash/feat/users-norole
Users: editable username, optional password with 'No role' default + hardening
2026-09-09 12:18:14 +03:00
Public relations & Virtual technology f66ba9b71d Merge branch 'main' into feat/users-norole 2026-09-09 12:18:05 +03:00
Public relations & Virtual technology 9c0f28c52d Merge pull request #150 from leonidorlov-hash/feat/ui-polish
UI polish: peer badge for speed limits, spinner animation fix, client-actions wrap
2026-09-09 12:17:09 +03:00