Refactor: Moved all under internal.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

FEAT: Directadmin provider is now working
This commit is contained in:
2024-03-18 09:55:01 +13:00
parent a52034216b
commit e1bb5adf36
19 changed files with 163 additions and 73 deletions

View File

@@ -12,7 +12,7 @@ This simple updater do the job, and send notifications, if change happen.
Before run, you need configure this environment variables. Before run, you need configure this environment variables.
- `CLOUDFLARE_DNS_TO_CHECK` - (required) dns records that will be automatically checked and modified based on the current public IP address. Multiple entries are separated by commas. For example: `domain.com,sub1.domain.com,sub2.domain.com` - `DNS_NAMES` - (required) dns records that will be automatically checked and modified based on the current public IP address. Multiple entries are separated by commas. For example: `domain.com,sub1.domain.com,sub2.domain.com`
- `CLOUDFLARE_API_KEY` - (required) your cloudflare api key, with access rights to edit selected domains. See: [https://developers.cloudflare.com/fundamentals/api/get-started/create-token/](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) - `CLOUDFLARE_API_KEY` - (required) your cloudflare api key, with access rights to edit selected domains. See: [https://developers.cloudflare.com/fundamentals/api/get-started/create-token/](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/)
- `CLOUDFLARE_ZONE` - (required) zone name with domain you want to check. See: [https://developers.cloudflare.com/fundamentals/get-started/concepts/accounts-and-zones/#zones](https://developers.cloudflare.com/fundamentals/get-started/concepts/accounts-and-zones/#zones) - `CLOUDFLARE_ZONE` - (required) zone name with domain you want to check. See: [https://developers.cloudflare.com/fundamentals/get-started/concepts/accounts-and-zones/#zones](https://developers.cloudflare.com/fundamentals/get-started/concepts/accounts-and-zones/#zones)
- `ON_CHANGE_COMMENT` - (optional) in the event that the ip address of the dns record changes, this comment will be added to the record - `ON_CHANGE_COMMENT` - (optional) in the event that the ip address of the dns record changes, this comment will be added to the record
@@ -31,7 +31,7 @@ Request body example:
"new_ip": "xxx.xxx.xxx.xxx", "new_ip": "xxx.xxx.xxx.xxx",
"checked_at": "2023-05-04T17:39:42.942850354+02:00", "checked_at": "2023-05-04T17:39:42.942850354+02:00",
"resolver_tag": "ifconfig.me", "resolver_tag": "ifconfig.me",
"domain": "my.domain.com" "domain": "my.domain.com",
"token": "a-webhook-token" "token": "a-webhook-token"
} }
``` ```
@@ -58,24 +58,42 @@ go install github.com/mkelcik/cloudflare-ddns-update
CLOUDFLARE_DNS_TO_CHECK="domain.com" CLOUDFLARE_API_KEY="my_key" CLOUDFLARE_ZONE="domain.com" cloudflare-ddns-update CLOUDFLARE_DNS_TO_CHECK="domain.com" CLOUDFLARE_API_KEY="my_key" CLOUDFLARE_ZONE="domain.com" cloudflare-ddns-update
``` ```
### Via `docker-compose` ### Via `docker-compose for Cloudflare`
```yaml ```yaml
version: "3" version: "3"
services: services:
cf-dns-updater: cf-dns-updater:
image: mkelcik/cloudflare-ddns-update:latest image: hub.cybercinch.nz/cybercinch/ddns-update:latest
restart: unless-stopped restart: unless-stopped
environment: environment:
- CLOUDFLARE_DNS_TO_CHECK=my.testdomain.com,your.testdomain.com
- CLOUDFLARE_API_KEY=your_cloudflare_api_key - CLOUDFLARE_API_KEY=your_cloudflare_api_key
- DNS_NAMES=my.testdomain.com,your.testdomain.com
- NOTIFIERS=webhook@http://localhost/cloudflare-updated-notification - NOTIFIERS=webhook@http://localhost/cloudflare-updated-notification
- CLOUDFLARE_ZONE=testdomain.com
- ON_CHANGE_COMMENT="automatically updated" - ON_CHANGE_COMMENT="automatically updated"
- CHECK_INTERVAL_SECONDS=300 - CHECK_INTERVAL_SECONDS=300
# Optional if your webhook receiver requires a token for verification # Optional if your webhook receiver requires a token for verification
# - WEBHOOK_TOKEN="SomeSup3rs3cureT0k3n" # - WEBHOOK_TOKEN="SomeSup3rs3cureT0k3n"
``` ```
### Via `docker-compose for DirectAdmin`
```yaml
version: "3"
services:
cf-dns-updater:
image: hub.cybercinch.nz/cybercinch/ddns-update:latest
restart: unless-stopped
environment:
- DNS_PROVIDER=directadmin
- DA_USER=some-directadmin-username
- DA_KEY=your-directadmin-password-or-login-key
- DA_URL=https://your.daserver.com:2222
- DNS_NAMES=my.testdomain.com,your.testdomain.com
- NOTIFIERS=webhook@http://localhost/cloudflare-updated-notification
- CHECK_INTERVAL_SECONDS=300
# Optional if your webhook receiver requires a token for verification
# - WEBHOOK_TOKEN="SomeSup3rs3cureT0k3n"
```
### Via `docker run` ### Via `docker run`
```shell ```shell
docker run -e CLOUDFLARE_DNS_TO_CHECK=my.testdomain.com,your.testdomain.com -e CLOUDFLARE_API_KEY=your_cloudflare_api_key -e CLOUDFLARE_ZONE=testdomain.com -e ON_CHANGE_COMMENT="automatically updated" -e CHECK_INTERVAL_SECONDS=300 mkelcik/cloudflare-ddns-update:latest docker run -e CLOUDFLARE_DNS_TO_CHECK=my.testdomain.com,your.testdomain.com -e CLOUDFLARE_API_KEY=your_cloudflare_api_key -e CLOUDFLARE_ZONE=testdomain.com -e ON_CHANGE_COMMENT="automatically updated" -e CHECK_INTERVAL_SECONDS=300 mkelcik/cloudflare-ddns-update:latest

10
go.mod
View File

@@ -1,18 +1,20 @@
module github.com/mkelcik/cloudflare-ddns-update module hub.cybercinch.nz/cybercinch/ddns-update
go 1.22 go 1.22
toolchain go1.22.1 toolchain go1.22.1
require github.com/cloudflare/cloudflare-go v0.83.0 require (
github.com/cloudflare/cloudflare-go v0.83.0
github.com/levelzerotechnology/directadmin-go v0.0.0-20240302013738-63b7793ebfd3
)
require ( require (
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
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 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

14
go.sum
View File

@@ -4,11 +4,13 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
@@ -18,16 +20,20 @@ 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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/levelzerotechnology/directadmin-go v0.0.0-20240302013738-63b7793ebfd3 h1:6kGC8EpZlYTvNI2ABYX87bBtgzfPrtZV8CKqLtSvyek= 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/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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 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=

View File

@@ -12,29 +12,32 @@ const (
defaultCheckInterval = 5 * 60 defaultCheckInterval = 5 * 60
envKeyDnsToCheck = "DNS_NAMES" envKeyDnsToCheck = "DNS_NAMES"
envKeyPublicIpResolverTag = "PUBLIC_IP_RESOLVER" envKeyPublicIpResolverTag = "PUBLIC_IP_RESOLVER"
envKeyDNSProviderTag = "DNS_PROVIDER"
envKeyPublicDNSServer = "PUBLIC_DNS_SERVER"
envKeyCloudflareApiKey = "CLOUDFLARE_API_KEY" envKeyCloudflareApiKey = "CLOUDFLARE_API_KEY"
envKeyCloudflareZone = "CLOUDFLARE_ZONE" envKeyCloudflareZone = "CLOUDFLARE_ZONE"
envKeyDirectadminUser = "DA_USER" envKeyDirectadminUser = "DA_USER"
envKeyDirectadminKey = "DA_LOGIN_KEY" envKeyDirectadminKey = "DA_KEY"
envKeyDirectadminUrl = "DA_LOGIN_URL" envKeyDirectadminUrl = "DA_URL"
envKeyOnChangeComment = "ON_CHANGE_COMMENT" envKeyOnChangeComment = "ON_CHANGE_COMMENT"
envKeyCheckIntervalSeconds = "CHECK_INTERVAL_SECONDS" envKeyCheckIntervalSeconds = "CHECK_INTERVAL_SECONDS"
envKeyNotifiers = "NOTIFIERS" envKeyNotifiers = "NOTIFIERS"
) )
type Config struct { type Config struct {
DnsRecordsToCheck []string DnsRecordsToCheck []string
PublicIpResolverTag string PublicIpResolverTag string
DNSProviderTag string PublicDNSServer string
DirectadminUsername string DNSProviderTag string
DirectadminKey string DirectadminUsername string
DirectadminUrl string DirectadminKey string
ApiToken string DirectadminUrl string
WebhookToken string ApiToken string
CloudflareZone string WebhookToken string
OnChangeComment string CloudflareZone string
Notifiers []string CloudflareOnChangeComment string
CheckInterval time.Duration Notifiers []string
CheckInterval time.Duration
} }
func (c Config) Validate() error { func (c Config) Validate() error {
@@ -44,21 +47,17 @@ func (c Config) Validate() error {
return fmt.Errorf("empty api token env key %s", envKeyCloudflareApiKey) return fmt.Errorf("empty api token env key %s", envKeyCloudflareApiKey)
} }
// if c.CloudflareZone == "" {
// return fmt.Errorf("empty zone in env key %s", envKeyCloudflareZone)
// }
case "directadmin": case "directadmin":
if c.DirectadminUrl == "" { if c.DirectadminUrl == "" {
return fmt.Errorf("empty DirectAdmin URL env key %s", envKeyDirectadminUrl) return fmt.Errorf("empty DirectAdmin URL env key %s", envKeyDirectadminUrl)
} }
if c.DirectadminUsername == "" { if c.DirectadminUsername == "" {
return fmt.Errorf("empty Username in env key %s", envKeyDirectadminUser) return fmt.Errorf("empty DirectAdmin Username in env key %s", envKeyDirectadminUser)
} }
if c.DirectadminKey == "" { if c.DirectadminKey == "" {
return fmt.Errorf("empty Login Key in env key %s", envKeyDirectadminKey) return fmt.Errorf("empty DirectAdmin Login Key in env key %s", envKeyDirectadminKey)
} }
} }
@@ -77,16 +76,26 @@ func NewConfig() Config {
} }
return Config{ return Config{
DnsRecordsToCheck: parseCommaDelimited(os.Getenv(envKeyDnsToCheck)), DnsRecordsToCheck: parseCommaDelimited(os.Getenv(envKeyDnsToCheck)),
PublicIpResolverTag: os.Getenv(envKeyPublicIpResolverTag), DNSProviderTag: getEnvDefault(envKeyDNSProviderTag, "cloudflare"),
ApiToken: os.Getenv(envKeyCloudflareApiKey), PublicDNSServer: getEnvDefault(envKeyPublicDNSServer, "1.1.1.1"),
DirectadminUsername: os.Getenv(envKeyDirectadminUser), PublicIpResolverTag: getEnvDefault(envKeyPublicIpResolverTag, "icanhazip"),
DirectadminKey: os.Getenv(envKeyDirectadminKey), ApiToken: os.Getenv(envKeyCloudflareApiKey),
DirectadminUrl: os.Getenv(envKeyDirectadminUrl), CloudflareZone: os.Getenv(envKeyCloudflareZone),
CloudflareZone: os.Getenv(envKeyCloudflareZone), CloudflareOnChangeComment: os.Getenv(envKeyOnChangeComment),
OnChangeComment: os.Getenv(envKeyOnChangeComment), DirectadminUsername: os.Getenv(envKeyDirectadminUser),
Notifiers: parseCommaDelimited(os.Getenv(envKeyNotifiers)), DirectadminKey: os.Getenv(envKeyDirectadminKey),
CheckInterval: time.Duration(checkInterval) * time.Second, DirectadminUrl: os.Getenv(envKeyDirectadminUrl),
WebhookToken: os.Getenv("WEBHOOK_TOKEN"), Notifiers: parseCommaDelimited(os.Getenv(envKeyNotifiers)),
CheckInterval: time.Duration(checkInterval) * time.Second,
WebhookToken: os.Getenv("WEBHOOK_TOKEN"),
} }
} }
func getEnvDefault(key, fallback string) string {
value, exists := os.LookupEnv(key)
if !exists {
value = fallback
}
return value
}

View File

@@ -59,12 +59,12 @@ func TestConfig_Validate(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := Config{ c := Config{
DnsRecordsToCheck: tt.fields.DnsRecordsToCheck, DnsRecordsToCheck: tt.fields.DnsRecordsToCheck,
PublicIpResolverTag: tt.fields.PublicIpResolverTag, PublicIpResolverTag: tt.fields.PublicIpResolverTag,
ApiToken: tt.fields.ApiToken, ApiToken: tt.fields.ApiToken,
CloudflareZone: tt.fields.CloudflareZone, CloudflareZone: tt.fields.CloudflareZone,
OnChangeComment: tt.fields.OnChangeComment, CloudflareOnChangeComment: tt.fields.OnChangeComment,
CheckInterval: tt.fields.CheckInterval, CheckInterval: tt.fields.CheckInterval,
} }
if err := c.Validate(); (err != nil) != tt.wantErr { if err := c.Validate(); (err != nil) != tt.wantErr {
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)

View File

@@ -5,7 +5,8 @@ import (
) )
type DNSProvider interface { type DNSProvider interface {
UpdateRecord(hostname string, ip string, old_ip string) error UpdateRecord(hostname string, ip string) error
FetchRecord(hostname string) (string, error)
} }
type DomainParts struct { type DomainParts struct {
@@ -16,8 +17,15 @@ type DomainParts struct {
func GetDomainParts(hostname string) *DomainParts { func GetDomainParts(hostname string) *DomainParts {
data := arrayToSlice(strings.Split(hostname, ".")) data := arrayToSlice(strings.Split(hostname, "."))
out := DomainParts{Name: data[0], Domain: strings.Join(data[1:], ".")} if len(data) <= 2 {
return &out // This might be the actual root domain
out := DomainParts{Name: strings.Join(data[0:], "."), Domain: strings.Join(data[0:], ".")}
return &out
} else {
// This is a subdomain
out := DomainParts{Name: data[0], Domain: strings.Join(data[1:], ".")}
return &out
}
} }
func arrayToSlice(array []string) []string { func arrayToSlice(array []string) []string {

View File

@@ -5,7 +5,7 @@ import (
"log" "log"
"github.com/cloudflare/cloudflare-go" "github.com/cloudflare/cloudflare-go"
"github.com/mkelcik/cloudflare-ddns-update/internal" "hub.cybercinch.nz/cybercinch/ddns-update/internal"
) )
const ( const (
@@ -18,7 +18,7 @@ type CloudflareProvider struct {
Config internal.Config Config internal.Config
} }
func (d *CloudflareProvider) UpdateRecord(hostname string, ip string, old_ip string) error { func (d *CloudflareProvider) UpdateRecord(hostname string, ip string) error {
// old_ip is not required for Cloudflare updates // old_ip is not required for Cloudflare updates
domain_parts := GetDomainParts(hostname) domain_parts := GetDomainParts(hostname)
zoneId := d.FindZoneIdByName(domain_parts.Domain) zoneId := d.FindZoneIdByName(domain_parts.Domain)
@@ -33,8 +33,8 @@ func (d *CloudflareProvider) UpdateRecord(hostname string, ip string, old_ip str
Content: ip, Content: ip,
} }
if d.Config.OnChangeComment != "" { if d.Config.CloudflareOnChangeComment != "" {
update.Comment = &d.Config.OnChangeComment update.Comment = &d.Config.CloudflareOnChangeComment
} }
_, err = d.Client.UpdateDNSRecord(d.Context, cloudflare.ZoneIdentifier(zoneId), update) _, err = d.Client.UpdateDNSRecord(d.Context, cloudflare.ZoneIdentifier(zoneId), update)
@@ -44,6 +44,13 @@ func (d *CloudflareProvider) UpdateRecord(hostname string, ip string, old_ip str
return nil return nil
} }
func (d *CloudflareProvider) FetchRecord(hostname string) (string, error) {
domain_parts := GetDomainParts(hostname)
zoneId := d.FindZoneIdByName(domain_parts.Domain)
dnsRecord, err := d.GetDnsRecord(hostname, zoneId)
return dnsRecord[0].Content, err
}
func (d *CloudflareProvider) NewClient(api_token string) { func (d *CloudflareProvider) NewClient(api_token string) {
api, err := cloudflare.NewWithAPIToken(api_token) api, err := cloudflare.NewWithAPIToken(api_token)
if err != nil { if err != nil {

View File

@@ -2,10 +2,12 @@ package dns_providers
import ( import (
"context" "context"
"fmt"
"log"
"time" "time"
"github.com/levelzerotechnology/directadmin-go" "github.com/levelzerotechnology/directadmin-go"
"github.com/mkelcik/cloudflare-ddns-update/internal" "hub.cybercinch.nz/cybercinch/ddns-update/internal"
) )
const ( const (
@@ -18,20 +20,54 @@ type Directadmin struct {
Config internal.Config Config internal.Config
} }
func (d *Directadmin) UpdateRecord(hostname string, ip string, old_ip string) error { type ListDNSRecordsParams struct {
Name string
}
func (d *Directadmin) FetchRecord(hostname string) (string, error) {
dnsRecord, err := d.GetDnsRecord(hostname)
if err != nil {
log.Fatal("unable to retrieve DNS record")
return "", err
}
return dnsRecord[0].Value, nil
}
func (d *Directadmin) UpdateRecord(hostname string, ip string) error {
result := GetDomainParts(hostname) result := GetDomainParts(hostname)
current_record, _ := d.GetDnsRecord(hostname)
a := directadmin.DNSRecord{Name: result.Name, Ttl: 300, Type: "A", Value: old_ip} // Create an updated record for the new ip
b := directadmin.DNSRecord{Name: result.Name, Ttl: 300, Type: "A", Value: ip} new_record := directadmin.DNSRecord{Name: current_record[0].Name, Ttl: current_record[0].Ttl, Type: current_record[0].Type, Value: ip}
err := d.Client.UpdateDNSRecord(result.Domain, a, b) err := d.Client.UpdateDNSRecord(result.Domain, current_record[0], new_record)
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
func (d *Directadmin) GetDnsRecord(hostname string) ([]directadmin.DNSRecord, error) {
domainParts := GetDomainParts(hostname)
dnsRecords, err := d.Client.GetDNSRecords(domainParts.Domain)
var slice []directadmin.DNSRecord
for _, dnsRecord := range dnsRecords {
if domainParts.Name == dnsRecord.Name {
slice = append(slice, dnsRecord)
}
}
if len(slice) == 0 {
return nil, fmt.Errorf("unable to find DNS record for %s", hostname)
}
return slice, err
}
func (d *Directadmin) NewClient(server_url string, username string, key string) { func (d *Directadmin) NewClient(server_url string, username string, key string) {
api, err := directadmin.New(server_url, 5*time.Second, false, false) api, err := directadmin.New(server_url, 5*time.Second, false, false)
if err != nil { if err != nil {

View File

@@ -1,6 +1,8 @@
package internal package internal
import "strings" import (
"strings"
)
func parseCommaDelimited(data string) []string { func parseCommaDelimited(data string) []string {
out := make([]string, 0, strings.Count(data, ",")+1) out := make([]string, 0, strings.Count(data, ",")+1)

20
main.go
View File

@@ -8,11 +8,10 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/mkelcik/cloudflare-ddns-update/internal" "hub.cybercinch.nz/cybercinch/ddns-update/internal"
"github.com/mkelcik/cloudflare-ddns-update/internal/dns_providers" "hub.cybercinch.nz/cybercinch/ddns-update/internal/dns_providers"
"github.com/mkelcik/cloudflare-ddns-update/internal/dns_resolver" "hub.cybercinch.nz/cybercinch/ddns-update/internal/notifications"
"github.com/mkelcik/cloudflare-ddns-update/notifications" "hub.cybercinch.nz/cybercinch/ddns-update/internal/public_resolvers"
"github.com/mkelcik/cloudflare-ddns-update/public_resolvers"
) )
type PublicIpResolver interface { type PublicIpResolver interface {
@@ -75,15 +74,18 @@ func main() {
} }
for _, dnsRecord := range config.DnsRecordsToCheck { for _, dnsRecord := range config.DnsRecordsToCheck {
current_dns_record := dns_resolver.ResolveHostname(dnsRecord, "1.1.1.1") current_dns_record, err := dnsProvider.FetchRecord(dnsRecord)
if err != nil {
log.Fatalf("Failed to fetch DNS record: %s", err)
}
log.Printf("Checking record `%s` with current value `%s` ...", dnsRecord, current_dns_record.String()) log.Printf("Checking record `%s` with current value `%s` ...", dnsRecord, current_dns_record)
if currentPublicIP.String() == current_dns_record.String() { if currentPublicIP.String() == current_dns_record {
log.Println("OK") log.Println("OK")
continue // no update needed continue // no update needed
} }
if err := dnsProvider.UpdateRecord(dnsRecord, currentPublicIP.String(), current_dns_record.String()); err != nil { if err := dnsProvider.UpdateRecord(dnsRecord, currentPublicIP.String()); err != nil {
log.Printf("error updating dns record: %s", err) log.Printf("error updating dns record: %s", err)
continue continue
} }