- JSON tags: fix snake_case to camelCase in dnsmasq (configFile, domainsConfigured, lastRestart), crowdsec Machine (lastPush, lastHeartbeat), network (primaryIP, primaryInterface). cscli raw parsing structs keep snake_case to match CLI output. - Error wrapping: fix %v to %w in enableAuthelia for proper error chain preservation - Naming: rename dnsmasq.ConfigGenerator to dnsmasq.Manager (matches all other packages), rename ServiceStatus to Status in dnsmasq and haproxy (matches authelia, crowdsec, etc.) - Logging: standardize all slog calls to use "component" key instead of message prefixes. Affects reconcile, dnsfilter, ddns — now consistent with dnsmasq, haproxy, nftables, sse.
116 lines
2.3 KiB
Go
116 lines
2.3 KiB
Go
package dnsfilter
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Runner manages the background blocklist update goroutine.
|
|
type Runner struct {
|
|
mu sync.RWMutex
|
|
manager *Manager
|
|
cancel context.CancelFunc
|
|
trigger chan struct{}
|
|
running bool
|
|
onUpdate func() // called after lists are downloaded and compiled
|
|
}
|
|
|
|
// NewRunner creates a new DNS filter update runner.
|
|
func NewRunner(manager *Manager, onUpdate func()) *Runner {
|
|
return &Runner{
|
|
manager: manager,
|
|
trigger: make(chan struct{}, 1),
|
|
onUpdate: onUpdate,
|
|
}
|
|
}
|
|
|
|
// Start launches the background update goroutine.
|
|
// Safe to call multiple times — cancels the previous run first.
|
|
func (r *Runner) Start(parentCtx context.Context, intervalHours int) {
|
|
r.mu.Lock()
|
|
if r.cancel != nil {
|
|
r.cancel()
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(parentCtx)
|
|
r.cancel = cancel
|
|
r.running = true
|
|
r.mu.Unlock()
|
|
|
|
if intervalHours <= 0 {
|
|
intervalHours = 24
|
|
}
|
|
|
|
go r.run(ctx, intervalHours)
|
|
}
|
|
|
|
// Stop cancels the running goroutine.
|
|
func (r *Runner) Stop() {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if r.cancel != nil {
|
|
r.cancel()
|
|
r.cancel = nil
|
|
}
|
|
r.running = false
|
|
}
|
|
|
|
// IsRunning returns whether the runner is active.
|
|
func (r *Runner) IsRunning() bool {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
return r.running
|
|
}
|
|
|
|
// Trigger requests an immediate update (non-blocking).
|
|
func (r *Runner) Trigger() {
|
|
select {
|
|
case r.trigger <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
|
|
func (r *Runner) run(ctx context.Context, intervalHours int) {
|
|
interval := time.Duration(intervalHours) * time.Hour
|
|
|
|
slog.Info("runner started", "component", "dns-filter", "interval", interval)
|
|
|
|
// Immediate update on start
|
|
r.updateAndCompile(ctx)
|
|
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
r.mu.Lock()
|
|
r.running = false
|
|
r.mu.Unlock()
|
|
slog.Info("runner stopped", "component", "dns-filter")
|
|
return
|
|
case <-ticker.C:
|
|
r.updateAndCompile(ctx)
|
|
case <-r.trigger:
|
|
r.updateAndCompile(ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *Runner) updateAndCompile(ctx context.Context) {
|
|
if err := r.manager.UpdateLists(ctx); err != nil {
|
|
slog.Error("update failed", "component", "dns-filter", "error", err)
|
|
}
|
|
|
|
if _, err := r.manager.Compile(); err != nil {
|
|
slog.Error("compile failed", "component", "dns-filter", "error", err)
|
|
return
|
|
}
|
|
|
|
if r.onUpdate != nil {
|
|
r.onUpdate()
|
|
}
|
|
}
|