// Package updater runs dynamic DNS update passes across all configured entries. package updater import ( "fmt" "log" "net" "git.aehoo.net/alphard/he-dydns/internal/config" "git.aehoo.net/alphard/he-dydns/internal/hedns" "git.aehoo.net/alphard/he-dydns/internal/network" ) 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) } if addrs.Empty() { log.Printf("no address found (interface=%q); skipping update", cfg.Interface) return nil } if addrs.IPv4 != nil { log.Printf("discovered IPv4 address %s", addrs.IPv4) } if addrs.IPv6 != nil { log.Printf("discovered IPv6 address %s", addrs.IPv6) } var failures int for _, entry := range cfg.Entries { var ip net.IP switch entry.RecordType { case config.RecordTypeAAAA: ip = addrs.IPv6 default: ip = addrs.IPv4 } if ip == nil { log.Printf("no %s address available for %s; skipping", entry.RecordType, entry.Hostname) 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++ continue } log.Printf("updated %s (%s) -> %s", entry.Hostname, entry.RecordType, ip) u.last[key] = ip } if failures > 0 { return fmt.Errorf("%d update(s) failed", failures) } return nil }