Ant Design's Spin wraps its content in aria-live="polite", so every
WebSocket tick on the dashboard, inbounds, clients and nodes pages was
announced, and only the changed number was read, with no label.
Pass aria-live="off" on every Spin that wraps content; aria-busy still
signals loading. Each vital tile is now a group named by its metric,
so "47.2 %" is read as CPU.
rc-select renders each tag's remove "x" as an aria-hidden span with no
tabIndex, so screen-reader users could only Backspace the last value
away, never remove one SNI or inbound from the middle.
selectTagRender swaps in a real button named "Remove <value>" and
moves focus to the next tag after removal. It is applied to every
tags/multiple Select. maxTagCount="responsive" is dropped because it
hid selected inbounds behind "+N", where no screen reader could reach
them.
Two oxlint rules (antd-a11y/spin-content-not-live and
antd-a11y/select-tag-remove) keep new code from regressing either fix.
xray-core 26.10.31 (#7231) added an "s3" XDRIVE service that carries the
stream through S3-compatible storage: secrets are AccessKeyID,
SecretAccessKey and an optional SessionToken, an "s3" object names the
endpoint, region, bucket and pathStyle, and remoteFolder becomes the
object prefix.
The schema and the shared XDRIVE form offer the service with a field for
every S3 key, so an outbound or inbound keeps its storage on save. The
save path refused "s3" as an unknown service; it now runs the vendored
loader, whose ValidateS3Config would otherwise only fire when the
listener starts and stop every inbound. A node refuses a pushed S3
storage like a pushed template one, since its endpoint is any URL the
node would dial, including its own panel. Labels in all 14 locales;
docs updated in en/fa/ru/zh.
xray-core 26.10.31 (#7288) widened a TUN inbound's
autoSystemWfpBlockLeak: "dns" now also blocks DNS over TLS/QUIC (port
853), "windnsclient" covers only Windows' DNS Client service, and any
"tcp/<port>" or "udp/<port>" keeps that port inside the TUN. Anything
else, such as "tcp/0" or "icmp/1", stops the core at startup.
The form offers windnsclient and takes typed port values, the hint
explains them in all 14 locales, and the inbound and template save
paths run the core's own ExpandWfpBlockLeak so a value it refuses is
refused at save instead of taking every inbound down.
xray-core 26.10.29 (#7267) checks a scheduled geodata section when the
core starts: at most 100 assets, every file a .dat, a cron it can parse
and an @every interval of at least a minute. A refusal there stops the
core and every inbound with it, and the panel's own file-name pattern
still let any extension through.
The geodata editor now requires a .dat file name, and the template save
path applies the same checks the core does. The invalid-file hint names
the .dat requirement in all 14 locales.
Adds PattN's semi-python and real-firefox client cipher lists as presets
in the cipher suites picker. A preset replaces the whole list because the
order is part of the ClientHello.
The inbound TLS form now warns when cipher suites are set under a uTLS
fingerprint other than "unsafe": clients ignore them there, while an
empty fingerprint is already pinned to unsafe in share links.
* feat(amneziawg): add opt-in Linux kernel datapath
Add the kernel backend as an explicit per-inbound option while keeping the embedded backend as the default. Include host preflight, UI/API support, the bundled installer CLI, lifecycle and accounting integration, regression tests, and upstream-facing documentation. Kernel mode is NAT-only and rejects unsupported routing, IPv6, forwarding, and pinned-listen configurations. Local saves check the local host; remote nodes check their own host during apply.
Preserve adopted interface accounting across panel restarts, retain manager state after failed teardown so the job can retry, prevent embedded startup while a kernel interface remains live, and prune collector state for removed peers. Tested the restart accounting path on Debian 13 with XanMod 7.1.11 on x86_64; Docker and cross-kernel coverage are not claimed.
* fix(amneziawg): align readiness and endpoint reporting
Expire endpoint sightings using the latest handshake and sweep stale addresses for the polled interface. Keep the readiness API schema, generated client types, and ready-host empty blocker list consistent; watch the nested form field and defer local readiness checks for remote targets. Document safe rollback before using a build without kernel support.
Regression evidence: the idle endpoint test failed before the collector change because the old address remained visible after 31 minutes; endpoint tests pass after the fix. The form component suite passes 3 tests; restoring the invalid array watch made its off-mode severity test fail, while the corrected dot-path watch passes. Targeted Go regressions across amneziawgkern, job, and service pass.
* style(amneziawg): format readiness test
* test: reproduce kernel AWG client validation gaps
* test(service): cover kernel AWG shared client fanout
* fix(amneziawg): validate kernel client fanout and config values
* test(amneziawgkern): cover owned interface lifecycle recovery
* fix(amneziawgkern): recover owned interfaces on restart
* test(amneziawgkern): retain visibility after recovered cleanup failure
* fix(amneziawgkern): report recovered interfaces while cleanup retries
* style(amneziawgkern): satisfy static analysis
* test(amneziawgkern): cover long rendered peer comments
* fix(amneziawgkern): accept long generated config comments
* test(amneziawgkern): refuse foreign occupied interfaces
Capture the current Ensure ordering failure with real temporary config paths and manager side-effect boundaries.
* test(amneziawgkern): cover stale interface collisions
Extend the Ensure regression across absent links, mismatched live interfaces, cached manager state, and Remove teardown.
* fix(amneziawgkern): verify interface ownership before mutation
Refuse to replace or tear down links unless the canonical config is a valid panel-owned file and its key matches the live interface. Preserve normal adoption and key rotation, and document migration of legacy unmarked configs.
Validation: go test ./internal/amneziawgkern -count=1; go test -race ./internal/amneziawgkern -count=1; golangci-lint run ./internal/amneziawgkern/...; git diff --check.
* build: update Go toolchain to 1.27.3
* test(amneziawg): reproduce unsafe key rotation recovery
Keep the previous owned config authoritative until teardown succeeds, and require fresh managers to rebuild structural key changes. These regressions expose the stuck live-key/disk-key mismatch after teardown failure.
* fix(amneziawg): recover interrupted key rotations
Keep the old owned config on disk until a structural rebuild has confirmed the previous interface is down. Fresh managers only adopt when the live config and desired structural identity match, so retries and removals retain the key needed to prove ownership.
* fix(scanner): align Go image with module version
* fix(docs): pin pnpm release with bootstrap fix
pnpm 12.9.1 restores a Node launcher before its postinstall replaces the target with ELF; pnpm 11 then runs that binary as JavaScript. Pin 12.10.0, which includes the shim fix, so the docs CI bootstrap can switch versions safely.
* fix(xray): build core from pinned module
The configured Xray release asset tag is unpublished, and the latest published binary lacks GetUsersStats used by the panel. Build the packaged core from the exact Xray module version already selected in go.mod for Docker and release targets.
* fix(tests): stabilize local network fixtures
Keep the TUIC stream fragmentation check on a working UDP loopback path,
allocate the AmneziaWG fixture ports dynamically, and wait for a real
Xray stats response before starting API mutation assertions. Recreate
failed readiness channels so an early connection cannot stall the probe.
Verified host and offline parallel Go suites, repeated occupied-port and
UDP tests, race/shuffle runs, and golangci-lint. Production behavior and
dependency versions are unchanged.
* fix(i18n): add the kernel-mode AmneziaWG keys to sk-SK
main gained the sk-SK locale after this branch added its kernel-mode keys
to the other 13, so i18n-dead-keys.test.ts failed on the merged tree.
* refactor(amneziawgkern): apply kernel interfaces with direct commands, no awg-quick
awg-quick evaluates PostUp/PostDown through a root shell, so every value the
panel rendered into its config was one validation slip away from root command
execution. The interface is now built with direct ip/awg/nft execs from a
setconf-only file: link add, awg setconf, address, mtu, an atomically replaced
nftables table per inbound, and the IPv4 forwarding sysctl written directly.
- Panel-owned names and paths: xui-awg<id> interfaces, xui_awg<id> NAT tables
and configs under the panel's data dir, so nothing collides with an
operator's own awg0/awg1 awg-quick tunnels in /etc/amnezia/amneziawg.
- FORWARD accepts (comment 3x-ui:xui-awg<id>) when iptables is present: Docker
and ufw set the FORWARD policy to DROP, which silently black-holed the tunnel.
- A full panel stop records the final counters and tears the interfaces down,
like the embedded datapath; a stopped panel enforces no quota or expiry.
Crash survivors are still adopted without a rebuild.
- The job reads counters before reconciling, and from every managed interface,
so a client disabled for quota, or a removed inbound, is charged for its last
interval instead of losing it with the dropped peer.
- An unchanged reconcile tick forks no process (it probed the live key with
`awg show` every 10s); readiness no longer loads the module from a GET.
Verified on the real module (WSL2 6.6 kernel, v3.1.20260906): create, peer and
egress changes in place, SIGKILL adoption with the same ifindex, SIGTERM
teardown leaving no link, table, FORWARD rule or config behind.
* fix(amneziawg): gate kernel host readiness only when a save starts the datapath
Every save of a kernel-mode inbound re-ran the host preflight, so a host that
lost its module (a kernel upgrade without a DKMS rebuild) refused even
disabling the inbound or switching it back to embedded mode. Host blockers now
apply only when the save starts the kernel datapath: a new inbound, a switch
from embedded, or enabling a disabled one. Feature blockers (IPv6, forwarded
ports, pinned listen) still apply to every save.
* feat(nodes): refuse kernel-mode AmneziaWG pushes to nodes that predate it
A node built before kernel mode drops the unknown kernelMode field and serves
the inbound on the embedded datapath while the master's form shows kernel
mode on. Nodes now advertise awg-kernel in X-3x-Node-Caps, and Remote refuses
to push a kernel-mode inbound to a node that has not advertised it, learning
the caps with an inbound list first when this Remote has not heard from it.
* fix(amneziawg): pin the kernel module installer to verified commits
The installer ran as root and built whatever upstream tag sorted newest at
install time. It now clones fixed tags (module v3.1.20260906, tools
v3.1.20260812) and refuses unless each resolves to its recorded commit. The
udp_tunnel perl patch is gone: those releases carry the kernel 7.1.5 compat
upstream, so the patch never matched.
* fix(i18n): translate the kernel-mode AmneziaWG strings
All thirteen non-English locales carried the English text verbatim.
* fix(amneziawg): skip the kernel traffic flush when no kernel interface runs
Shutdown listed the desired AmneziaWG inbounds from the database even on a
panel with no kernel interface, an unneeded query on every stop. A server
stopped before its database was opened panicked on it, which turned
TestServerStopWhenXrayNotRunningSucceeds red whenever it ran first.
---------
Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
Rotating SNIs meant one host entry per domain. A host now takes a pool of
SNIs: with more than one, every generated config gets a random entry,
or, with the shared switch on, the whole host group uses one pick per
subscription fetch. Raw, JSON and Clash output, the panel's link preview
and node adoption all carry the pool.
sni stays the pool's first entry, so a rolled-back binary and API callers
that only know the single field keep working; an sni outside the pool
becomes its primary. A host with one SNI renders exactly as before.
The bot answered every action with a new message, so a chat filled with stale
menus whose buttons still acted, and long client or inbound lists were paged
through dozens of buttons. Each chat now has one live screen that is edited in
place: the menu is grouped into Server, Clients, Traffic and Maintenance, lists
open through inline search, the add-client flow starts from a draft card, QR
codes arrive as one labelled sheet, and repeated notices collapse into one with
a counter. A client's /start and /usage show only that client's own links and
usage, groups are refused, and the settings page warns when the bot's inline
mode is off. Admin topics, the indexed Telegram lookup and every locale,
Slovak included, carry over.
Host rules applied per inbound only, so every client of an inbound got the same
subscription endpoints (#6618). A client can now be bound to one or more host
rules, and its raw, JSON and Clash subscriptions render only those hosts, in
either subscription order; an unbound client is unchanged. A binding whose hosts
are deleted, disabled or excluded from the format fails closed: the client gets
no links there but stays in traffic accounting. Host edits keep row ids, so
bindings survive them; nil leaves a stored binding alone and an empty list
clears it.
Adds a complete Slovak translation of every panel and bot string, registers
sk-SK in the language picker (a browser set to sk now lands on it instead of
English) and loads Ant Design's Slovak locale. The locale count in the READMEs,
docs and contributor guides moves from 13 to 14.