// dynamic domain no-name system service // // a simple dynamic dns record updating utility which fetches your // ip address from a source, and lets a provider know of the fresh // address. package main import ( "flag" "fmt" "log/slog" "os" "git.rnrd.eu/ddnnss/provider" "git.rnrd.eu/ddnnss/source" ) const version = "1.0.0" func main() { configPath := flag.String("config", "/etc/ddnnss.json", "config file") showVersion := flag.Bool("version", false, "print the version and exit") flag.Parse() if *showVersion { fmt.Println("ddnnss", version) return } slog.Info("ddnnss!", slog.String("version", version)) cfg, err := LoadConfig(*configPath) if err != nil { slog.Error("could not load config", slog.String("error", err.Error())) os.Exit(1) } providers := map[string]provider.Provider{} sources := map[string]source.Source{} slog.Info("starting new cycle", slog.Int("records", len(cfg.Records))) failed := false for _, r := range cfg.Records { fqdn := r.Name + "." + r.Zone log := slog.With("record", fqdn, "type", r.Type) log.Info( "ensuring record", slog.String("provider", r.Provider), slog.String("source", r.Source), ) p, ok := providers[r.Provider] if !ok { p, err = provider.New(log, cfg.Providers[r.Provider]) if err != nil { log.Error("provider setup failed", "provider", r.Provider, "error", err) failed = true continue } providers[r.Provider] = p } s, ok := sources[r.Source] if !ok { s, err = source.New(log, cfg.Sources[r.Source]) if err != nil { log.Error("source setup failed", "source", r.Source, "error", err) failed = true continue } sources[r.Source] = s } family := source.IPv4 if r.Type == provider.TypeAAAA { family = source.IPv6 } if !s.Supports(family) { log.Error("source cannot provide the needed address family", "source", r.Source, "family", family) failed = true continue } addr, err := s.Lookup(family) if err != nil { log.Error("address lookup failed", "source", r.Source, "error", err) failed = true continue } prev, changed, err := p.Ensure(r.Zone, r.Name, r.Type, addr) switch { case err != nil: log.Error("update failed", "error", err) failed = true case changed && prev == "": log.Info("record created", "addr", addr) case changed: log.Info("record updated", "from", prev, "to", addr) default: log.Info("record unchanged", "addr", addr) } } slog.Info( "cycle finished", slog.Int("records", len(cfg.Records)), slog.Bool("failed", failed), ) if failed { slog.Error("not all records were ensured successfully") os.Exit(1) } }