504 Commits
Author SHA1 Message Date
Public relations & Virtual technology b729fd381a Merge pull request #180 from Almeonamy/feat/email-config-delivery
feat(mail): email configs and proxy links to one user or to everyone
2026-09-28 16:24:57 +03:00
Public relations & Virtual technology 25e66b4b50 Merge branch 'main' into feat/email-config-delivery 2026-09-28 16:24:47 +03:00
Public relations & Virtual technology 7adb527921 Merge pull request #179 from Almeonamy/feat/instance-public-address
Per-instance public address for client endpoints
2026-09-28 16:17:11 +03:00
Public relations & Virtual technology 938054c2bc Merge branch 'main' into feat/instance-public-address 2026-09-28 16:17:00 +03:00
Public relations & Virtual technology 95ff3b54ff Merge pull request #178 from leonidorlov-hash/fix/awg-build-detached
fix(awg): survive unstable links during docker build + honest error logs
2026-09-28 16:12:53 +03:00
Public relations & Virtual technology 68920c6de0 Merge pull request #177 from leonidorlov-hash/feat/peer-poll-interval
feat(servers): opt-in live peer refresh interval (default off)
2026-09-28 16:12:37 +03:00
SeraishandClaude Opus 5.5 0568c92936 fix(awg): recognise AWG 3 installed by the Amnezia client in amnezia-awg2
The official Amnezia client installs AmneziaWG 3.x into the amnezia-awg2
container; only HeaderProtectionKey in the config tells it from 2.0. Flag
it on /check and name the card/connection accordingly, keeping the
protocol key and container untouched.

Closes #191

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 12:38:51 +00:00
Rostislav Kabalin 745f652150 Fix pooled SSH connection race 2026-09-24 20:09:08 +03:00
Максимов Владимир Сергеевич 9db04ab1cf fix(telemt): use config_text when saving client config
add_client() and edit_client() referenced config_content, which only
exists in install_protocol(), raising NameError: name 'config_content'
is not defined on every telemt client create/update (issue #109).

Save the locally built config_text instead, and cover both paths with
regression tests.
2026-09-22 09:34:38 -07:00
leonidorlov-hash 2b3462cd4e fix(ssh): hold _conn_lock for the whole command; no mid-command teardown
force_disconnect() (pool eviction) only took _conn_lock while exec took
_exec_lock, so an eviction could null self.client between
ensure_connected() and exec_command() -> 'NoneType' object has no
attribute 'open_session', with the reconnect retry losing the same race
again. _conn_lock is now an RLock held for the entire command/SFTP
operation, so eviction waits for in-flight work. Regression test blocks a
fake exec and asserts the evictor stays parked until it finishes.
2026-09-21 00:49:09 +03:00
leonidorlov-hash 54fd02b3b3 fix(awg): never show a false 0 connections from a truncated clientsTable read
A truncated/corrupt clientsTable JSON (flaky SSH, channel drop mid-read)
used to hit 'except JSONDecodeError: return []' and the UI rendered
'Connections: 0' for a server that has peers. Now a corrupt prefetched
batch entry is dropped and re-read directly; a corrupt direct read raises
so get_server_status reports an error and the UI shows no number instead
of lying.
2026-09-21 00:49:09 +03:00
leonidorlov-hash 39edc01d41 perf(status): batch docker ps snapshot + one-command AWG state prefetch in /check
- ssh_manager: docker_ps_snapshot() caches 'docker ps -a' (TTL 10s),
  docker_container_state() answers existence/running from the snapshot
- awg_manager: prefetch_awg_state() fetches awg0.conf + clientsTable of
  ALL running AWG containers in ONE ssh command (TTL 15s); check/status
  paths read from the batch
- other managers use docker_container_state with graceful fallback
- caches invalidated on container install/remove/toggle and on
  clientsTable save
- /check: RAHMET ~19 commands -> ~4, FINN ~31 -> ~6
2026-09-20 21:38:07 +03:00
leonidorlov-hash 11dda32b23 fix(awg): back up container peer state before destructive reinstall
AWG containers keep /opt/amnezia/awg (peers, keys, clientsTable) inside the
container - there is no bind mount, so the 'Remove old container' step of a
reinstall destroyed every peer irrecoverably (incident: a stale status made
the UI offer plain Install over a live AWG 3.1 instance; the reinstall path
rm -fv'd the production container). Now install_protocol snapshots the state
dir to /opt/amnezia/backups/<container>-<timestamp> on the host via docker cp
before touching the container. Backup failure warns in the install log but
never blocks the install.
2026-09-20 21:38:07 +03:00
Алексей ЦыганковandClaude Opus 5 0b834c0cc6 fix(awg): size the default client MTU against per-packet obfuscation
AWG_DEFAULTS['mtu'] is a fixed 1376 for every protocol generation, but
AWG 3.x appends a random ContentPaddingAddition to every data packet on
top of S4. With the parameters this panel generates, a fresh 3.x install
produces worst-case packets of 1504 bytes against a plain 1500-byte link.

ContentPaddingAddition is a range, so only the packets drawing its upper
end are oversized: handshakes succeed and small requests work while bulk
transfers stall, which reads as "flaky with some sites" rather than as a
broken tunnel.

Derive the default instead. transport_overhead() counts what obfuscation
costs per data packet (60-byte header stack + S4 + the top of
ContentPaddingAddition; handshake sizes and junk packets travel in their
own packets and are excluded). safe_client_mtu() turns that into the
largest MTU whose worst case still fits, floored at 1280 per RFC 8200.

install_protocol uses it when no MTU was passed; an explicit value from
the UI still wins, since operators behind a provider that tunnels their
traffic need to go lower still. The AWG settings modal now warns when the
entered MTU cannot fit a standard link, and the install modal no longer
hardcodes 1376.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 18:02:33 +07:00
Алексей ЦыганковandClaude Opus 5 0b412e7da3 fix(ui): searchable select dropdown no longer clipped by the modal
The panel is absolutely positioned inside .modal, which is a scroll box
(max-height: 90vh; overflow-y: auto) with a transform on it, inside a
backdrop that carries a backdrop-filter. Such a box clips the panel at its
own edge, and because the transform also makes the modal the containing
block for fixed children, position: fixed does not get out either. Add
Connection with a long user list showed the option list cut off mid-row.

While open, the panel now moves to <body>, where searchable-select.js
places it in viewport coordinates: under the trigger, flipped above it when
there is no room below, with the option list shrunk to fit and the panel
clamped into the viewport. On close it goes back under its own trigger and
drops the coordinates.

Leaving the wrapper costs a few things that used to come for free, so:

- the outside-click check ignores the panel, otherwise clicking the search
  box closed the dropdown straight away;
- scroll (capture phase, so a scroll inside the modal is seen) and resize
  re-anchor the panel; a scroll inside the option list is ignored, and the
  list keeps its scroll position across a reposition;
- the panel closes with the modal it belongs to - a closed modal keeps its
  layout box, so that is an ancestor MutationObserver plus a real
  visibility check - and when the trigger scrolls out of view;
- Tab hands focus back to the trigger, so the next tab stop is the field
  after it and not whatever follows in <body>.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 15:35:41 +07:00
Алексей ЦыганковandClaude Opus 5 71aea08c39 feat(mail): email configs and proxy links to one user or to everyone
The panel could already show a user their configuration, put it behind a
share link or push it through the Telegram bot. Email was the one channel
missing, and the one most admins fall back to a hand-written script for.

Adds an SMTP block to /settings (host, port, none/STARTTLS/SSL, credentials,
sender, Reply-To, timeout) with a one-click test message, a "send by email"
action on every user card, and a mass send from the Users toolbar.

What travels: AmneziaWG / WireGuard peers as .conf attachments plus the
vpn:// key, Xray and Telemt as links -- their config *is* the link, so a
.conf would be a file no client can import. Files and links are independent
switches. The config text comes from the same manager call the share page
and the bot already use, so a protocol that renders in the UI renders in
the mail.

The mass send runs in a worker thread over a single SMTP session (a fresh
connection per message is what gets a sending account throttled) and the
dialog polls its progress: sent, skipped -- no address, disabled, no
connections yet -- and failed, each with the reason next to the name. One
unreachable node costs that one connection, not the message and not the run.

Details worth knowing:
- The stored SMTP password is never echoed back to the page; an empty field
  on save keeps it, a blank one clears it.
- Messages are text plus an HTML alternative styled with the panel's own
  light-theme tokens and appearance title/logo -- no third-party branding.
- A multi-line subject is folded instead of raising: EmailMessage refuses
  linefeeds in headers.
- If the SMTP settings go bad between the click and the worker thread, the
  job finishes with an error rather than staying "running" forever and
  blocking every later run with a 409.

tests/test_mail_service.py covers the settings merge, the transport
(STARTTLS, login, reconnect after a dropped session), message shape and
attachment naming, the single send and every branch of the bulk counters,
plus the API surface (auth, password round-trip, translated errors).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 15:08:01 +07:00
AlmeonamyandClaude Opus 5 959ec78e3e feat(protocols): per-instance public address for client endpoints
Every config and link the panel issues carried the address it opens SSH
to. That is not always where clients can reach the instance: a second IP
on the same box, a port forward, a domain name. Telemt made it worse --
it prints its own tg:// links, and install rewrote public_host in
config.toml with the panel's address, so a manually bound instance
handed out links to the wrong endpoint after every reinstall.

An instance record now carries optional public_host/public_port, set
from the globe button on its card or via
POST /api/servers/{id}/protocol/public-endpoint. The address flows into
AmneziaWG/WireGuard Endpoints, vless:// and tg:// links, connections
created for a user and self-service provisioning. Unset means exactly
what it meant before: the server address and the instance's listen port.

- telemt: the address is written into config.toml and reloaded without a
  restart; saving with the box down keeps the record and warns
- telemt: _patch_public_links matches the keys line by line -- the old
  pattern could span lines and weld public_host onto the line above
- xray: an explicit public port wins, but meta.json (synced from the
  live server.json) still beats the panel's remembered port
- reinstalling an instance keeps the address, like its exit link

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 14:08:54 +07:00
leonidorlov-hash 84175817af fix(awg): run docker build detached, poll for exit code
On flaky links the SSH channel dies seconds into the build while the docker
daemon keeps building to success - a synchronous wait reported a false
failure (observed on EUROBYTE: channel closed 2s in, image built fine).
Start the build under nohup with the exit code written to a file and poll
that file; every poll is a fresh short channel, so transport drops can no
longer falsify the build result. Build log stays on the server for
post-mortem; failures return the log tail.
2026-09-14 14:56:42 +03:00
leonidorlov-hash fd96acad00 fix(awg): build via server-side log file + untruncated error display
BuildKit progress (megabytes of stderr) no longer streams through the SSH
channel mid-build: output goes to /tmp/docker-build-<name>.log on the server
and only the last 6000 bytes come back. This removes mid-build stalls/deaths
on flaky links and guarantees the real error text survives.

Frontend: clearInterval on the error path (progress lines no longer appear
after the failure) and render the error via textContent so log content can
never be swallowed by HTML parsing.
2026-09-14 14:56:42 +03:00
leonidorlov-hash 0c5c3440d3 fix(awg): docker build timeout 300s -> 900s + honest timeout error
Slow registry pulls (flaky routes) exceeded the 300s build timeout; the
killed build left a mid-download stderr fragment - or nothing - as the
'error'. The build now gets 900s, and a timeout-killed build reports
the timeout explicitly with the stdout tail instead of an empty error.
2026-09-14 14:56:42 +03:00
leonidorlov-hash edd6f2f640 feat(servers): opt-in live peer refresh interval (default off)
The connections list used to poll every 5s unconditionally. Each poll is a
full connections read over SSH - fine on fast servers, heavy on slow/flaky
ones. The management modal now offers a per-server interval selector:
0 (off, default), 5, 10, 15, 20, 45, 120, 300, 600 seconds. Stored as
server['peer_poll_interval'], validated server-side against the step list,
i18n for all five locales.
2026-09-14 14:39:53 +03:00
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