package main import ( "context" "fmt" "github.com/libdns/libdns" "log" "net" "os" "strings" "time" "github.com/libdns/inwx" "github.com/sethvargo/go-envconfig" ) type Config struct { Username string `env:"DYNDNS_USERNAME, required"` Password string `env:"DYNDNS_PASSWORD, required"` Zone string `env:"DYNDNS_ZONE, required"` Record string `env:"DYNDNS_RECORD, required"` Nameservers string `env:"DYNDNS_NAMESERVERS, required"` } func main() { ctx := context.Background() var cfg Config if err := envconfig.Process(ctx, &cfg); err != nil { log.Fatal("Error reading config: ", err) } ifaces, err := net.Interfaces() if err != nil { log.Fatal("Error getting interfaces: ", err) } var ownIp string for _, iface := range ifaces { addrs, err := iface.Addrs() if err != nil { log.Fatalf("Error getting addresses for %v: %s", iface, err) } for _, addr := range addrs { if ipnet, ok := addr.(*net.IPNet); ok && ipnet.IP.To4() == nil && !ipnet.IP.IsPrivate() && !ipnet.IP.IsLinkLocalUnicast() && !ipnet.IP.IsLoopback() && ipnet.IP.IsGlobalUnicast() { ownIp = ipnet.IP.String() } } } log.Print("Detected own IP: ", ownIp) resolv := &net.Resolver{ PreferGo: true, Dial: func(ctx context.Context, network, address string) (net.Conn, error) { d := net.Dialer{ Timeout: time.Duration(10) * time.Second, } var conn net.Conn var err error for _, server := range strings.Split(cfg.Nameservers, ",") { conn, err = d.DialContext(ctx, network, fmt.Sprintf("%s:53", server)) if err == nil { return conn, err } else { log.Printf("Error resolving with NS \"%s\"", server) } } return conn, err }, } target := fmt.Sprintf("%s.%s", cfg.Record, cfg.Zone) resolvIps, err := resolv.LookupHost(ctx, target) if err != nil { log.Fatalf("Error resolving %s: %v", target, err) } if len(resolvIps) != 1 { log.Fatalf("Detected %d IPs for \"%s\". Don't know what to do", len(resolvIps), target) } resolvIp := resolvIps[0] log.Printf("Resolved IP: %s", resolvIp) forceUpdate := len(os.Args) == 2 && os.Args[1] == "--force" if forceUpdate { log.Println("Detected force update") } if ownIp == resolvIp && !forceUpdate { log.Println("Resolved IP is equal to own IP. Nothing to do.") os.Exit(0) } provider := &inwx.Provider{ Username: cfg.Username, Password: cfg.Password, } records, err := provider.SetRecords(context.Background(), cfg.Zone, []libdns.Record{ { Type: "AAAA", Name: cfg.Record, Value: ownIp, TTL: time.Duration(15) * time.Minute, }, }) if err != nil { log.Fatalf("Error updating DNS record \"%s\": %s", target, err) } for _, record := range records { log.Printf("Set record: %v", record) } }