2023-04-28 14:57:21 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"log"
|
|
|
|
|
"net"
|
|
|
|
|
"os/signal"
|
|
|
|
|
"syscall"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/cloudflare/cloudflare-go"
|
2023-04-28 23:09:40 +02:00
|
|
|
"github.com/mkelcik/cloudflare-ddns-update/internal"
|
2023-05-04 11:44:27 +02:00
|
|
|
"github.com/mkelcik/cloudflare-ddns-update/notifications"
|
2023-04-28 23:09:40 +02:00
|
|
|
"github.com/mkelcik/cloudflare-ddns-update/public_resolvers"
|
2023-04-28 14:57:21 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type PublicIpResolver interface {
|
|
|
|
|
ResolvePublicIp(ctx context.Context) (net.IP, error)
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-01 10:01:43 +02:00
|
|
|
func getResolver(resolverName string) (PublicIpResolver, string) {
|
2023-04-28 14:57:21 +02:00
|
|
|
switch resolverName {
|
|
|
|
|
// HERE add another resolver if needed
|
2023-05-03 23:06:57 +02:00
|
|
|
case public_resolvers.CloudflareTraceTag:
|
|
|
|
|
return public_resolvers.NewDefaultCloudflareTrace(), public_resolvers.CloudflareTraceTag
|
2023-05-01 10:01:43 +02:00
|
|
|
case public_resolvers.V4IdentMeTag:
|
|
|
|
|
return public_resolvers.NewV4IdentMeDefault(), public_resolvers.V4IdentMeTag
|
2023-04-28 14:57:21 +02:00
|
|
|
case public_resolvers.IfConfigMeTag:
|
|
|
|
|
fallthrough
|
|
|
|
|
default:
|
2023-05-01 10:01:43 +02:00
|
|
|
return public_resolvers.NewDefaultIfConfigMe(), public_resolvers.IfConfigMeTag
|
2023-04-28 14:57:21 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
|
defer stop()
|
|
|
|
|
|
|
|
|
|
config := internal.NewConfig()
|
|
|
|
|
if err := config.Validate(); err != nil {
|
|
|
|
|
log.Fatalln(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
api, err := cloudflare.NewWithAPIToken(config.ApiToken)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetch user details on the account
|
|
|
|
|
zoneID, err := api.ZoneIDByName(config.CloudflareZone)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-04 17:28:25 +02:00
|
|
|
notifiers := notifications.GetNotifiers(config.Notifiers)
|
2023-05-04 11:44:27 +02:00
|
|
|
|
2023-04-30 08:46:25 +02:00
|
|
|
// public ip resolver
|
2023-05-01 10:01:43 +02:00
|
|
|
publicIpResolver, resolverTag := getResolver(config.PublicIpResolverTag)
|
2023-04-30 08:46:25 +02:00
|
|
|
|
|
|
|
|
checkFunc := func() {
|
|
|
|
|
currentPublicIP, err := publicIpResolver.ResolvePublicIp(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
2023-05-03 23:06:57 +02:00
|
|
|
log.Printf("Current public ip `%s` (resolver: %s)", currentPublicIP, resolverTag)
|
2023-04-30 08:46:25 +02:00
|
|
|
|
2023-12-09 16:12:58 +01:00
|
|
|
// check if ip is not in ignore list
|
|
|
|
|
if internal.IgnoredIpChange(currentPublicIP, config.IgnoredIpChange) {
|
|
|
|
|
log.Printf("Ignored ip change `%s`, skipping.", currentPublicIP)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-30 08:46:25 +02:00
|
|
|
dns, err := allDNSRecords(ctx, api, cloudflare.ZoneIdentifier(zoneID))
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, dnsRecord := range dns {
|
|
|
|
|
if internal.Contains(config.DnsRecordsToCheck, dnsRecord.Name) {
|
|
|
|
|
log.Printf("Checking record `%s` with current value `%s` ...", dnsRecord.Name, dnsRecord.Content)
|
|
|
|
|
if currentPublicIP.String() == dnsRecord.Content {
|
|
|
|
|
log.Println("OK")
|
|
|
|
|
continue // no update needed
|
2023-04-30 08:25:33 +02:00
|
|
|
}
|
|
|
|
|
|
2023-04-30 08:46:25 +02:00
|
|
|
update := cloudflare.UpdateDNSRecordParams{
|
|
|
|
|
ID: dnsRecord.ID,
|
|
|
|
|
Content: currentPublicIP.String(),
|
|
|
|
|
}
|
2023-04-30 08:25:33 +02:00
|
|
|
|
2023-04-30 08:46:25 +02:00
|
|
|
if config.OnChangeComment != "" {
|
2023-12-09 16:20:31 +01:00
|
|
|
update.Comment = &config.OnChangeComment
|
2023-04-29 12:45:21 +02:00
|
|
|
}
|
|
|
|
|
|
2023-04-30 08:46:25 +02:00
|
|
|
if _, err := api.UpdateDNSRecord(ctx, cloudflare.ZoneIdentifier(zoneID), update); err != nil {
|
|
|
|
|
log.Printf("error updating dns record: %s", err)
|
2023-05-04 11:44:27 +02:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := notifiers.NotifyWithLog(ctx, notifications.Notification{
|
|
|
|
|
OldIp: net.ParseIP(dnsRecord.Content),
|
|
|
|
|
NewIp: currentPublicIP,
|
|
|
|
|
CheckedAt: time.Now(),
|
|
|
|
|
ResolverTag: resolverTag,
|
|
|
|
|
Domain: dnsRecord.Name,
|
|
|
|
|
}); err != nil {
|
|
|
|
|
log.Printf("errors in notifications: %s", err)
|
2023-04-29 12:45:21 +02:00
|
|
|
}
|
2023-05-04 11:44:27 +02:00
|
|
|
log.Printf("Updated to `%s`", currentPublicIP)
|
2023-04-30 08:46:25 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Printf("checking ...")
|
|
|
|
|
checkFunc()
|
|
|
|
|
|
|
|
|
|
log.Println("waiting for check tick ...")
|
|
|
|
|
ticker := time.NewTicker(config.CheckInterval)
|
|
|
|
|
defer ticker.Stop()
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-ticker.C:
|
|
|
|
|
log.Println("tick received checking ...")
|
|
|
|
|
checkFunc()
|
2023-04-29 12:45:21 +02:00
|
|
|
case <-ctx.Done():
|
|
|
|
|
break
|
2023-04-28 14:57:21 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func allDNSRecords(ctx context.Context, api *cloudflare.API, rc *cloudflare.ResourceContainer) ([]cloudflare.DNSRecord, error) {
|
|
|
|
|
out := make([]cloudflare.DNSRecord, 0, 100)
|
|
|
|
|
params := cloudflare.ListDNSRecordsParams{
|
|
|
|
|
ResultInfo: cloudflare.ResultInfo{Page: 1},
|
|
|
|
|
}
|
|
|
|
|
for {
|
|
|
|
|
page, res, err := api.ListDNSRecords(ctx, rc, params)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
out = append(out, page...)
|
|
|
|
|
|
|
|
|
|
if res.Page >= res.TotalPages {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
params.Page++
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|