From 07cdb82027e4ab86c0d1be96a67a92d4961319f8 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Fri, 15 May 2026 12:43:16 +0200 Subject: [PATCH] fix(inbounds): don't delete remote inbound when toggling enable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SetInboundEnable called rt.DelInbound for every runtime, but Remote.DelInbound hits panel/api/inbounds/del/:id on the node — a real row delete, not just a "stop serving" hint like Local.DelInbound. Flipping the enable switch on a remote inbound therefore wiped the row on the node entirely. Route remote inbounds through UpdateInbound instead so the row stays and only the enable flag is patched. Local path keeps the Del+Add flow since that's how Xray's gRPC API expects to be driven. Fixes #4402 --- web/service/inbound.go | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/web/service/inbound.go b/web/service/inbound.go index 866a765c..16bb2528 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -521,6 +521,19 @@ func (s *InboundService) SetInboundEnable(id int, enable bool) (bool, error) { return true, nil } + // Remote nodes interpret DelInbound as a real row delete (it hits + // panel/api/inbounds/del/:id on the remote), so toggling the enable + // switch on a remote inbound used to wipe the row entirely (#4402). + // PATCH the remote row via UpdateInbound instead — preserves the + // settings/client history and just flips the enable flag. + if inbound.NodeID != nil { + if err := rt.UpdateInbound(context.Background(), inbound, inbound); err != nil { + logger.Debug("SetInboundEnable: remote UpdateInbound on", rt.Name(), "failed:", err) + return false, err + } + return false, nil + } + if err := rt.DelInbound(context.Background(), inbound); err != nil && !strings.Contains(err.Error(), "not found") { logger.Debug("SetInboundEnable: DelInbound on", rt.Name(), "failed:", err) @@ -530,20 +543,13 @@ func (s *InboundService) SetInboundEnable(id int, enable bool) (bool, error) { return needRestart, nil } - addTarget := inbound - if inbound.NodeID == nil { - runtimeInbound, err := s.buildRuntimeInboundForAPI(db, inbound) - if err != nil { - logger.Debug("SetInboundEnable: build runtime config failed:", err) - return true, nil - } - addTarget = runtimeInbound + runtimeInbound, err := s.buildRuntimeInboundForAPI(db, inbound) + if err != nil { + logger.Debug("SetInboundEnable: build runtime config failed:", err) + return true, nil } - if err := rt.AddInbound(context.Background(), addTarget); err != nil { + if err := rt.AddInbound(context.Background(), runtimeInbound); err != nil { logger.Debug("SetInboundEnable: AddInbound on", rt.Name(), "failed:", err) - if inbound.NodeID != nil { - return false, err - } needRestart = true } return needRestart, nil