16 Commits

Author SHA1 Message Date
f31f2d74b7 SECURITY: Run as non-privileged user
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-03-18 10:13:45 +13:00
e1bb5adf36 Refactor: Moved all under internal.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
FEAT: Directadmin provider is now working
2024-03-18 09:55:01 +13:00
a52034216b SKIP CI: Remved erroneus file
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-03-15 23:19:12 +13:00
17014eeae1 FEAT: Refactor allowing multiple DNS Providers
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-03-15 23:16:23 +13:00
a977adf929 Added icanhazip resolver
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-21 22:38:19 +13:00
5e4a7f8135 Update .woodpecker.yml
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-12 23:22:20 +13:00
f09bbfa6b7 Update .woodpecker.yml 2024-02-12 23:20:16 +13:00
cbe676846a Add Woodpecker pipeline
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
2024-02-12 22:54:25 +13:00
cffeeaa2f8 Update README.md 2024-02-12 22:26:27 +13:00
adf83e7782 Specify that we are sending JSON 2024-02-12 17:10:02 +13:00
ddd6326a6e Added ability to set/add Webhook Token in payload 2024-02-12 16:50:56 +13:00
mkelcik
13e2fa6f7b Merge pull request #11 from mkelcik/upgrade
Upgrade packages + go
2023-12-13 23:05:13 +01:00
mkelcik
401884304e upgrade go 2023-12-13 23:03:08 +01:00
mkelcik
df74a0e159 upgrade 2023-12-09 16:29:26 +01:00
mkelcik
316a40b662 upgrade 2023-12-09 16:20:31 +01:00
mkelcik
17afc65f92 upgrade 2023-12-09 16:15:22 +01:00
23 changed files with 515 additions and 238 deletions

49
.woodpecker.yml Normal file
View File

@@ -0,0 +1,49 @@
variables:
- &platforms 'linux/arm/v7,linux/arm64/v8,linux/amd64'
- &docker_creds
username:
from_secret: hub_username_cybercinch
password:
from_secret: docker_password_cybercinch
- &pypi_creds
username:
from_secret: hub_username_cybercinch
password:
from_secret: docker_password_cybercinch
steps:
publish-docker-latest:
image: docker.io/cybercinch/woodpecker-plugin-depot
pull: true
settings:
<<: *docker_creds
token:
from_secret: depot_token
repohost: hub.cybercinch.nz
repo: cybercinch/${CI_REPO_NAME}
project: 8b4ht8th6p
dockerfile: Dockerfile
push: true
platforms: *platforms
tags: ["latest"]
when:
branch: ${CI_REPO_DEFAULT_BRANCH}
event:
- push
- manual
update-swarm-service-portainer:
image: docker.io/plugins/webhook
settings:
urls:
from_secret: deploy_url
method: POST
# depends_on: publish-docker-latest
# settings:
# <<: *docker_creds
# repohost: hub.cybercinch.nz
# repo: cybercinch/imap_retention_manager
# dockerfile: Dockerfile
# platforms: *platforms
# tags: ["latest", "${CI_COMMIT_TAG}"]
# when:
# event: tag

View File

@@ -1,15 +1,22 @@
FROM golang:1.21 as build FROM golang:1.22 as build
# Copy project sources # Copy project sources
COPY . /opt/project/ COPY . /opt/project/
WORKDIR /opt/project WORKDIR /opt/project
# Install Pre-Requisites
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates=20230311 RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates=20230311
# Create a user
RUN useradd --no-create-home --system --shell /bin/false ddnsuser
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /cloudflare-ddns-updater RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /cloudflare-ddns-updater
FROM scratch FROM scratch
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /cloudflare-ddns-updater /cloudflare-ddns-updater COPY --from=build /cloudflare-ddns-updater /cloudflare-ddns-updater
COPY --from=build /bin/false /bin/false
COPY --from=build /etc/passwd /etc/passwd
USER nobody
ENTRYPOINT ["/cloudflare-ddns-updater"] ENTRYPOINT ["/cloudflare-ddns-updater"]
CMD ["cloudflare-ddns-updater"] CMD ["cloudflare-ddns-updater"]

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,8 @@ 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"
} }
``` ```
@@ -57,20 +58,40 @@ 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
# - 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`

13
go.mod
View File

@@ -1,14 +1,21 @@
module github.com/mkelcik/cloudflare-ddns-update module hub.cybercinch.nz/cybercinch/ddns-update
go 1.21
require github.com/cloudflare/cloudflare-go v0.83.0 go 1.22
toolchain go1.22.1
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/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

35
go.sum
View File

@@ -1,43 +1,52 @@
github.com/cloudflare/cloudflare-go v0.66.0 h1:B74IvVGQ4UFYJnqQSK/9GbR+Y1HwNxqqdN2Bmg0dckg=
github.com/cloudflare/cloudflare-go v0.66.0/go.mod h1:tA44hjU9FfycofKT+lWWMHb/dEq1pRbiVPGuJo1WzLQ=
github.com/cloudflare/cloudflare-go v0.83.0 h1:aq85Hbr5W6KfXZV7v3lx6fhBkiu0FYqY+3+xzG14mdY= github.com/cloudflare/cloudflare-go v0.83.0 h1:aq85Hbr5W6KfXZV7v3lx6fhBkiu0FYqY+3+xzG14mdY=
github.com/cloudflare/cloudflare-go v0.83.0/go.mod h1:5pkAzpoWJYI5NekLZoRryQAcghYDhdbUxdcal1f7lu4= github.com/cloudflare/cloudflare-go v0.83.0/go.mod h1:5pkAzpoWJYI5NekLZoRryQAcghYDhdbUxdcal1f7lu4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/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.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=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
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/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-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/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/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.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -9,36 +9,56 @@ 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"
envKeyDNSProviderTag = "DNS_PROVIDER"
envKeyPublicDNSServer = "PUBLIC_DNS_SERVER"
envKeyCloudflareApiKey = "CLOUDFLARE_API_KEY" envKeyCloudflareApiKey = "CLOUDFLARE_API_KEY"
envKeyCloudflareZone = "CLOUDFLARE_ZONE" envKeyCloudflareZone = "CLOUDFLARE_ZONE"
envKeyDirectadminUser = "DA_USER"
envKeyDirectadminKey = "DA_KEY"
envKeyDirectadminUrl = "DA_URL"
envKeyOnChangeComment = "ON_CHANGE_COMMENT" envKeyOnChangeComment = "ON_CHANGE_COMMENT"
envKeyCheckIntervalSeconds = "CHECK_INTERVAL_SECONDS" envKeyCheckIntervalSeconds = "CHECK_INTERVAL_SECONDS"
envKeyNotifiers = "NOTIFIERS" envKeyNotifiers = "NOTIFIERS"
envKeyIgnoredIpChange = "IGNORED_IP_CHANGE"
) )
type Config struct { type Config struct {
DnsRecordsToCheck []string DnsRecordsToCheck []string
PublicIpResolverTag string PublicIpResolverTag string
ApiToken string PublicDNSServer string
CloudflareZone string DNSProviderTag string
OnChangeComment string DirectadminUsername string
Notifiers []string DirectadminKey string
CheckInterval time.Duration DirectadminUrl string
IgnoredIpChange []string ApiToken string
WebhookToken string
CloudflareZone string
CloudflareOnChangeComment string
Notifiers []string
CheckInterval time.Duration
} }
func (c Config) Validate() error { func (c Config) Validate() error {
if c.ApiToken == "" { switch c.DNSProviderTag {
return fmt.Errorf("empty api token env key %s", envKeyCloudflareApiKey) case "cloudflare":
} if c.ApiToken == "" {
return fmt.Errorf("empty api token env key %s", envKeyCloudflareApiKey)
}
if c.CloudflareZone == "" { case "directadmin":
return fmt.Errorf("empty zone in env key %s", envKeyCloudflareZone) if c.DirectadminUrl == "" {
return fmt.Errorf("empty DirectAdmin URL env key %s", envKeyDirectadminUrl)
}
if c.DirectadminUsername == "" {
return fmt.Errorf("empty DirectAdmin Username in env key %s", envKeyDirectadminUser)
}
if c.DirectadminKey == "" {
return fmt.Errorf("empty DirectAdmin Login Key in env key %s", envKeyDirectadminKey)
}
} }
if len(c.DnsRecordsToCheck) == 0 { if len(c.DnsRecordsToCheck) == 0 {
@@ -56,13 +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"),
CloudflareZone: os.Getenv(envKeyCloudflareZone), PublicIpResolverTag: getEnvDefault(envKeyPublicIpResolverTag, "icanhazip"),
OnChangeComment: os.Getenv(envKeyOnChangeComment), ApiToken: os.Getenv(envKeyCloudflareApiKey),
Notifiers: parseCommaDelimited(os.Getenv(envKeyNotifiers)), CloudflareZone: os.Getenv(envKeyCloudflareZone),
CheckInterval: time.Duration(checkInterval) * time.Second, CloudflareOnChangeComment: os.Getenv(envKeyOnChangeComment),
IgnoredIpChange: parseCommaDelimited(os.Getenv(envKeyIgnoredIpChange)), DirectadminUsername: os.Getenv(envKeyDirectadminUser),
DirectadminKey: os.Getenv(envKeyDirectadminKey),
DirectadminUrl: os.Getenv(envKeyDirectadminUrl),
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

@@ -0,0 +1,33 @@
package dns_providers
import (
"strings"
)
type DNSProvider interface {
UpdateRecord(hostname string, ip string) error
FetchRecord(hostname string) (string, error)
}
type DomainParts struct {
Name string
Domain string
}
func GetDomainParts(hostname string) *DomainParts {
data := arrayToSlice(strings.Split(hostname, "."))
if len(data) <= 2 {
// 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 {
return array[:]
}

View File

@@ -0,0 +1,92 @@
package dns_providers
import (
"context"
"log"
"github.com/cloudflare/cloudflare-go"
"hub.cybercinch.nz/cybercinch/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) 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.CloudflareOnChangeComment != "" {
update.Comment = &d.Config.CloudflareOnChangeComment
}
_, err = d.Client.UpdateDNSRecord(d.Context, cloudflare.ZoneIdentifier(zoneId), update)
if err != nil {
return err
}
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) {
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
}

View File

@@ -0,0 +1,91 @@
package dns_providers
import (
"context"
"fmt"
"log"
"time"
"github.com/levelzerotechnology/directadmin-go"
"hub.cybercinch.nz/cybercinch/ddns-update/internal"
)
const (
DirectadminTag = "directadmin"
)
type Directadmin struct {
Client *directadmin.UserContext
Context context.Context
Config internal.Config
}
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)
current_record, _ := d.GetDnsRecord(hostname)
// Create an updated record for the new 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, current_record[0], new_record)
if err != nil {
return err
}
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) {
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
}

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

View File

@@ -1,26 +0,0 @@
package internal
import (
"net"
"regexp"
)
func checkAddress(address, pattern string) bool {
pattern = "^" + pattern + "$"
re := regexp.MustCompile(pattern)
return re.MatchString(address)
}
func IgnoredIpChange(ip net.IP, ignored []string) bool {
if len(ignored) == 0 {
return false
}
for _, i := range ignored {
if checkAddress(ip.String(), i) {
return true
}
}
return false
}

View File

@@ -1,75 +0,0 @@
package internal
import "testing"
func Test_checkAddress(t *testing.T) {
type args struct {
address string
pattern string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "empty",
args: args{
address: "192.168.0.1",
pattern: "",
},
want: false,
}, {
name: "true match 192.*",
args: args{
address: "192.168.0.1",
pattern: "192.*",
},
want: true,
}, {
name: "false match 193.*",
args: args{
address: "192.168.0.1",
pattern: "193.*",
},
want: false,
},
{
name: "true match 192.168.0.1",
args: args{
address: "192.168.0.1",
pattern: "192.168.0.1",
},
want: true,
},
{
name: "false not match 192.168.0.2",
args: args{
address: "192.168.0.1",
pattern: "192.168.0.2",
},
want: false,
}, {
name: "true match 192.168.0.*",
args: args{
address: "192.168.0.10",
pattern: "192.168.0.*",
},
want: true,
}, {
name: "false match 192.168.0.*",
args: args{
address: "192.168.1.10",
pattern: "192.168.0.*",
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := checkAddress(tt.args.address, tt.args.pattern); got != tt.want {
t.Errorf("checkAddress() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -15,6 +15,10 @@ const (
configDelimiter = "@" configDelimiter = "@"
) )
type Doer interface {
Do(*http.Request) (*http.Response, error)
}
type Notifiers []Notifier type Notifiers []Notifier
func (n Notifiers) NotifyWithLog(ctx context.Context, notification Notification) error { func (n Notifiers) NotifyWithLog(ctx context.Context, notification Notification) error {
@@ -30,11 +34,12 @@ func (n Notifiers) NotifyWithLog(ctx context.Context, notification Notification)
} }
type Notification struct { type Notification struct {
OldIp net.IP `json:"old_ip,omitempty"` OldIp net.IP `json:"old_ip,omitempty"`
NewIp net.IP `json:"new_ip"` NewIp net.IP `json:"new_ip"`
CheckedAt time.Time `json:"checked_at"` CheckedAt time.Time `json:"checked_at"`
ResolverTag string `json:"resolver_tag"` ResolverTag string `json:"resolver_tag"`
Domain string `json:"domain"` Domain string `json:"domain"`
WebhookToken string `json:"token,omitempty"`
} }
func (n Notification) ToSlice() []string { func (n Notification) ToSlice() []string {

View File

@@ -13,10 +13,6 @@ const (
webhookTag = "webhook" webhookTag = "webhook"
) )
type Doer interface {
Do(*http.Request) (*http.Response, error)
}
type WebhookConfig struct { type WebhookConfig struct {
Url string Url string
} }
@@ -53,6 +49,8 @@ func (w WebhookNotification) Notify(ctx context.Context, notification Notificati
return fmt.Errorf("WebhookNotification::NotifyWithLog error creating request: %w", err) return fmt.Errorf("WebhookNotification::NotifyWithLog error creating request: %w", err)
} }
req.Header.Set("Content-Type", "application/json")
resp, err := w.client.Do(req) resp, err := w.client.Do(req)
if err != nil { if err != nil {
return fmt.Errorf("WebhookNotification::NotifyWithLog error while sending notification: %w", err) return fmt.Errorf("WebhookNotification::NotifyWithLog error while sending notification: %w", err)

View File

@@ -0,0 +1,31 @@
package public_resolvers
import (
"net/http"
"time"
)
const (
IcanhazipTag = "icanhazip"
IcanhazipUrl = "https://v4.icanhazip.com/"
)
type Icanhazip struct {
baseResolver
}
func NewIcanhazipDefault() *Icanhazip {
return NewIcanhazip(&http.Client{
Timeout: 10 * time.Second,
})
}
func NewIcanhazip(client Doer) *Icanhazip {
return &Icanhazip{
baseResolver: baseResolver{
client: client,
url: v4IdentMeUrl,
ipParser: defaultIpParser,
},
}
}

121
main.go
View File

@@ -8,10 +8,10 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/cloudflare/cloudflare-go" "hub.cybercinch.nz/cybercinch/ddns-update/internal"
"github.com/mkelcik/cloudflare-ddns-update/internal" "hub.cybercinch.nz/cybercinch/ddns-update/internal/dns_providers"
"github.com/mkelcik/cloudflare-ddns-update/notifications" "hub.cybercinch.nz/cybercinch/ddns-update/internal/notifications"
"github.com/mkelcik/cloudflare-ddns-update/public_resolvers" "hub.cybercinch.nz/cybercinch/ddns-update/internal/public_resolvers"
) )
type PublicIpResolver interface { type PublicIpResolver interface {
@@ -25,6 +25,8 @@ func getResolver(resolverName string) (PublicIpResolver, string) {
return public_resolvers.NewDefaultCloudflareTrace(), public_resolvers.CloudflareTraceTag return public_resolvers.NewDefaultCloudflareTrace(), public_resolvers.CloudflareTraceTag
case public_resolvers.V4IdentMeTag: case public_resolvers.V4IdentMeTag:
return public_resolvers.NewV4IdentMeDefault(), public_resolvers.V4IdentMeTag return public_resolvers.NewV4IdentMeDefault(), public_resolvers.V4IdentMeTag
case public_resolvers.IcanhazipTag:
return public_resolvers.NewIcanhazipDefault(), public_resolvers.IcanhazipTag
case public_resolvers.IfConfigMeTag: case public_resolvers.IfConfigMeTag:
fallthrough fallthrough
default: default:
@@ -32,6 +34,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()
@@ -41,73 +54,53 @@ 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)
// check if ip is not in ignore list
if internal.IgnoredIpChange(currentPublicIP, config.IgnoredIpChange) {
log.Printf("Ignored ip change `%s`, skipping.", currentPublicIP)
return
}
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, err := dnsProvider.FetchRecord(dnsRecord)
log.Printf("Checking record `%s` with current value `%s` ...", dnsRecord.Name, dnsRecord.Content) if err != nil {
if currentPublicIP.String() == dnsRecord.Content { log.Fatalf("Failed to fetch DNS record: %s", err)
log.Println("OK")
continue // no update needed
}
update := cloudflare.UpdateDNSRecordParams{
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)
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)
}
log.Printf("Updated to `%s`", currentPublicIP)
} }
log.Printf("Checking record `%s` with current value `%s` ...", dnsRecord, current_dns_record)
if currentPublicIP.String() == current_dns_record {
log.Println("OK")
continue // no update needed
}
if err := dnsProvider.UpdateRecord(dnsRecord, currentPublicIP.String()); err != nil {
log.Printf("error updating dns record: %s", err)
continue
}
if err := notifiers.NotifyWithLog(ctx, notifications.Notification{
OldIp: net.ParseIP(string(current_dns_record)),
NewIp: currentPublicIP,
CheckedAt: time.Now(),
ResolverTag: resolverTag,
Domain: dnsRecord,
WebhookToken: config.WebhookToken,
}); err != nil {
log.Printf("errors in notifications: %s", err)
}
log.Printf("Updated to `%s`", currentPublicIP)
} }
} }
@@ -127,23 +120,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
}