diff --git a/.gitignore b/.gitignore index 0991d4f30..fd67a2625 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ .idea/ .vscode/ .cursor/ -.claude/ +.claude/* .cache/ .sync* diff --git a/internal/xray/hot_diff.go b/internal/xray/hot_diff.go index 6a9816bc8..21a4ed616 100644 --- a/internal/xray/hot_diff.go +++ b/internal/xray/hot_diff.go @@ -108,6 +108,10 @@ func diffInbounds(oldCfg, newCfg *Config, diff *HotDiff) bool { if oldIb.Tag == apiTag || oldIb.Tag == "api" { return false } + if exists && (inboundHasReverseClient(oldIb) || inboundHasReverseClient(newIb)) { + logger.Debug("hot diff: inbound [", oldIb.Tag, "] carries a reverse-tagged client, forcing a full restart instead of a hot swap") + return false + } diff.RemovedInboundTags = append(diff.RemovedInboundTags, oldIb.Tag) if exists { raw, err := json.Marshal(newIb) @@ -134,6 +138,31 @@ func diffInbounds(oldCfg, newCfg *Config, diff *HotDiff) bool { return true } +func inboundHasReverseClient(ib *InboundConfig) bool { + if ib == nil { + return false + } + var settings struct { + Clients []struct { + Reverse json.RawMessage `json:"reverse"` + } `json:"clients"` + } + if err := json.Unmarshal(ib.Settings, &settings); err != nil { + return false + } + for _, c := range settings.Clients { + if len(c.Reverse) == 0 { + continue + } + var tag any + if err := json.Unmarshal(c.Reverse, &tag); err != nil || tag == nil { + continue + } + return true + } + return false +} + // diffOutbounds fills diff with outbound removals/additions keyed by tag. // The first outbound is xray's default handler and the API can only append, // so any change to its identity or content forces a restart. Reordering of