diff --git a/internal/updater/updater.go b/internal/updater/updater.go index ece7c81..38ad63d 100644 --- a/internal/updater/updater.go +++ b/internal/updater/updater.go @@ -1,4 +1,4 @@ -// Package updater runs one dynamic DNS update pass across all configured entries. +// Package updater runs dynamic DNS update passes across all configured entries. package updater import ( @@ -11,11 +11,28 @@ import ( "git.aehoo.net/alphard/he-dydns/internal/network" ) -// Run discovers the current address(es) and updates every configured entry. -// It returns an error only when address discovery itself fails outright; -// per-entry update failures are logged and aggregated but do not stop other -// entries from being attempted. -func Run(cfg *config.Config) error { +type cacheKey struct { + hostname string + recordType config.RecordType +} + +// Updater runs update passes and remembers the last address successfully +// published for each entry, so unchanged addresses aren't re-sent upstream. +type Updater struct { + last map[cacheKey]net.IP +} + +// New returns an Updater with no cached state; its first Run always +// publishes every entry that has a usable address. +func New() *Updater { + return &Updater{last: make(map[cacheKey]net.IP)} +} + +// Run discovers the current address(es) and updates entries whose address +// changed since the last successful update. It returns an error only when +// address discovery itself fails outright; per-entry update failures are +// logged and aggregated but do not stop other entries from being attempted. +func (u *Updater) Run(cfg *config.Config) error { addrs, err := network.Discover(cfg.Interface) if err != nil { return fmt.Errorf("discovering address: %w", err) @@ -48,12 +65,20 @@ func Run(cfg *config.Config) error { continue } + key := cacheKey{hostname: entry.Hostname, recordType: entry.RecordType} + if last, ok := u.last[key]; ok && last.Equal(ip) { + log.Printf("%s (%s) unchanged at %s; skipping update", entry.Hostname, entry.RecordType, ip) + continue + } + if err := hedns.Update(entry.Hostname, entry.Token, ip); err != nil { log.Printf("error: %v", err) failures++ - } else { - log.Printf("updated %s (%s) -> %s", entry.Hostname, entry.RecordType, ip) + continue } + + log.Printf("updated %s (%s) -> %s", entry.Hostname, entry.RecordType, ip) + u.last[key] = ip } if failures > 0 { diff --git a/main.go b/main.go index b8e686c..b8622a5 100644 --- a/main.go +++ b/main.go @@ -61,7 +61,9 @@ func main() { log.Fatalf("fatal: %v", err) } - runErr := updater.Run(cfg) + u := updater.New() + + runErr := u.Run(cfg) if runErr != nil { log.Printf("update pass completed with errors: %v", runErr) } @@ -85,7 +87,7 @@ func main() { for { select { case <-ticker.C: - if err := updater.Run(cfg); err != nil { + if err := u.Run(cfg); err != nil { log.Printf("update pass completed with errors: %v", err) } case sig := <-sigCh: