You've already forked ddns-updater
FEAT: Refactor allowing multiple DNS Providers
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.21 as build
|
FROM golang:1.22 as build
|
||||||
|
|
||||||
# Copy project sources
|
# Copy project sources
|
||||||
COPY . /opt/project/
|
COPY . /opt/project/
|
||||||
|
|||||||
7
go.mod
7
go.mod
@@ -1,6 +1,8 @@
|
|||||||
module github.com/mkelcik/cloudflare-ddns-update
|
module github.com/mkelcik/cloudflare-ddns-update
|
||||||
|
|
||||||
go 1.21
|
go 1.22
|
||||||
|
|
||||||
|
toolchain go1.22.1
|
||||||
|
|
||||||
require github.com/cloudflare/cloudflare-go v0.83.0
|
require github.com/cloudflare/cloudflare-go v0.83.0
|
||||||
|
|
||||||
@@ -9,6 +11,9 @@ require (
|
|||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
||||||
|
github.com/levelzerotechnology/directadmin-go v0.0.0-20240302013738-63b7793ebfd3 // indirect
|
||||||
|
github.com/mehrdadep/dex v1.0.2 // indirect
|
||||||
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
golang.org/x/net v0.19.0 // indirect
|
golang.org/x/net v0.19.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
|
|||||||
6
go.sum
6
go.sum
@@ -18,12 +18,18 @@ github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXc
|
|||||||
github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
|
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
||||||
|
github.com/levelzerotechnology/directadmin-go v0.0.0-20240302013738-63b7793ebfd3 h1:6kGC8EpZlYTvNI2ABYX87bBtgzfPrtZV8CKqLtSvyek=
|
||||||
|
github.com/levelzerotechnology/directadmin-go v0.0.0-20240302013738-63b7793ebfd3/go.mod h1:FJ/EtEMwe3k2ABVYLXmE/K+H8cHVpVaSgihVT2XbSkc=
|
||||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
github.com/mehrdadep/dex v1.0.2 h1:4rUs5xbcODC107BM6cICsfMF3qBAEqKWoZaqXClMvoo=
|
||||||
|
github.com/mehrdadep/dex v1.0.2/go.mod h1:Vg+AUvtXkTFfNTYtQiY+NTvV3Bvyz7oiT0asmU4z440=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||||
|
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
|||||||
56137
index.html
Normal file
56137
index.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,11 +10,13 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
defaultCheckInterval = 5 * 60
|
defaultCheckInterval = 5 * 60
|
||||||
|
envKeyDnsToCheck = "DNS_NAMES"
|
||||||
envKeyDnsToCheck = "CLOUDFLARE_DNS_TO_CHECK"
|
|
||||||
envKeyPublicIpResolverTag = "PUBLIC_IP_RESOLVER"
|
envKeyPublicIpResolverTag = "PUBLIC_IP_RESOLVER"
|
||||||
envKeyCloudflareApiKey = "CLOUDFLARE_API_KEY"
|
envKeyCloudflareApiKey = "CLOUDFLARE_API_KEY"
|
||||||
envKeyCloudflareZone = "CLOUDFLARE_ZONE"
|
envKeyCloudflareZone = "CLOUDFLARE_ZONE"
|
||||||
|
envKeyDirectadminUser = "DA_USER"
|
||||||
|
envKeyDirectadminKey = "DA_LOGIN_KEY"
|
||||||
|
envKeyDirectadminUrl = "DA_LOGIN_URL"
|
||||||
envKeyOnChangeComment = "ON_CHANGE_COMMENT"
|
envKeyOnChangeComment = "ON_CHANGE_COMMENT"
|
||||||
envKeyCheckIntervalSeconds = "CHECK_INTERVAL_SECONDS"
|
envKeyCheckIntervalSeconds = "CHECK_INTERVAL_SECONDS"
|
||||||
envKeyNotifiers = "NOTIFIERS"
|
envKeyNotifiers = "NOTIFIERS"
|
||||||
@@ -23,6 +25,10 @@ const (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
DnsRecordsToCheck []string
|
DnsRecordsToCheck []string
|
||||||
PublicIpResolverTag string
|
PublicIpResolverTag string
|
||||||
|
DNSProviderTag string
|
||||||
|
DirectadminUsername string
|
||||||
|
DirectadminKey string
|
||||||
|
DirectadminUrl string
|
||||||
ApiToken string
|
ApiToken string
|
||||||
WebhookToken string
|
WebhookToken string
|
||||||
CloudflareZone string
|
CloudflareZone string
|
||||||
@@ -32,12 +38,28 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c Config) Validate() error {
|
func (c Config) Validate() error {
|
||||||
|
switch c.DNSProviderTag {
|
||||||
|
case "cloudflare":
|
||||||
if c.ApiToken == "" {
|
if c.ApiToken == "" {
|
||||||
return fmt.Errorf("empty api token env key %s", envKeyCloudflareApiKey)
|
return fmt.Errorf("empty api token env key %s", envKeyCloudflareApiKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.CloudflareZone == "" {
|
// if c.CloudflareZone == "" {
|
||||||
return fmt.Errorf("empty zone in env key %s", envKeyCloudflareZone)
|
// return fmt.Errorf("empty zone in env key %s", envKeyCloudflareZone)
|
||||||
|
// }
|
||||||
|
|
||||||
|
case "directadmin":
|
||||||
|
if c.DirectadminUrl == "" {
|
||||||
|
return fmt.Errorf("empty DirectAdmin URL env key %s", envKeyDirectadminUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.DirectadminUsername == "" {
|
||||||
|
return fmt.Errorf("empty Username in env key %s", envKeyDirectadminUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.DirectadminKey == "" {
|
||||||
|
return fmt.Errorf("empty Login Key in env key %s", envKeyDirectadminKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.DnsRecordsToCheck) == 0 {
|
if len(c.DnsRecordsToCheck) == 0 {
|
||||||
@@ -58,6 +80,9 @@ func NewConfig() Config {
|
|||||||
DnsRecordsToCheck: parseCommaDelimited(os.Getenv(envKeyDnsToCheck)),
|
DnsRecordsToCheck: parseCommaDelimited(os.Getenv(envKeyDnsToCheck)),
|
||||||
PublicIpResolverTag: os.Getenv(envKeyPublicIpResolverTag),
|
PublicIpResolverTag: os.Getenv(envKeyPublicIpResolverTag),
|
||||||
ApiToken: os.Getenv(envKeyCloudflareApiKey),
|
ApiToken: os.Getenv(envKeyCloudflareApiKey),
|
||||||
|
DirectadminUsername: os.Getenv(envKeyDirectadminUser),
|
||||||
|
DirectadminKey: os.Getenv(envKeyDirectadminKey),
|
||||||
|
DirectadminUrl: os.Getenv(envKeyDirectadminUrl),
|
||||||
CloudflareZone: os.Getenv(envKeyCloudflareZone),
|
CloudflareZone: os.Getenv(envKeyCloudflareZone),
|
||||||
OnChangeComment: os.Getenv(envKeyOnChangeComment),
|
OnChangeComment: os.Getenv(envKeyOnChangeComment),
|
||||||
Notifiers: parseCommaDelimited(os.Getenv(envKeyNotifiers)),
|
Notifiers: parseCommaDelimited(os.Getenv(envKeyNotifiers)),
|
||||||
|
|||||||
25
internal/dns_providers/base_provider.go
Normal file
25
internal/dns_providers/base_provider.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package dns_providers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DNSProvider interface {
|
||||||
|
UpdateRecord(hostname string, ip string, old_ip string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type DomainParts struct {
|
||||||
|
Name string
|
||||||
|
Domain string
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDomainParts(hostname string) *DomainParts {
|
||||||
|
data := arrayToSlice(strings.Split(hostname, "."))
|
||||||
|
|
||||||
|
out := DomainParts{Name: data[0], Domain: strings.Join(data[1:], ".")}
|
||||||
|
return &out
|
||||||
|
}
|
||||||
|
|
||||||
|
func arrayToSlice(array []string) []string {
|
||||||
|
return array[:]
|
||||||
|
}
|
||||||
85
internal/dns_providers/cloudflare.go
Normal file
85
internal/dns_providers/cloudflare.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package dns_providers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/cloudflare/cloudflare-go"
|
||||||
|
"github.com/mkelcik/cloudflare-ddns-update/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CloudflareTag = "cloudflare"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CloudflareProvider struct {
|
||||||
|
Context context.Context
|
||||||
|
Client *cloudflare.API
|
||||||
|
Config internal.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *CloudflareProvider) UpdateRecord(hostname string, ip string, old_ip string) error {
|
||||||
|
// old_ip is not required for Cloudflare updates
|
||||||
|
domain_parts := GetDomainParts(hostname)
|
||||||
|
zoneId := d.FindZoneIdByName(domain_parts.Domain)
|
||||||
|
dnsRecords, err := d.GetDnsRecord(hostname, zoneId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
update := cloudflare.UpdateDNSRecordParams{
|
||||||
|
ID: dnsRecords[0].ID,
|
||||||
|
Content: ip,
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.Config.OnChangeComment != "" {
|
||||||
|
update.Comment = &d.Config.OnChangeComment
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = d.Client.UpdateDNSRecord(d.Context, cloudflare.ZoneIdentifier(zoneId), update)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *CloudflareProvider) NewClient(api_token string) {
|
||||||
|
api, err := cloudflare.NewWithAPIToken(api_token)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Client = api
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *CloudflareProvider) FindZoneIdByName(hostname string) string {
|
||||||
|
// Fetch user details on the account
|
||||||
|
zoneID, err := d.Client.ZoneIDByName(hostname)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return zoneID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *CloudflareProvider) GetDnsRecord(name string, zoneId string) ([]cloudflare.DNSRecord, error) {
|
||||||
|
|
||||||
|
params := cloudflare.ListDNSRecordsParams{
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
|
||||||
|
page, _, err := d.Client.ListDNSRecords(d.Context, cloudflare.ZoneIdentifier(zoneId), params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return page, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCloudflareProvider(ctx context.Context, config internal.Config) *CloudflareProvider {
|
||||||
|
c := &CloudflareProvider{}
|
||||||
|
c.Context = ctx
|
||||||
|
c.Config = config
|
||||||
|
c.NewClient(c.Config.ApiToken)
|
||||||
|
return c
|
||||||
|
}
|
||||||
55
internal/dns_providers/directadmin.go
Normal file
55
internal/dns_providers/directadmin.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package dns_providers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/levelzerotechnology/directadmin-go"
|
||||||
|
"github.com/mkelcik/cloudflare-ddns-update/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DirectadminTag = "directadmin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Directadmin struct {
|
||||||
|
Client *directadmin.UserContext
|
||||||
|
Context context.Context
|
||||||
|
Config internal.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Directadmin) UpdateRecord(hostname string, ip string, old_ip string) error {
|
||||||
|
|
||||||
|
result := GetDomainParts(hostname)
|
||||||
|
|
||||||
|
a := directadmin.DNSRecord{Name: result.Name, Ttl: 300, Type: "A", Value: old_ip}
|
||||||
|
b := directadmin.DNSRecord{Name: result.Name, Ttl: 300, Type: "A", Value: ip}
|
||||||
|
|
||||||
|
err := d.Client.UpdateDNSRecord(result.Domain, a, b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Directadmin) NewClient(server_url string, username string, key string) {
|
||||||
|
api, err := directadmin.New(server_url, 5*time.Second, false, false)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
userCtx, err := api.LoginAsUser(username, key)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Client = userCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDirectAdminProvider(ctx context.Context, config internal.Config) *Directadmin {
|
||||||
|
c := &Directadmin{}
|
||||||
|
c.Context = ctx
|
||||||
|
c.Config = config
|
||||||
|
c.NewClient(config.DirectadminUrl, config.DirectadminUsername, config.DirectadminKey)
|
||||||
|
return c
|
||||||
|
}
|
||||||
29
internal/dns_resolver/dns_resolver.go
Normal file
29
internal/dns_resolver/dns_resolver.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package dns_resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ResolveHostname(host string, dns_resolver_ip string) net.IP {
|
||||||
|
r := &net.Resolver{
|
||||||
|
PreferGo: true,
|
||||||
|
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
d := net.Dialer{
|
||||||
|
Timeout: time.Millisecond * time.Duration(10000),
|
||||||
|
}
|
||||||
|
return d.DialContext(ctx, network, dns_resolver_ip+":53")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ips, err := r.LookupHost(context.Background(), host)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Could not get IPs: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.ParseIP(ips[len(ips)-1])
|
||||||
|
}
|
||||||
74
main.go
74
main.go
@@ -8,8 +8,9 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflare-go"
|
|
||||||
"github.com/mkelcik/cloudflare-ddns-update/internal"
|
"github.com/mkelcik/cloudflare-ddns-update/internal"
|
||||||
|
"github.com/mkelcik/cloudflare-ddns-update/internal/dns_providers"
|
||||||
|
"github.com/mkelcik/cloudflare-ddns-update/internal/dns_resolver"
|
||||||
"github.com/mkelcik/cloudflare-ddns-update/notifications"
|
"github.com/mkelcik/cloudflare-ddns-update/notifications"
|
||||||
"github.com/mkelcik/cloudflare-ddns-update/public_resolvers"
|
"github.com/mkelcik/cloudflare-ddns-update/public_resolvers"
|
||||||
)
|
)
|
||||||
@@ -34,6 +35,17 @@ func getResolver(resolverName string) (PublicIpResolver, string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getProvider(ctx context.Context, config internal.Config) (dns_providers.DNSProvider, string) {
|
||||||
|
switch config.DNSProviderTag {
|
||||||
|
case dns_providers.DirectadminTag:
|
||||||
|
return dns_providers.NewDirectAdminProvider(ctx, config), dns_providers.DirectadminTag
|
||||||
|
case dns_providers.CloudflareTag:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return dns_providers.NewCloudflareProvider(ctx, config), dns_providers.CloudflareTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer stop()
|
defer stop()
|
||||||
@@ -43,62 +55,45 @@ func main() {
|
|||||||
log.Fatalln(err)
|
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
notifiers := notifications.GetNotifiers(config.Notifiers)
|
notifiers := notifications.GetNotifiers(config.Notifiers)
|
||||||
|
|
||||||
// public ip resolver
|
// public ip resolver
|
||||||
publicIpResolver, resolverTag := getResolver(config.PublicIpResolverTag)
|
publicIpResolver, resolverTag := getResolver(config.PublicIpResolverTag)
|
||||||
|
dnsProvider, providerTag := getProvider(ctx, config)
|
||||||
|
log.Printf("Using DNS Provider %s", providerTag)
|
||||||
|
|
||||||
checkFunc := func() {
|
checkFunc := func() {
|
||||||
currentPublicIP, err := publicIpResolver.ResolvePublicIp(ctx)
|
currentPublicIP, err := publicIpResolver.ResolvePublicIp(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Current public ip `%s` (resolver: %s)", currentPublicIP, resolverTag)
|
log.Printf("Current public ip `%s` (resolver: %s)", currentPublicIP, resolverTag)
|
||||||
|
|
||||||
dns, err := allDNSRecords(ctx, api, cloudflare.ZoneIdentifier(zoneID))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, dnsRecord := range dns {
|
for _, dnsRecord := range config.DnsRecordsToCheck {
|
||||||
if internal.Contains(config.DnsRecordsToCheck, dnsRecord.Name) {
|
current_dns_record := dns_resolver.ResolveHostname(dnsRecord, "1.1.1.1")
|
||||||
log.Printf("Checking record `%s` with current value `%s` ...", dnsRecord.Name, dnsRecord.Content)
|
|
||||||
if currentPublicIP.String() == dnsRecord.Content {
|
log.Printf("Checking record `%s` with current value `%s` ...", dnsRecord, current_dns_record.String())
|
||||||
|
if currentPublicIP.String() == current_dns_record.String() {
|
||||||
log.Println("OK")
|
log.Println("OK")
|
||||||
continue // no update needed
|
continue // no update needed
|
||||||
}
|
}
|
||||||
|
|
||||||
update := cloudflare.UpdateDNSRecordParams{
|
if err := dnsProvider.UpdateRecord(dnsRecord, currentPublicIP.String(), current_dns_record.String()); err != nil {
|
||||||
ID: dnsRecord.ID,
|
|
||||||
Content: currentPublicIP.String(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.OnChangeComment != "" {
|
|
||||||
update.Comment = &config.OnChangeComment
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := api.UpdateDNSRecord(ctx, cloudflare.ZoneIdentifier(zoneID), update); err != nil {
|
|
||||||
log.Printf("error updating dns record: %s", err)
|
log.Printf("error updating dns record: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := notifiers.NotifyWithLog(ctx, notifications.Notification{
|
if err := notifiers.NotifyWithLog(ctx, notifications.Notification{
|
||||||
OldIp: net.ParseIP(dnsRecord.Content),
|
OldIp: net.ParseIP(string(current_dns_record)),
|
||||||
NewIp: currentPublicIP,
|
NewIp: currentPublicIP,
|
||||||
CheckedAt: time.Now(),
|
CheckedAt: time.Now(),
|
||||||
ResolverTag: resolverTag,
|
ResolverTag: resolverTag,
|
||||||
Domain: dnsRecord.Name,
|
Domain: dnsRecord,
|
||||||
WebhookToken: config.WebhookToken,
|
WebhookToken: config.WebhookToken,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Printf("errors in notifications: %s", err)
|
log.Printf("errors in notifications: %s", err)
|
||||||
@@ -106,7 +101,6 @@ func main() {
|
|||||||
log.Printf("Updated to `%s`", currentPublicIP)
|
log.Printf("Updated to `%s`", currentPublicIP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("checking ...")
|
log.Printf("checking ...")
|
||||||
checkFunc()
|
checkFunc()
|
||||||
@@ -124,23 +118,3 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user