app: wire checker retention janitor and user gate

Construct the retention janitor and the user gate alongside the
checker scheduler. Three new options drive their behaviour:

  --checker-retention-days        (default 365)
  --checker-janitor-interval      (default 6h)
  --checker-inactivity-pause-days (default 90)

The janitor starts immediately on App.Start and is shut down on
App.Stop. The user gate is installed on the scheduler with the same
storage-backed user resolver, so paused users and users that haven't
logged in for the configured horizon stop being checked until they
come back.
This commit is contained in:
nemunaire 2026-04-08 11:54:43 +07:00
commit 5cbe3f580f
3 changed files with 43 additions and 0 deletions

View file

@ -76,6 +76,7 @@ type Usecases struct {
checkerPlanUC *checkerUC.CheckPlanUsecase
checkerStatusUC *checkerUC.CheckStatusUsecase
checkerScheduler *checkerUC.Scheduler
checkerJanitor *checkerUC.Janitor
}
type App struct {
@ -273,6 +274,21 @@ func (app *App) initUsecases() {
)
app.usecases.checkerScheduler = checkerUC.NewScheduler(app.usecases.checkerEngine, app.cfg.CheckerMaxConcurrency, app.store, app.store, app.store, app.store)
// Install the user-level gate so paused or long-inactive users do not
// get checked. The same user resolver is reused by the janitor for
// per-user retention overrides.
gater := checkerUC.NewUserGater(app.store, app.cfg.CheckerInactivityPauseDays)
app.usecases.checkerScheduler.SetGate(gater.Allow)
// Retention janitor.
app.usecases.checkerJanitor = checkerUC.NewJanitor(
app.store,
app.store,
app.store,
checkerUC.DefaultRetentionPolicy(app.cfg.CheckerRetentionDays),
app.cfg.CheckerJanitorInterval,
)
// Wire scheduler notifications for incremental queue updates.
domainService.SetSchedulerNotifier(app.usecases.checkerScheduler)
app.usecases.orchestrator.SetSchedulerNotifier(app.usecases.checkerScheduler)
@ -348,6 +364,10 @@ func (app *App) Start() {
app.usecases.checkerScheduler.Start(context.Background())
}
if app.usecases.checkerJanitor != nil {
app.usecases.checkerJanitor.Start(context.Background())
}
log.Printf("Public interface listening on %s\n", app.cfg.Bind)
if err := app.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
@ -365,6 +385,10 @@ func (app *App) Stop() {
app.usecases.checkerScheduler.Stop()
}
if app.usecases.checkerJanitor != nil {
app.usecases.checkerJanitor.Stop()
}
// Close storage
if app.store != nil {
app.store.Close()

View file

@ -25,6 +25,7 @@ import (
"flag"
"fmt"
"runtime"
"time"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/model"
@ -47,6 +48,9 @@ func declareFlags(o *happydns.Options) {
flag.Var(&URL{&o.ExternalAuth}, "external-auth", "Base URL to use for login and registration (use embedded forms if left empty)")
flag.BoolVar(&o.OptOutInsights, "opt-out-insights", false, "Disable the anonymous usage statistics report. If you care about this project and don't participate in discussions, don't opt-out.")
flag.IntVar(&o.CheckerMaxConcurrency, "checker-max-concurrency", runtime.NumCPU(), "Maximum number of checker jobs that can run simultaneously")
flag.IntVar(&o.CheckerRetentionDays, "checker-retention-days", 365, "System-wide default retention horizon for check execution history (overridable per user)")
flag.DurationVar(&o.CheckerJanitorInterval, "checker-janitor-interval", 6*time.Hour, "How often the checker retention janitor runs")
flag.IntVar(&o.CheckerInactivityPauseDays, "checker-inactivity-pause-days", 90, "Pause checks for users that haven't logged in for this many days (0 disables, overridable per user)")
flag.Var(&URL{&o.ListmonkURL}, "newsletter-server-url", "Base URL of the listmonk newsletter server")
flag.IntVar(&o.ListmonkID, "newsletter-id", 1, "Listmonk identifier of the list receiving the new user")

View file

@ -26,6 +26,7 @@ import (
"net/mail"
"net/url"
"path"
"time"
)
// Options stores the configuration of the software.
@ -97,6 +98,20 @@ type Options struct {
// run simultaneously. Defaults to runtime.NumCPU().
CheckerMaxConcurrency int
// CheckerRetentionDays is the system-wide default for how many days of
// check execution history are kept. Per-user UserQuota.RetentionDays
// overrides this value.
CheckerRetentionDays int
// CheckerJanitorInterval is how often the retention janitor runs.
CheckerJanitorInterval time.Duration
// CheckerInactivityPauseDays is the system-wide default number of days
// without login after which the scheduler stops running checks for a
// user. 0 disables inactivity pausing globally; per-user UserQuota
// overrides this value.
CheckerInactivityPauseDays int
// CaptchaProvider selects the captcha provider ("hcaptcha", "recaptchav2", "turnstile", or "").
CaptchaProvider string