Adds Pi-hole-style DNS filtering using dnsmasq's native address=/ directives, which block domains and all subdomains. Users subscribe to blocklists by URL or upload files, with suggested lists from Hagezi, Steven Black, and OISD. Background runner refreshes lists on a configurable interval (default 24h).
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("dns-filter: runner started", "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("dns-filter: runner stopped")
|
|
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("dns-filter: update failed", "error", err)
|
|
}
|
|
|
|
if _, err := r.manager.Compile(); err != nil {
|
|
slog.Error("dns-filter: compile failed", "error", err)
|
|
return
|
|
}
|
|
|
|
if r.onUpdate != nil {
|
|
r.onUpdate()
|
|
}
|
|
}
|