From bde5686401c47c49cf3490ce8b5843dc73b3fb51 Mon Sep 17 00:00:00 2001 From: Kuzz007 Date: Mon, 3 Aug 2026 22:06:50 +0300 Subject: [PATCH] fix(amneziawg): flag Xray for resync when a peer edit changes qualifying state updateAmneziaWGInbound/AddInbound/DelInbound only ever updated the embedded amneziawgnet Device -- they never called SetNeedRestart the way every other protocol's mutation path does. injectAmneziawgnetSocks's Xray-side relay inbound depends on InstanceFromInbound finding at least one qualifying peer, so an edit that flips that (first peer added, last one removed, or the inbound re-enabled) previously required a full panel restart before the relay actually got created or torn down, with no error or signal anywhere. Confirmed as the root cause of a real, separate bug in this fork's retired kernel-module architecture (same missing-flag shape, different manager) -- fixing it here too before this embedded path is ever deployed for real. --- internal/web/runtime/local.go | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/internal/web/runtime/local.go b/internal/web/runtime/local.go index 978003cce..92a390880 100644 --- a/internal/web/runtime/local.go +++ b/internal/web/runtime/local.go @@ -60,7 +60,16 @@ func (l *Local) AddInbound(_ context.Context, ib *model.Inbound) error { if !ok { return nil } - return amneziawgnet.GetManager().Ensure(amneziawgnet.Desired{Instance: inst}) + err := amneziawgnet.GetManager().Ensure(amneziawgnet.Desired{Instance: inst}) + // A brand new inbound can be the first one to qualify for + // injectAmneziawgnetSocks's Xray-side relay inbound (e.g. its first + // valid peer). Ensure only updates the embedded Device -- flag Xray + // for a resync so the relay actually gets created within the next + // ApplyPendingRestart tick instead of only at the next full restart. + if l.deps.SetNeedRestart != nil { + l.deps.SetNeedRestart() + } + return err } body, err := json.MarshalIndent(ib.GenXrayInboundConfig(), "", " ") if err != nil { @@ -78,6 +87,12 @@ func (l *Local) DelInbound(_ context.Context, ib *model.Inbound) error { } if ib.Protocol == model.AmneziaWG { amneziawgnet.GetManager().Remove(ib.Id) + // The removed inbound may have been the only one backing Xray's + // injectAmneziawgnetSocks relay inbound for this tag -- flag a + // resync so the now-stale relay gets torn down promptly. + if l.deps.SetNeedRestart != nil { + l.deps.SetNeedRestart() + } return nil } return l.withAPI(func(api *xray.XrayAPI) error { @@ -134,7 +149,18 @@ func (l *Local) updateMtprotoInbound(ctx context.Context, oldIb, newIb *model.In // can reconfigure the running embedded Device in place via IpcSet instead // of always rebuilding it (see internal/amneziawgnet.Manager.ensureLocked -- // only an address/MTU change forces a rebuild there, not a peer edit). +// +// Every exit path below only touches the embedded Device via +// amneziawgnet.GetManager() -- none of it rebuilds Xray's own config, which +// is what actually creates/removes injectAmneziawgnetSocks's relay inbound. +// A peer edit that changes whether this inbound has a qualifying peer at +// all (its first peer added, or its last one removed) must still get that +// relay created or torn down, so flag Xray for a resync unconditionally +// here rather than trying to enumerate which of the branches below need it. func (l *Local) updateAmneziaWGInbound(ctx context.Context, oldIb, newIb *model.Inbound) error { + if l.deps.SetNeedRestart != nil { + l.deps.SetNeedRestart() + } if oldIb.Protocol == model.AmneziaWG && newIb.Protocol != model.AmneziaWG { amneziawgnet.GetManager().Remove(oldIb.Id) if !newIb.Enable {