fb248961c6
Manager: one-shot registration (marker gate; backoff cap 15m), adopt-lost-marker, re-key-on-mismatch, REVOKED-STAYS-REVOKED teardown (marker kept, zero execs on later ticks), no-teardown-on-absent-data, hash-gated apply (zero execs steady state), restart-not-reload on conf change, self-heal enable. Status stanza with latest-handshakes-ONLY wg read. Collector WireguardReporter seam. desired.Syncer AddConsumer fan-out with panic containment. Red-proofs a/b/d run + reverted; no-key-material-in-logs asserted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
83 lines
2.0 KiB
Go
83 lines
2.0 KiB
Go
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)
|
|
}
|
|
}
|
|
|
|
// WireguardStatus implements the hub collector's WireguardReporter seam.
|
|
func (l *Loop) WireguardStatus(ctx context.Context) *hub.WireguardStatus {
|
|
return l.mgr.Status(ctx)
|
|
}
|