feat: add node stale/delete config

closes: #93
This commit is contained in:
Enot (ded) Skelly committed 2026-07-24 13:09:51 -07:00
1 parent 3f2506fc8b
commit 99d7eb0bab
17 files changed
+332 -22

No files matched your search

+5
View File
@@ -48,6 +48,11 @@ type NodeSummary struct {
DefaultScope *string `json:"defaultScope,omitempty"` // most recently matched transport scope name e.g. "#bc"
KnownNeighborCount int64 `json:"knownNeighborCount"`
NeighborIDs []uuid.UUID `json:"neighborIds,omitempty"` // only populated when the list request opts in; see ?neighbors=true
// Stale is true when the node hasn't been seen (last_seen) within the configured
// staleness window (default 24h; internal/config.ResolvedConfig.NodeStaleThreshold).
// Applies to every node type, unlike ClockDriftSeconds/ClockOutOfSync on Node, which
// are repeater/room-server only.
Stale bool `json:"stale"`
}
// Node is the full node representation including firmware capability flags,
+5 -2
View File
@@ -44,8 +44,8 @@ func ViewRefreshTask(store *db.Store, interval time.Duration) Task {
}
}
// CleanupTask returns a Task that prunes old telemetry and packet rows.
func CleanupTask(store *db.Store, telemetryRetention, packetRetention, interval time.Duration) Task {
// CleanupTask returns a Task that prunes old telemetry, packet, and node rows.
func CleanupTask(store *db.Store, telemetryRetention, packetRetention, nodeDeleteAfter, interval time.Duration) Task {
return Task{
Name: "cleanup",
Interval: interval,
@@ -65,6 +65,9 @@ func CleanupTask(store *db.Store, telemetryRetention, packetRetention, interval
if err := store.DeleteOldTraceIATAs(ctx, cutoff); err != nil {
return err
}
if err := store.DeleteOldNodes(ctx, time.Now().Add(-nodeDeleteAfter)); err != nil {
return err
}
return nil
},
}
+24 -1
View File
@@ -48,6 +48,11 @@ type ResolvedConfig struct {
// node's ADVERT timestamp, above which the node API reports clockOutOfSync=true for
// that node. Only meaningful for repeaters/room servers (nodeType 2/3).
ClockDriftThreshold time.Duration
// NodeStaleThreshold and NodeDeleteAfter mirror ClockDriftThreshold's "0 means unset,
// resolve to a default" pattern -- see NodesConfig.
NodeStaleThreshold time.Duration
NodeDeleteAfter time.Duration
}
// PresenceConfig controls coalescing of presence bookkeeping writes
@@ -178,6 +183,13 @@ type NodesConfig struct {
// repeater/room server's ADVERT timestamp, above which the node API reports
// clockOutOfSync=true for that node. Defaults to 5m if not set.
ClockDriftThreshold duration `yaml:"clock_drift_threshold"`
// StaleThreshold is how long since a node's last_seen before the node API reports
// stale=true for it. Defaults to 24h if not set.
StaleThreshold duration `yaml:"stale_threshold"`
// DeleteAfter is how long since a node's last_seen before the cleanup job deletes the
// node entirely. Defaults to the same 30-day default as packets.retention if not set --
// independently configurable from it, just the same starting point.
DeleteAfter duration `yaml:"delete_after"`
}
// duration is a wrapper around time.Duration that supports YAML unmarshalling
@@ -299,6 +311,8 @@ func Resolve(cfg *Config) ResolvedConfig {
PresencePacketTTL: cfg.Presence.PacketTTL.Duration,
ClockDriftThreshold: cfg.Nodes.ClockDriftThreshold.Duration,
NodeStaleThreshold: cfg.Nodes.StaleThreshold.Duration,
NodeDeleteAfter: cfg.Nodes.DeleteAfter.Duration,
}
if r.TelemetryResolution == 0 {
r.TelemetryResolution = time.Hour
@@ -330,14 +344,23 @@ func Resolve(cfg *Config) ResolvedConfig {
if r.ClockDriftThreshold == 0 {
r.ClockDriftThreshold = 5 * time.Minute
}
if r.NodeStaleThreshold == 0 {
r.NodeStaleThreshold = 24 * time.Hour
}
if r.NodeDeleteAfter == 0 {
// Same default as packets.retention (30 days) -- independently configurable, just
// the same starting point, not tied to whatever PacketRetention resolves to.
r.NodeDeleteAfter = 30 * 24 * time.Hour
}
return r
}
func (r ResolvedConfig) String() string {
return fmt.Sprintf(
"telemetryResolution=%s telemetryRetention=%s packetRetention=%s maxConnsPerIP=%d viewRefresh=%s reconfirm=%s cleanup=%s presenceFlush=%s presencePacketTTL=%s clockDriftThreshold=%s",
"telemetryResolution=%s telemetryRetention=%s packetRetention=%s maxConnsPerIP=%d viewRefresh=%s reconfirm=%s cleanup=%s presenceFlush=%s presencePacketTTL=%s clockDriftThreshold=%s nodeStaleThreshold=%s nodeDeleteAfter=%s",
r.TelemetryResolution, r.TelemetryRetention, r.PacketRetention,
r.MaxConnsPerIP, r.ViewRefreshInterval, r.ReconfirmInterval, r.CleanupInterval,
r.PresenceFlushInterval, r.PresencePacketTTL, r.ClockDriftThreshold,
r.NodeStaleThreshold, r.NodeDeleteAfter,
)
}
+6
View File
@@ -98,6 +98,12 @@ func TestResolve_Defaults(t *testing.T) {
if r.ClockDriftThreshold != 5*time.Minute {
t.Errorf("expected ClockDriftThreshold 5m, got %v", r.ClockDriftThreshold)
}
if r.NodeStaleThreshold != 24*time.Hour {
t.Errorf("expected NodeStaleThreshold 24h, got %v", r.NodeStaleThreshold)
}
if r.NodeDeleteAfter != 30*24*time.Hour {
t.Errorf("expected NodeDeleteAfter 720h (same default as PacketRetention), got %v", r.NodeDeleteAfter)
}
}
func TestResolve_ExplicitValues(t *testing.T) {