Hard cutover, part 3: everything that only ever existed to drive the
kernel-module (DKMS) + awg-quick + TPROXY architecture is gone now that
internal/amneziawgnet's embedded path is wired in as the real thing.
internal/amneziawg/manager.go -> instance.go (renamed, ~90% smaller): kept
InstanceFromInbound and its direct helpers (interfaceNameForID,
serverAddress, serverAddressV6) plus the exported FirstIPv4 (still used by
server.go's access-log email index) -- all pure, protocol-shape-only code
with no OS dependency, reused by both the old and new paths historically.
Deleted the old Manager (GetManager/Ensure/Reconcile/StopAll/CollectTraffic/
the fingerprint methods), generateServerConfig and everything under it
(writeObfuscation, defaultPostUpDown, appendOrTrue, detectDefaultInterface),
and process control (interfaceUp/Down, syncConfig, getPeerStats,
IsAwgInstalled). route_egress.go deleted entirely (the TPROXY bridge's
port/fwmark/table constants and rule-rendering, fully superseded by
internal/amneziawgnet's SOCKSPortForInbound/SocksPassword). portfwd.go
trimmed to just the parsing/validation half (ForwardedPortsInclude, still
used for save-time conflict checks); the iptables DNAT rendering half is
gone -- per-client port-forwarding has no equivalent under the embedded
path yet (tracked as Phase 3.6).
install.sh: removed install_ndppd, enable_ipv6_forwarding,
enable_tproxy_support, should/install_amneziawg, and check_secure_boot (and
their call sites) -- roughly 265 lines. No more DKMS build, PPA/keyring
setup, TPROXY kernel module loading, or Secure Boot warning: the embedded
path needs none of it.
Not in this commit (tracked as an explicit follow-up, not silently
dropped): the frontend's routeThroughXray toggle is now vestigial (the
field stays in the Go/JSON schema for backward compat with existing stored
settings, see types.go) but its UI/schema removal needs the frontend
type-regen + openapi.json hand-patch dance this fork always does for a
settings-shape change, which is its own separate pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same 8 findings fixed on upstream-pr/amneziawg, ported here since this
fork's internal/amneziawg + related web/service files predate that PR
branch's own fix-up commits:
1. hostRulesFingerprint now folds in a peer's IPv4 whenever
ForwardedPorts is set, not only when RouteThroughXray is on, so a
re-IP forces the bounce needed to move the DNAT rule too.
2. ValidateConfigValue (new, params.go) rejects control characters in
server/client keys, email and I1 at save time; sanitizeConfigValue
strips them defensively at .conf-render time.
3. checkForwardedPortsConflict now scopes to node_id IS NULL and takes
a pre-loaded portConflictContext (loadPortConflictContext), so a
port used only on another node isn't a false collision and an
inbound with N clients costs one query instead of N.
4. PostDown commands are now best-effort (appendOrTrue) so an external
firewall flush can't abort the rest of the teardown chain.
5. The "ip rule list | grep -q" existence check now uses
grep -c >/dev/null, avoiding a pipefail/SIGPIPE false negative that
could re-add a duplicate rule.
6. route_egress.go's stale "always present, no opt-in" comment
corrected to describe the real RouteThroughXray-gated behavior.
(This fork's genAmneziaWGLink already emits vpn://, and there's no
upstream-facing docs page here, so neither needed the PR branch's
Finding 6 docs/link-format changes.)
7. install.sh: Arch's ndppd install uses pacman -Sy, not -Syu, matching
every other pacman call in the script; should_install_amneziawg
short-circuits to yes when awg is already installed, so `x-ui
update` doesn't re-prompt -- this fork's own opt-out-by-default
philosophy for should_install_amneziawg is unchanged, only the
redundant-reprompt behavior is fixed.
8. CollectTraffic checks pointer identity before writing back a
traffic-counter baseline, so a concurrent restart's freshly-reset
(empty) baseline can't be clobbered by stale pre-restart counters.
sweepOrphansLocked no longer permanently disables itself on a
transient os.ReadDir failure.
go build/vet/test and frontend typecheck/lint/build/vitest all pass.
Each is independently reproducible; fixed together since one review pass
found all of them.
- manager.go: the shared "ip rule add fwmark" policy route had no
existence check, so it duplicated in "ip rule show" on every interface
bounce (which hostRulesFingerprint forces on any client add/remove/
re-IP). Now checked via "ip rule list | grep -q ..." first. (Finding 2)
- params.go: ExternalInterface, IPv6ExternalInterface, and subnetIp/
subnetCidr are interpolated unescaped into a shell-executed PostUp/
PostDown line, but only obfuscation and the IPv6 subnet were validated
before save. Added ValidateInterfaceName (a strict charset+length
pattern) and ValidateSubnetIPv4 (netip.ParsePrefix), wired into
normalizeAmneziaWGSettings. (Finding 3)
- amneziawg_job.go: IsAwgInstalled() existed but nothing ever called it,
so a host without awg/awg-quick (the Docker image, RHEL, Arch, a failed
install.sh PPA step) logged a reconcile failure every 10s forever. Now
checked once an inbound actually needs it, warning once instead of
spamming. (Finding 4)
- client_inbound_apply.go: the WireGuard/AmneziaWG credential
carry-forward (added so a metadata-only client edit doesn't rotate
keys) never covered ForwardedPorts, so a partial edit -- an API call or
Telegram-bot toggle that omits the field -- silently wiped a client's
port-forwarding spec. Carried forward and written back the same way the
key fields already are. (Finding 5)
- manager.go: hostRulesFingerprint keyed each peer on its IPv4 address
only, and structuralFingerprint omitted IPv6Enabled/IPv6ExternalInterface
entirely, so an IPv6-only change could pick the syncconf reload path
(which never re-runs PostUp, leaving a stale NDP-proxy entry) or be a
complete no-op. Both fingerprints now cover the IPv6 fields. (Finding 6)
- port_conflict.go: the AmneziaWG egress bridge (injectAmneziawgEgress)
binds 127.0.0.1:63100+id with no collision check anywhere, since it
isn't a database row the ordinary port-conflict query can see -- same
blind spot the reserved Xray API port already has its own check for.
Added the equivalent check for the AmneziaWG bridge port. (Finding 7)
- install.sh: install_amneziawg ran unconditionally for every install/
update, building a DKMS kernel module and enabling host-wide IPv4/IPv6
forwarding whether or not the feature is ever used. Gated behind a new
should_install_amneziawg (XUI_INSTALL_AMNEZIAWG=true/false, or an
interactive y/N prompt defaulting to no). Also replaced the deprecated
apt-key adv with a dedicated keyring + signed-by= on the Debian branch,
and guarded its sources.list appends against duplication on a retried
install. (Finding 8)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AmneziaWG (WireGuard plus DPI-resistant obfuscation) needs no Docker
here — it runs as a genuine kernel interface via awg-quick/awg, managed
the same way internal/mtproto manages mtg: one Inbound row is one
desired Instance, and a Manager reconciles running interfaces toward
the database every 10s (internal/web/job/amneziawg_job.go) plus
immediately after a client edit (applyLocalAmneziaWG).
Clients reuse model.Client verbatim (the same PrivateKey/PublicKey/
PreSharedKey/AllowedIPs fields WireGuard already uses), so bulk
operations, the QR/share-link modal and subscriptions come from the
shared inbound infrastructure instead of a parallel implementation.
internal/amneziawg owns the obfuscation param generator/validator
(ported from coinman-dev/3ax-ui, upgraded to AmneziaWG 2.0's S3/S4
padding and I1 signature packet) and the exec wrapper around
awg-quick/awg, with fingerprint-based reconcile (noop / reload-via-
syncconf / full restart) mirroring mtproto.Manager so a same-protocol
edit doesn't force an unnecessary interface bounce that would drop
every peer's connection.
Frontend and install.sh's DKMS/awg-tools setup are tracked separately;
this is backend-only.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>