From da01893aa3e1339fd3524a09247a67a51b058d80 Mon Sep 17 00:00:00 2001 From: cclohmar Date: Sat, 25 Jul 2026 18:22:34 +0000 Subject: [PATCH] feat: reload also scrubs expired leases from lease file before reloading dnsmasq --- server.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/server.go b/server.go index d473a69..ff3262f 100644 --- a/server.go +++ b/server.go @@ -197,6 +197,11 @@ func unpinLease(mac string) error { // reloadDnsmasq runs the reload command (e.g. sudo systemctl reload dnsmasq). func reloadDnsmasq() error { + // Scrub expired leases before reloading so the UI stays clean. + if err := cleanLeaseFile(); err != nil { + log.Printf("WARNING: failed to clean lease file: %v", err) + // Continue — reload is still useful even if cleaning fails. + } cmd := exec.Command(reloadCmd, reloadArgs...) output, err := cmd.CombinedOutput() if err != nil { @@ -205,6 +210,52 @@ func reloadDnsmasq() error { return nil } +// cleanLeaseFile reads the lease file, removes expired entries, writes back atomically. +func cleanLeaseFile() error { + leases, err := parseLeases() + if err != nil { + return err + } + + now := time.Now() + var kept []string + for _, l := range leases { + if l.Expiry.After(now) { + kept = append(kept, fmt.Sprintf("%d %s %s %s %s", + l.Expiry.Unix(), l.MAC, l.IP, l.Hostname, l.ClientID)) + } + } + + // If nothing changed, skip the write. + if len(kept) == len(leases) { + return nil + } + + tmpFile := leasesFile + ".tmp" + f, err := os.Create(tmpFile) + if err != nil { + return fmt.Errorf("create temp lease file: %w", err) + } + writeErr := func() error { + defer f.Close() + for _, line := range kept { + if _, err := fmt.Fprintln(f, line); err != nil { + return err + } + } + return nil + }() + if writeErr != nil { + os.Remove(tmpFile) + return writeErr + } + if err := os.Rename(tmpFile, leasesFile); err != nil { + os.Remove(tmpFile) + return fmt.Errorf("rename lease file: %w", err) + } + return nil +} + // getAllLeases returns merged active (non-expired) and pinned lease info. func getAllLeases() ([]LeaseEntry, error) { activeLeases, err := parseLeases()