package wgtunnel import ( "context" "log/slog" "sync" "time" "gitea.dooplex.hu/admin/felhom-agent/internal/hub" ) // Loop drives the Manager on its own cadence (the lanresolver shape) AND consumes fetched // desired-state via the desired.Syncer raw-consumer seam. It distinguishes "no desired data // seen yet" (fetched=false — never a teardown signal) from "desired-state present without the // wireguard block" (revocation). type Loop struct { mgr *Manager interval time.Duration logger *slog.Logger mu sync.Mutex fetched bool block *hub.WireWireguard nudge chan struct{} } // NewLoop builds the loop. interval defaults to 60s. func NewLoop(mgr *Manager, interval time.Duration, logger *slog.Logger) *Loop { if interval <= 0 { interval = 60 * time.Second } if logger == nil { logger = slog.Default() } return &Loop{mgr: mgr, interval: interval, logger: logger, nudge: make(chan struct{}, 1)} } // OnDesiredState implements desired.RawConsumer: store the latest wireguard block (or its // absence) and nudge the loop. Non-blocking and panic-free by construction. func (l *Loop) OnDesiredState(_ context.Context, resp *hub.DesiredStateResponse) { if resp == nil { return } l.mu.Lock() l.fetched = true l.block = resp.DesiredState.Wireguard l.mu.Unlock() select { case l.nudge <- struct{}{}: default: } } func (l *Loop) snapshot() (bool, *hub.WireWireguard) { l.mu.Lock() defer l.mu.Unlock() return l.fetched, l.block } // Run applies immediately, then on every tick or desired-state nudge, until ctx is cancelled. func (l *Loop) Run(ctx context.Context) error { fetched, block := l.snapshot() l.mgr.Apply(ctx, fetched, block) t := time.NewTicker(l.interval) defer t.Stop() for { select { case <-ctx.Done(): return ctx.Err() case <-t.C: case <-l.nudge: } fetched, block = l.snapshot() l.mgr.Apply(ctx, fetched, block) l.mgr.Watchdog(ctx, fetched, block) // re-resolve + recover on endpoint re-IP (doc 06 §4.2) } } // WireguardStatus implements the hub collector's WireguardReporter seam. func (l *Loop) WireguardStatus(ctx context.Context) *hub.WireguardStatus { return l.mgr.Status(ctx) }