package main import ( "context" "errors" "log/slog" "net/http" "os" "os/signal" "syscall" "time" "github.com/spf13/cobra" "git.tswf.io/infra/go-synapse-backupper/pkg/adapters/config" "git.tswf.io/infra/go-synapse-backupper/pkg/adapters/healthz" "git.tswf.io/infra/go-synapse-backupper/pkg/adapters/scheduler/cron" "git.tswf.io/infra/go-synapse-backupper/pkg/domain" "git.tswf.io/infra/go-synapse-backupper/pkg/domain/backup" ) var ( newScheduler = func( expr string, job func(), ) ( domain.Scheduler, error, ) { return cron.NewCronScheduler(expr, job) } newHealthz = func(port int) (healthz.Server, error) { return healthz.New(port) } runOnceFunc = backup.RunOnce osExitFunc = func(code int) { os.Exit(code) } ) var runCmd = &cobra.Command{ Use: "run", Short: "Start the backup scheduler", RunE: run, } func init() { config.RegisterFlags(runCmd) } func run(cmd *cobra.Command, args []string) error { cfg, err := config.Load(cmd) if err != nil { return err } return runWithConfig(cmd.Context(), cfg) } func runWithConfig( ctx context.Context, cfg *config.Config, ) error { job := func() { jobCtx := context.Background() if err := runOnceFunc(jobCtx, cfg); err != nil { slog.Error("backup job failed", "error", err) } } scheduler, err := newScheduler(cfg.Backup.Cron, job) if err != nil { return err } healthzServer, err := newHealthz(cfg.Healthz.Port) if err != nil { slog.Error("failed to bind healthz server", "error", err) osExitFunc(1) return nil } go func() { if err := healthzServer.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) { slog.Error("healthz server error", "error", err) } }() scheduler.Start() sigCtx, stop := signal.NotifyContext(ctx, syscall.SIGTERM, syscall.SIGINT) defer stop() <-sigCtx.Done() shutdownTimeout := cfg.ShutdownTimeout if shutdownTimeout <= 0 { shutdownTimeout = 30 * time.Second } shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) defer cancel() _ = healthzServer.Stop(shutdownCtx) stoppedCtx := scheduler.Stop() select { case <-stoppedCtx.Done(): slog.Info("graceful shutdown complete") case <-time.After(shutdownTimeout): slog.Warn("forcing exit, backup job still running") osExitFunc(0) } return nil }