From 116ef900d565348530633b9d5bed818899e8a09e Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Wed, 15 Jul 2026 03:24:43 +0200 Subject: [PATCH] fix(xray): do not revive a manually stopped Xray on a background restart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RestartXray cleared isManuallyStopped unconditionally at its top, so the @30s pending-config cron (and warp/ldap/outbound reconcile jobs) that call RestartXray(false) resurrected an Xray the admin had deliberately stopped — unlike the crash-detector, which honors the manual-stop flag. Skip a non-forced restart while the stop flag is set; only an explicit forced restart clears it. --- internal/web/service/xray.go | 6 ++++++ internal/web/service/xray_restart_test.go | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 internal/web/service/xray_restart_test.go diff --git a/internal/web/service/xray.go b/internal/web/service/xray.go index 7e97d8e0c..cd0786abd 100644 --- a/internal/web/service/xray.go +++ b/internal/web/service/xray.go @@ -986,6 +986,12 @@ func (s *XrayService) RestartXray(isForce bool) error { lock.Lock() defer lock.Unlock() logger.Debug("restart Xray, force:", isForce) + // A background reconcile (a pending config-change flag, warp/ldap/outbound + // jobs) must not revive an Xray the admin deliberately stopped; only an + // explicit forced restart clears the manual-stop state. + if !isForce && isManuallyStopped.Load() { + return nil + } isManuallyStopped.Store(false) xrayConfig, err := s.GetXrayConfig() diff --git a/internal/web/service/xray_restart_test.go b/internal/web/service/xray_restart_test.go new file mode 100644 index 000000000..3c39871d5 --- /dev/null +++ b/internal/web/service/xray_restart_test.go @@ -0,0 +1,23 @@ +package service + +import ( + "testing" +) + +// A background (non-forced) restart — the pending-config-change cron, warp/ldap/ +// outbound reconcile jobs — must not revive an Xray the admin deliberately +// stopped. Only an explicit forced restart clears the manual-stop state. +func TestRestartXrayRespectsManualStop(t *testing.T) { + setupSettingTestDB(t) + if err := (&SettingService{}).saveSetting("xrayTemplateConfig", "{ not valid json"); err != nil { + t.Fatalf("seed template: %v", err) + } + t.Cleanup(func() { isManuallyStopped.Store(false) }) + + isManuallyStopped.Store(true) + _ = (&XrayService{}).RestartXray(false) + + if !isManuallyStopped.Load() { + t.Fatal("a non-forced restart cleared a deliberate manual stop and would revive xray") + } +}