From bedbe04bf1b42e1d774d6d257ab2b40d8258f022 Mon Sep 17 00:00:00 2001 From: n0ctal <4c866w5fn9@privaterelay.appleid.com> Date: Sat, 20 Jun 2026 03:38:00 +0500 Subject: [PATCH] fix(web): recover panicking cron jobs instead of crashing the panel (#5363) The scheduler was created without a panic recovery wrapper, so a panic in any scheduled job (traffic write, IP check, etc.) propagated up and could take down the whole panel process. Wrap jobs with cron.Recover so a panic is logged and the scheduler keeps running. --- internal/web/web.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/internal/web/web.go b/internal/web/web.go index 6ecc972a0..b77683217 100644 --- a/internal/web/web.go +++ b/internal/web/web.go @@ -53,6 +53,12 @@ var distFS embed.FS var startTime = time.Now() +// cronPanicLogger adapts the package logger to cron's Printf-style logger so a +// panicking scheduled job is recovered and logged instead of crashing the panel. +type cronPanicLogger struct{} + +func (cronPanicLogger) Printf(format string, args ...any) { logger.Errorf(format, args...) } + // wrapDistFS adapts the embedded `dist/` directory so it can be mounted // as the panel's `/assets/` static route. Vite emits its bundled JS/CSS // under `dist/assets/`; serving the FS rooted at `dist/assets` makes @@ -435,7 +441,9 @@ func (s *Server) start(restartXray bool, startTgBot bool) (err error) { } service.StartTrafficWriter() - s.cron = cron.New(cron.WithLocation(loc), cron.WithSeconds()) + // cron.Recover wraps every job so a panic is logged and the scheduler keeps + // running, instead of the panic taking down the whole panel process. + s.cron = cron.New(cron.WithLocation(loc), cron.WithSeconds(), cron.WithChain(cron.Recover(cron.PrintfLogger(cronPanicLogger{})))) s.cron.Start() // Wire the inbound-runtime manager once so InboundService can route