// edgeguard-scheduler runs background jobs that don't belong on the // API request path: // // - ACME cert renewal (every 6h, re-issues anything < 30d to expiry) // // Future jobs (cluster heartbeat, backup, audit-log retention) // hang off the same Tick loop. Stays single-process — no leader // election yet (Phase 3). package main import ( "context" "log/slog" "os" "time" "git.netcell-it.de/projekte/edgeguard-native/internal/database" "git.netcell-it.de/projekte/edgeguard-native/internal/services/acme" "git.netcell-it.de/projekte/edgeguard-native/internal/services/certrenewer" "git.netcell-it.de/projekte/edgeguard-native/internal/services/setup" "git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts" ) var version = "1.0.43" const ( // renewTickInterval — how often we re-evaluate expiring certs. // 6h is enough: LE renewal window is 30 days; missing one tick // makes no difference. Hourly would log too much. renewTickInterval = 6 * time.Hour // certDir matches handlers.NewTLSCertsHandler default — HAProxy // reads from this directory. certDir = "/etc/edgeguard/tls" ) func main() { slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo}))) slog.Info("edgeguard-scheduler starting", "version", version) ctx := context.Background() pool, err := database.Open(ctx, database.ConnStringFromEnv()) if err != nil { slog.Error("scheduler: DB open failed — sleeping forever", "error", err) select {} } defer pool.Close() tlsRepo := tlscerts.New(pool) setupStore := setup.NewStore(setup.DefaultDir) st, _ := setupStore.Load() var renewer *certrenewer.Service if st != nil && st.ACMEEmail != "" { issuer := acme.New(st.ACMEEmail) renewer = certrenewer.New(tlsRepo, issuer, certDir, 30*24*time.Hour) slog.Info("scheduler: ACME renewer enabled", "email", st.ACMEEmail, "tick", renewTickInterval, "threshold", "30d") } else { slog.Warn("scheduler: setup.acme_email empty — ACME renewal disabled until setup wizard ran") } if renewer != nil { runRenewer(ctx, renewer) } tick := time.NewTicker(renewTickInterval) defer tick.Stop() for range tick.C { if renewer != nil { runRenewer(ctx, renewer) } } } func runRenewer(ctx context.Context, r *certrenewer.Service) { res, err := r.Run(ctx) if err != nil { slog.Error("scheduler: renewer run failed", "error", err) return } slog.Info("scheduler: renewer pass complete", "checked", res.Checked, "renewed", res.Renewed, "failed", res.Failed, "skipped", res.Skipped) }