Node autocleanup
All checks were successful
release-tag / release-image (push) Successful in 1m32s

This commit is contained in:
2025-09-29 21:19:07 +02:00
parent aaa95410e4
commit 36d1c1512a
2 changed files with 90 additions and 1 deletions

View File

@@ -22,6 +22,14 @@ import (
"git.send.nrw/sendnrw/decent-webui/internal/mesh"
)
func parseDuration(s string, def time.Duration) time.Duration {
d, err := time.ParseDuration(strings.TrimSpace(s))
if err != nil || d <= 0 {
return def
}
return d
}
/*** Config ***/
func loadConfig() AppConfig {
// HTTP
@@ -44,6 +52,9 @@ func loadConfig() AppConfig {
DiscoveryAddress: getenvDefault("MESH_DISCOVERY_ADDR", "239.8.8.8:9898"),
}
m.PeerTTL = parseDuration(os.Getenv("MESH_PEER_TTL"), 2*time.Minute)
m.PruneInterval = parseDuration(os.Getenv("MESH_PRUNE_INTERVAL"), 30*time.Second)
// Wenn keine AdvertURL gesetzt ist, versuche eine sinnvolle Herleitung:
if strings.TrimSpace(m.AdvertURL) == "" {
m.AdvertURL = inferAdvertURL(m.BindAddr)
@@ -440,6 +451,10 @@ func main() {
if err != nil {
log.Fatalf("mesh init: %v", err)
}
// Hintergrund-Pruner starten
mnode.StartPeerPruner()
go func() {
log.Printf("[mesh] listening on %s advertise %s seeds=%v discovery=%v",
cfg.Mesh.BindAddr, cfg.Mesh.AdvertURL, cfg.Mesh.Seeds, cfg.Mesh.EnableDiscovery)

View File

@@ -30,7 +30,9 @@ type Config struct {
Seeds []string // other peers' mesh base URLs
ClusterSecret string // HMAC key
EnableDiscovery bool
DiscoveryAddress string // "239.8.8.8:9898"
DiscoveryAddress string // "239.8.8.8:9898"
PeerTTL time.Duration // wie lange darf ein Peer inaktiv sein (Default siehe unten)
PruneInterval time.Duration // wie oft wird gepruned
}
type Peer struct {
@@ -72,6 +74,78 @@ type Node struct {
wg sync.WaitGroup
}
// RemovePeer löscht einen Peer aus der Peer-Tabelle. Seeds werden standardmäßig nicht entfernt.
func (n *Node) RemovePeer(url string) bool {
n.mu.Lock()
defer n.mu.Unlock()
if url == "" || url == n.self.URL {
return false
}
// Seeds schützen
if n.isSeed(url) {
return false
}
if _, ok := n.peers[url]; ok {
delete(n.peers, url)
return true
}
return false
}
// PruneNow entfernt alle Peers, deren LastSeen vor cutoff liegt (Seeds bleiben).
func (n *Node) PruneNow(cutoff time.Time) int {
n.mu.Lock()
defer n.mu.Unlock()
removed := 0
for url, p := range n.peers {
if url == n.self.URL || n.isSeed(url) {
continue
}
if p.LastSeen.IsZero() || p.LastSeen.Before(cutoff) {
delete(n.peers, url)
removed++
}
}
return removed
}
// StartPeerPruner startet den Hintergrundjob (stoppt automatisch bei n.stop).
func (n *Node) StartPeerPruner() {
go n.loopPrunePeers()
}
func (n *Node) loopPrunePeers() {
ttl := n.cfg.PeerTTL
if ttl <= 0 {
ttl = 2 * time.Minute
}
interval := n.cfg.PruneInterval
if interval <= 0 {
interval = 30 * time.Second
}
t := time.NewTicker(interval)
defer t.Stop()
for {
select {
case <-n.stop:
return
case <-t.C:
cutoff := time.Now().Add(-ttl)
_ = n.PruneNow(cutoff)
}
}
}
// helper: ist url ein Seed?
func (n *Node) isSeed(url string) bool {
for _, s := range n.cfg.Seeds {
if strings.TrimSpace(s) == strings.TrimSpace(url) {
return true
}
}
return false
}
func New(cfg Config, cbs Callbacks) (*Node, error) {
if cfg.BindAddr == "" || cfg.AdvertURL == "" {
return nil, errors.New("mesh: BindAddr and AdvertURL required")