Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 53 additions & 5 deletions cmd/bot/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,47 @@ import (
"github.com/fus1ond/vpn_bot/internal/remnawave"
)

// runWithRestart запускает fn в цикле: при панике логирует, ждёт backoff и перезапускает.
// Если ctx отменён — выходит без retry.
func runWithRestart(ctx context.Context, name string, fn func()) {
const maxBackoff = 5 * time.Minute
backoff := 5 * time.Second

for {
panicked := func() (didPanic bool) {
defer func() {
if r := recover(); r != nil {
slog.Error("goroutine panicked, will restart", "goroutine", name, "recover", r, "backoff", backoff)
didPanic = true
}
}()
fn()
return false
}()

if !panicked {
// fn вернулась штатно — проверяем, был ли это штатный shutdown
if ctx.Err() != nil {
return
}
// fn завершилась без паники и без отмены ctx — неожиданный выход, перезапускаем
slog.Warn("goroutine exited unexpectedly, will restart", "goroutine", name, "backoff", backoff)
}

select {
case <-ctx.Done():
return
case <-time.After(backoff):
}

// Экспоненциальный backoff до maxBackoff
backoff *= 2
if backoff > maxBackoff {
backoff = maxBackoff
}
}
}

func main() {
// Настройка логирования
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Expand Down Expand Up @@ -75,17 +116,18 @@ func main() {
<-sigChan
slog.Info("Shutdown signal received, stopping bot...")
cancel()
telegramBot.Stop()
}()

// Запуск callback-сервера (если Platega настроена)
if cfg.PlategaMerchantID != "" && cfg.PlategaSecret != "" {
callbackServer := callback.NewServer(cfg.CallbackPort, cfg.PlategaMerchantID, cfg.PlategaSecret, telegramBot.PaymentCallbackHandler())

go func() {
go runWithRestart(ctx, "callback-server", func() {
if err := callbackServer.Start(); err != nil && err != http.ErrServerClosed {
slog.Error("Callback server error", "error", err)
}
}()
})

go func() {
<-ctx.Done()
Expand All @@ -100,14 +142,20 @@ func main() {
}

// Запуск фоновой синхронизации targets.json для мониторинга нод
go monitoring.StartSyncLoop(ctx, remnawaveClient, cfg.SDConfigsPath)
go runWithRestart(ctx, "sync-loop", func() {
monitoring.StartSyncLoop(ctx, remnawaveClient, cfg.SDConfigsPath)
})

// Запуск алертера (проверка состояния нод раз в минуту)
alertSender := bot.NewBotAlertSender(telegramBot)
go monitoring.StartAlerter(ctx, telegramBot.MetricsClient(), cfg.SDConfigsPath, alertSender)
go runWithRestart(ctx, "alerter", func() {
monitoring.StartAlerter(ctx, telegramBot.MetricsClient(), cfg.SDConfigsPath, alertSender)
})

// Запуск ежедневного scheduler подписок (уведомления и автокик).
go telegramBot.StartScheduler(ctx)
go runWithRestart(ctx, "scheduler", func() {
telegramBot.StartScheduler(ctx)
})

// Запуск бота (блокирующий вызов)
telegramBot.Run()
Expand Down
Loading
Loading