dyndns-go/main.go
2024-02-04 12:58:01 +01:00

118 lines
2.7 KiB
Go

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)
}
}