package realDynDns import ( "fmt" "log/slog" "time" "realdnydns/pkg/config" "realdnydns/pkg/dnsProvider" "realdnydns/pkg/externalIpProvider" "realdnydns/pkg/notificationProvider" "github.com/go-co-op/gocron" ) type ChangeDetector struct { externalIpProvider externalIpProvider.ExternalIpProvider dnsProvider dnsProvider.DNSProvider notificationProvider notificationProvider.NotificationProvider domains []config.DomainConfig logger *slog.Logger } func New( externalIpProvider externalIpProvider.ExternalIpProvider, dnsProvider dnsProvider.DNSProvider, notificationProvider notificationProvider.NotificationProvider, domains []config.DomainConfig, logger *slog.Logger, ) ChangeDetector { return ChangeDetector{ externalIpProvider: externalIpProvider, dnsProvider: dnsProvider, notificationProvider: notificationProvider, domains: domains, logger: logger, } } func (c *ChangeDetector) RunWithSchedule(checkInterval string) (*gocron.Scheduler, *gocron.Job, error) { s := gocron.NewScheduler(time.UTC) s.SingletonMode() s.CronWithSeconds(checkInterval) job, err := s.DoWithJobDetails(func(job gocron.Job) { numberChanged, err := c.detectAndApplyChanges() if err != nil { panic(err) } fmt.Printf("Number of changes: %d\n", numberChanged) fmt.Println("Next run:", job.NextRun()) }) if err != nil { return nil, nil, err } return s, job, nil } func (c *ChangeDetector) RunOnce() (int, error) { return c.detectAndApplyChanges() } func (c *ChangeDetector) detectAndApplyChanges() (int, error) { c.logger.Info("Detecting and applying changes") externalIp, err := c.externalIpProvider.GetExternalIp() if err != nil { c.logger.Error("Failed to retrieve external IP", slog.String("error", err.Error())) return 0, err } var numberUpdated int for _, domain := range c.domains { for _, subdomain := range domain.Subdomains { c.logger.Info("Checking record", slog.String("tld", domain.TLD), slog.String("subdomain", subdomain), ) currentRecord, err := c.dnsProvider.GetRecord(domain.TLD, subdomain) if err != nil { c.logger.Error("Failed to retrieve record", slog.String("error", err.Error()), slog.String("tld", domain.TLD), slog.String("subdomain", subdomain), ) return numberUpdated, err } if currentRecord.IP != externalIp.String() { c.logger.Info("Record has changed", slog.String("tld", domain.TLD), slog.String("subdomain", subdomain), slog.String("current_ip", currentRecord.IP), slog.String("external_ip", externalIp.String()), ) err = c.notificationProvider.SendNotification( fmt.Sprintf("Update %s.%s", subdomain, domain.TLD), fmt.Sprintf("The IP of %s has changed from %s to %s", domain.TLD, currentRecord.IP, externalIp.String()), ) if err != nil { c.logger.Warn("Failed to send notification", slog.String("error", err.Error()), ) return numberUpdated, err } c.logger.Info("Updating record", slog.String("tld", domain.TLD), slog.String("subdomain", subdomain), slog.String("current_ip", currentRecord.IP), slog.String("external_ip", externalIp.String()), ) _, err = c.dnsProvider.UpdateRecord(domain.TLD, subdomain, externalIp, currentRecord.TTL, currentRecord.Prio, currentRecord.Disabled) if err != nil { c.logger.Error("Failed to update record", slog.String("error", err.Error()), slog.String("tld", domain.TLD), slog.String("subdomain", subdomain), ) return numberUpdated, err } numberUpdated++ } } } c.logger.Info("Run completed", slog.Int("number_of_changes", numberUpdated)) return numberUpdated, nil }