504 Commits
Author SHA1 Message Date
Алексей ЦыганковandClaude Opus 5 2452af4a58 Keep AWG tunnels IPv4-only unless IPv6 works end-to-end
Dual-stack was enabled whenever the *host* had a global IPv6 address, but
Docker networks are IPv4-only unless the daemon is explicitly configured
for IPv6. On such a server the tunnel still gets the fd42:8:1::/64 ULA and
NAT66 with nowhere to forward, while clients receive an IPv6 address, an
IPv6 DNS server and ::/0 — so every IPv6 request blackholes. Clients that
prefer IPv6 (macOS in particular) connect but pass no traffic, while the
very same config works from an IPv4-only mobile network.

- probe the protocol container for an IPv6 default route, not just the host
- add an AWG_IPV6 knob (auto/off/on) next to the panel's other env vars
- emit ::/0 in client configs only when the client really has an IPv6
  address, so an IPv4-only tunnel no longer hijacks the client's own IPv6

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 17:20:47 +07:00
leonidorlov-hash f2acebe948 fix(awg): auto-enable Docker IPv6 in prepare_host when host has global IPv6
Dual-stack instances silently ended up IPv4-only: the panel writes v6
addresses into configs and NAT66 rules, but the Docker daemon defaults
to IPv4, so containers had no v6 route out (ping6 -> Network unreachable,
clients got no IPv6 despite fd42:: addresses in their configs).

prepare_host now checks for a global IPv6 address on the host, and if
/etc/docker/daemon.json lacks ipv6 support: backs it up (daemon.json.bak.awp),
merges in {"ipv6": true, "fixed-cidr-v6": "fd00:42::/64"} (python3 merge,
fallback to a fresh file when python3 is missing), and restarts docker.
Runs once; skipped on IPv4-only hosts and when already enabled.
2026-08-28 03:50:10 +03:00
leonidorlov-hash 956edcb966 fix(awg): auto-enable Docker IPv6 in prepare_host when host has global IPv6
Dual-stack instances silently ended up IPv4-only because the Docker
daemon defaults to IPv4: containers got no v6 address/route. prepare_host
now merges ipv6+fixed-cidr-v6 into /etc/docker/daemon.json (with .bak.awp
backup, python3 merge, fallback to fresh file) and restarts docker —
once, only when the host actually has global IPv6.
2026-08-28 03:27:55 +03:00
leonidorlov-hash 17f3d1fc91 feat(awg): install amneziawg kernel module (DKMS) on protocol install
Without the kernel module, awg-quick in the container falls back to the
userspace amneziawg-go, which is slower under load. New step in
install_protocol: build and install amneziawg v3.1.20260812 via DKMS
(supports AWG 3.1 features, backward-compatible with AWG 2.0 configs).

Best effort with automatic userspace fallback:
- skipped when the module already exists
- skipped when Secure Boot is enabled (unsigned module won't load)
- skipped when headers/build tools can't be installed or build fails

The container already mounts /lib/modules and has SYS_MODULE, so
awg-quick picks the kernel path automatically on next (re)start.
Module status is shown in the Host tuning modal (kernel version or
'userspace (amneziawg-go)').
2026-08-28 02:41:35 +03:00
leonidorlov-hash 24a8d19dc3 feat(awg): container network tuning, host BBR, and tuning status modal
Container image (Dockerfile):
- writes sysctl tuning into /etc/sysctl.conf (64MB socket buffers,
  netdev_max_backlog, somaxconn, tcp_fastopen=3, tcp_mtu_probing, etc.),
  same set the AmneziaVPN client uses, minus hybla which needs a kernel
  module on the host
- start.sh applies it via 'sysctl -p' at container start (Alpine does
  not apply sysctl.conf by itself)
- docker run gets --ulimit nofile=51200:51200 (limits.conf has no
  effect on containers)

Host (new setup_host_tuning() step in install_protocol):
- enables BBR + fq qdisc, loads tcp_bbr if needed, persists via
  /etc/sysctl.d/99-awp-bbr.conf and /etc/modules-load.d/awp-bbr.conf
- silently skipped when the kernel lacks BBR

UI:
- new 'Host tuning' button in the server status bar opens a modal with
  live host sysctls (congestion control, qdisc, conntrack usage,
  backlog, somaxconn) and per-AWG-container values (buffers, fastopen,
  mtu_probing, nofile) + container online status
- adds POST /api/servers/{id}/host_tuning and host_tuning translations
2026-08-28 02:36:32 +03:00
leonidorlov-hash 4e02f2ea35 fix(ui): restore missing block scripts tag (template syntax error) 2026-08-28 02:28:15 +03:00
leonidorlov-hash 1c62291fcc refactor(ui): move tuning summary to server-level 'Host tuning' modal
Tuning data describes the whole server, not a single protocol config.
Replaces the per-config block with a '📊 Host tuning' button in the
server status bar opening a modal with host sysctls (BBR, qdisc,
conntrack, backlog, somaxconn) and per-AWG-container live values.
Adds POST /api/servers/{id}/host_tuning and host_tuning translation key.
2026-08-28 02:24:03 +03:00
leonidorlov-hash af3f6120b6 fix(awg): never lower an existing higher nf_conntrack_max 2026-08-28 02:11:52 +03:00
leonidorlov-hash 9c61d05c31 fix(awg): raise nf_conntrack_max on install (default too low for VPN)
On small VMs the conntrack table default can be ~7680 entries. Every
client flow through the NAT consumes one entry, so a handful of active
peers fills the table and the kernel drops packets
('nf_conntrack: table full'). Raise to 262144 during setup_firewall()
when the current value is lower; persisted via
/etc/sysctl.d/98-awp-conntrack.conf.
2026-08-28 02:10:44 +03:00
leonidorlov-hash 549b26fceb feat(awg): tuning summary in server config modal + raise nf_conntrack_max
- Server config modal now shows container name/status, host BBR/qdisc and
  conntrack usage, and live container sysctl values (buffers, fastopen,
  mtu_probing, nofile).
- setup_host_tuning() also raises net.netfilter.nf_conntrack_max to
  262144 (default ~7680 on small VMs causes 'nf_conntrack: table full'
  packet drops on busy VPN gateways). Persisted via
  /etc/sysctl.d/98-awp-conntrack.conf.
2026-08-28 02:09:55 +03:00
leonidorlov-hash 8acdb8c278 fix(awg): set nofile ulimit via docker run (limits.conf has no effect in containers) 2026-08-28 01:56:18 +03:00
leonidorlov-hash 90b39351b0 feat(awg): container network tuning + host BBR on install
- Dockerfile now writes AmneziaVPN-style sysctl tuning (64MB buffers,
  backlog, fastopen, mtu_probing, etc.) and nofile limits into the image,
  and start.sh applies them via 'sysctl -p' at container start.
- New setup_host_tuning() step in install_protocol: loads tcp_bbr if
  needed and persists BBR + fq qdisc via /etc/sysctl.d/99-awp-bbr.conf
  and /etc/modules-load.d/awp-bbr.conf.
2026-08-28 01:13:47 +03:00
leonidorlov-hash 4643a7d421 fix(ui): don't clobber the open rename editor on 5s auto-refresh 2026-08-28 00:51:32 +03:00
leonidorlov-hash 29cec6f425 fix(ui): don't clobber the open rename editor on 5s auto-refresh
Poll-triggered loadConnections() rebuilt the whole list innerHTML and
destroyed the rename input mid-typing. Polls are now skipped while a
rename editor is open (manual refreshes still work).
2026-08-28 00:50:20 +03:00
leonidorlov-hash e981c28a4a refactor: drop client-ip part (shipped as a separate PR) 2026-08-28 00:11:37 +03:00
leonidorlov-hash 69166e046a fix(awg): read conntrack table inside the instance container
NAT for the VPN subnet happens in the container's network namespace, so the
host /proc/net/nf_conntrack only sees the container's own IP and per-peer
counts were always zero.
2026-08-28 00:10:01 +03:00
leonidorlov-hash d6c6d50ffe feat(awg): per-peer connection flood warnings (torrent detection)
- Count conntrack entries per peer IP from /proc/net/nf_conntrack (no extra
  packages on the host), matching against the instance subnet
- Record a warning when a peer exceeds 600 simultaneous connections,
  at most one per hour per peer, keeping the last 5 events; persisted in
  conn_warnings.json inside the container data dir (volume-backed)
- Render warnings under the client meta row (.conn-warnings/.conn-warning,
  no inline styles), localized via new conn_warning_text key (en/ru/fr/fa/zh)
- Show peer internal IP next to the client name (.client-ip)
2026-08-28 00:10:01 +03:00
leonidorlov-hash 964b6b778e feat(ui): show peer internal IP next to the client name
Uses clientIp/allowedIps already present in the API response. Also
fixes the rename editor picking up the IP text together with the name
(saving produced 'Name 10.x.x.x 10.x.x.x').
2026-08-28 00:08:36 +03:00
leonidorlov-hash 04e8b14822 fix(ui): invoke live speed/online rendering dropped during #88 squash merge
The squash merge of #88 kept the helpers (parseHandshakeAgo,
formatHandshakeCompact, computeRates) and the CSS (.meta-online,
.speed-badge, .avatar-online), but the render loop never called them,
so the online pulse, compact handshake and live speed badge never
appeared. This wires the render loop back to the helpers.
2026-08-28 00:05:21 +03:00
leonidorlov-hash 549faafc41 fix(ui): restore live-UI call sites lost in upstream merge; keep peer IP out of rename editor
- invoke parseHandshakeAgo/formatHandshakeCompact/computeRates again so
  the online pulse dot, compact handshake and speed badge render
- startRenameConnection: strip the .client-ip span from the edited name
  (previously saving produced 'Name 10.x.x.x 10.x.x.x')
2026-08-28 00:03:57 +03:00
leonidorlov-hash 4b897a332d fix(awg3): use fixed random H1-H4 instead of ranges (cherry-pick 605fafb) 2026-08-27 23:43:20 +03:00
leonidorlov-hash 605fafb528 fix(awg3): use fixed random H1-H4 instead of ranges
Ranged magic headers (min-max) are valid per spec, but with AWG 3.1
features enabled (HeaderProtectionKey, RandomTrailers, ...) current
clients fail to authenticate packets: the tunnel connects but degrades
to <1 Mbit/s with a flood of 'invalid mac1' errors. Fixed random values
work at full speed and keep header obfuscation per-deployment unique
(unlike the 1/2/3/4 defaults the AmneziaVPN app uses).

Refs: https://github.com/amnezia-vpn/amnezia-client/issues/3073
2026-08-27 23:40:46 +03:00
Viktor Baranov 581ad0dbd0 fix: address self-service review findings 2026-08-26 21:52:00 +03:00
PRVT e8ad7d3026 Fix release binary uploads v1.6.0 2026-08-26 15:34:52 +03:00
PRVT f8d1e1b2d0 Fix release binary uploads 2026-08-26 15:22:01 +03:00
hellandClaude Opus 5 026b7712f3 Merge branch 'main' into feat/awg-special-junk-mtu-dns
v1.6.0 landed the same fix for the unbound `dns1`/`dns2` in the client
config builders that this branch carries, and wires in the dual-stack
`address_line`/`dns_line` the same way. Merged as-is; the MTU lookup
from this branch stays on top of it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 19:11:14 +07:00
Public relations & Virtual technology 6d3c6f0e96 Update README.md 2026-08-26 15:00:51 +03:00
Public relations & Virtual technology ede6dfd7c0 Merge pull request #95 from helldweller/feat/ghcr-publish
Publish Docker images to GHCR from the build workflow
2026-08-26 14:32:26 +03:00
Public relations & Virtual technology 98880cbdd5 Merge pull request #96 from helldweller/fix/current-version-1.6.0
Bump CURRENT_VERSION to v1.6.0
2026-08-26 14:31:57 +03:00
hellandClaude Opus 5 fe94dbf4f9 Bump CURRENT_VERSION to v1.6.0
The v1.6.0 release commit (dec0e90) changed app.py but left the constant
at v1.5.0, so the settings page shows the wrong version in the badge and
the update check never settles: it compares CURRENT_VERSION against the
tag_name of the latest GitHub release, so an up-to-date 1.6.0 panel keeps
advertising "Update available: v1.6.0".

Reported in #93.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 16:02:43 +07:00
hellandClaude Opus 5 ec3c750480 Remove temporary pull_request debug trigger
The workflow was validated on a pull request in the fork (all jobs green,
multi-arch manifest pushed to GHCR). Drop the debug-only bits: the
pull_request trigger, the PUSH_IMAGE guard and the pr-<number> tag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 15:12:29 +07:00
hellandClaude Opus 5 48227ae77a Publish Docker images to GHCR from the build workflow
Add a `docker` job to the existing build workflow that builds both image
variants (Dockerfile and Dockerfile.warp) with Buildx and pushes them to
ghcr.io/<owner>/amnezia-panel on pushes to main and on v* tags.

The image name is derived from the repository owner, so forks publish to
their own namespace without any edit. Tags follow the scheme already used
on Docker Hub: latest, <branch>, semver (v1.6.0 / 1.6 / 1) and sha-<short>;
the WARP variant gets the same tags with a -warp suffix.

Docker Hub publishing is untouched. The binary build and release jobs are
unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 15:12:28 +07:00
Viktor Baranov 34bfdbb3b4 feat: add Telegram slash command suggestions 2026-08-25 14:24:45 +03:00
Viktor Baranov 7cbb08aa36 fix: localize Telegram bot menus 2026-08-25 13:55:41 +03:00
hellandClaude Opus 5 63dbb7ba98 Encode vpn:// keys with the base64 alphabet the client decodes
Amnezia decodes a vpn:// key with
QByteArray::Base64UrlEncoding|OmitTrailingEquals, and Qt's non-strict
fromBase64 skips characters outside that alphabet rather than failing.
Standard base64 emits '+' and '/', which are not in it, so every byte
after the first such character shifts and the tail of the config turns
into noise. The client then loses the [Peer] section and reports the key
as invalid.

Whether that happens depends on the exact config bytes: base64 of ASCII
text only produces '+' or '/' when a group ends on '>', '?' or '~'. That
made the bug intermittent until now -- the default I1 packet contains
'>', so it is about to become common.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 14:03:49 +07:00
hellandClaude Opus 5 bbe2a6e95b Add MTU, DNS and I1-I5 fields to the UI
The install dialog gains an AmneziaWG section, and installed AWG
protocols gain an "AWG" button next to the raw config editor that opens
the same fields for editing. A "Default" button fills I1 with the packet
the desktop client uses, so the common case is one click.

AWG Legacy predates special junk packets, so it only gets MTU and DNS.

The dialog says outright that changes apply to configs generated from
now on: a client that already imported a config keeps its old values,
because the values live in the config it holds.

fr/zh/fa strings are machine-written and want a native reader.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 14:03:48 +07:00
hellandClaude Opus 5 5549b68c55 Expose AWG MTU, DNS and I1-I5 over the API
Install accepts awg_mtu / awg_dns1 / awg_dns2 / awg_i1..awg_i5, and two
new endpoints read and update the same values on an installed protocol:

    POST /api/servers/{id}/awg/settings
    POST /api/servers/{id}/awg/settings/save

Malformed packets are rejected with 400 before any SSH connection is
opened -- the panel opens a fresh connection per call, and a burst of
them runs into sshd's MaxStartups.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 14:03:48 +07:00
hellandClaude Opus 5 01f24960b1 Support MTU, DNS and special junk packets I1-I5 in AWG configs
Three things this fixes, all visible in issue #76.

1. Generated server configs carried a placeholder block:

       # I1 = 0
       # CPS = signature

   `I1 = 0` is not just commented out, it is a no-op even when enabled:
   jp_parse_tags() looks for `<`, finds none, and builds a zero-length
   packet, so the config reads as if obfuscation were on while nothing is
   ever sent. `CPS` does not exist at all -- amneziawg-tools answers
   `Line unrecognized: CPS=1`. Both are gone; real I1-I5 lines are written
   instead, with the same default packet the desktop client ships
   (protocols::awg::defaultSpecialJunk1, a fake DNS response for
   icloud.com behind a random transaction id).

2. MTU defaulted to 1280, which is itself a usable fingerprint. Amnezia's
   own clients use 1376, so that is the default now, and both MTU and DNS
   are read back from the server config instead of being hardcoded.
   They are stored as comments: awg-quick would otherwise resize the
   server interface and call resolvconf, which the container lacks --
   the same trick _get_dns() already used.

3. get_client_config() and add_client() referenced `dns1`/`dns2`, which
   are not bound in either scope, so building any client config raised
   NameError. The dual-stack `address_line`/`dns_line` they belong to
   were computed and then never used; they are wired in instead.

Values reaching a server config are validated against the tag grammar in
junk.c first. A tag the module cannot parse takes the whole interface
down with a bare EINVAL -- the explanation only goes to
net_dbg_ratelimited -- and a value with no tags at all silently sends
nothing.

Removing a packet needs a container restart: `I1 =` cannot be spelled in
a config file (get_value() in amneziawg-tools rejects an empty value), so
syncconf cannot clear what the kernel already holds. Everything else
applies through syncconf with peers left up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 14:03:28 +07:00
Dmitry 57b43bce9d Merge branch 'PRVTPRO:main' into main 2026-08-25 13:36:59 +07:00
Viktor Baranov 4f8c3e4bb6 fix: add self-service settings HTML form to settings page 2026-08-25 09:31:40 +03:00
Viktor Baranov fc8ebf8a53 feat: add Telegram self-service localization with TG_TRANSLATIONS and _tt() integration 2026-08-25 09:25:32 +03:00
Viktor Baranov 572bfec5e9 fix: correct user variable scope in ConnectionService and self-service telegram settings check 2026-08-25 09:19:42 +03:00
Viktor Baranov 4816c36581 i18n: add self-service translation keys for all languages 2026-08-25 09:12:31 +03:00
Viktor Baranov cd0c05852a feat: add self-service UI to settings, server edit, and nav 2026-08-25 09:10:43 +03:00
Viktor Baranov 748fb5de7b feat: add self-service Telegram bot integration 2026-08-25 09:08:44 +03:00
Viktor Baranov 131e0c62be feat: add self-service API routes, models, and settings to app.py 2026-08-25 09:01:46 +03:00
Viktor Baranov 0f91bed479 feat: add ConnectionService and self-service tests 2026-08-25 08:48:18 +03:00
leonidorlov-hash 738d3736bb feat(install): used-port awareness when installing protocols
Previously the install modal defaulted to a fixed port and only avoided
ports of same-family instances, so installing e.g. AWG3 on a host where
AWG2 already held 55424 failed mid-install with a half-created broken
container ('port is already allocated').

- GET /api/servers/{id}/used-ports: real listening UDP/TCP ports via ss
- api_install_protocol: reject a busy port upfront with a clear error
  naming the owning process, before any docker run
- Install modal: fetches used ports, auto-suggests the first free port,
  live-validates manual input (red field + hint + disabled Install button)
- new translation key port_in_use_by (en/ru/fr/fa/zh)
2026-08-25 04:24:38 +03:00
leonidorlov-hash add408f67f fix(awg): read conntrack table inside the instance container
NAT for the VPN subnet happens in the container's network namespace, so the
host /proc/net/nf_conntrack only sees the container's own IP and per-peer
counts were always zero.
2026-08-25 02:10:05 +03:00
leonidorlov-hash c16c6af7a4 feat(awg): per-peer connection flood warnings (torrent detection)
- Count conntrack entries per peer IP from /proc/net/nf_conntrack (no extra
  packages on the host), matching against the instance subnet
- Record a warning when a peer exceeds 600 simultaneous connections,
  at most one per hour per peer, keeping the last 5 events; persisted in
  conn_warnings.json inside the container data dir (volume-backed)
- Render warnings under the client meta row (.conn-warnings/.conn-warning,
  no inline styles), localized via new conn_warning_text key (en/ru/fr/fa/zh)
- Show peer internal IP next to the client name (.client-ip)
2026-08-25 02:10:05 +03:00