241 Commits

Author SHA1 Message Date
Benjamin
b53164a8fc feat(provider): add hetznercloud (#1046)
credits to @legend813 @likt0r @qdm12
2026-04-21 17:37:35 +02:00
dependabot[bot]
df18c42301 chore(deps): bump github.com/breml/rootcerts from 0.3.4 to 0.3.5 (#1126) 2026-04-21 17:23:34 +02:00
William Bout
2a474a292b feat(provider): add vercel (#1065) 2026-04-16 16:08:56 +02:00
dependabot[bot]
e8af269660 chore(deps): bump golang.org/x/oauth2 from 0.34.0 to 0.36.0 (#1115) 2026-04-16 15:08:50 +02:00
dependabot[bot]
d93186d0da chore(deps): bump docker/setup-qemu-action from 3 to 4 (#1124) 2026-04-15 14:37:20 +02:00
Florian Gareis
88b151e320 feat(provider): add ipv64 (#937) 2026-04-14 00:19:49 +02:00
dependabot[bot]
a49deb85bb chore(deps): bump docker/metadata-action from 5 to 6 (#1121) 2026-04-14 00:03:44 +02:00
dependabot[bot]
86f3737081 chore(deps): bump golang.org/x/net from 0.52.0 to 0.53.0 (#1116) 2026-04-14 00:03:33 +02:00
dependabot[bot]
e11f7263e5 chore(deps): bump github.com/miekg/dns from 1.1.70 to 1.1.72 (#1117) 2026-04-14 00:03:22 +02:00
dependabot[bot]
133c9b8c3c chore(deps): bump github.com/breml/rootcerts from 0.3.3 to 0.3.4 (#1118) 2026-04-14 00:03:12 +02:00
dependabot[bot]
5ea242b0d7 chore(deps): bump docker/build-push-action from 6 to 7 (#1119) 2026-04-14 00:02:48 +02:00
dependabot[bot]
1b21f542cc chore(deps): bump DavidAnson/markdownlint-cli2-action from 22 to 23 (#1120) 2026-04-14 00:02:39 +02:00
dependabot[bot]
7555b7ae39 chore(deps): bump docker/login-action from 3 to 4 (#1122) 2026-04-14 00:02:31 +02:00
dependabot[bot]
dece29d46a chore(deps): bump docker/setup-buildx-action from 3 to 4 (#1123) 2026-04-14 00:02:24 +02:00
Ehsan Shirvanian
02a0ddd7c1 chore(deps): upgrade oauth2 and transitive dependencies (#1071) 2026-04-11 14:51:20 +02:00
Quentin McGaw
a3a89a112c chore(ci): bump actions/checkout from v4 to v6 2026-04-11 12:46:11 +00:00
Francesco Calcavecchia
34a7f28905 feat: add scaleway registrar (#899) 2026-04-10 22:29:21 +02:00
Quentin McGaw
39bfd2e0d8 docs(all): add detail on ipv6_suffix about temporary IPv6 addresses 2026-04-10 19:52:51 +00:00
Quentin McGaw
a55ea6eb4d chore(ci): add 429 and 403 status code as valid code for markdown link check 2026-04-10 19:09:23 +00:00
dependabot[bot]
2081578060 chore(deps): bump github.com/go-chi/chi/v5 from 5.2.1 to 5.2.3 (#1081) 2026-04-10 19:16:53 +02:00
dependabot[bot]
83aa2fd8fb chore(deps): bump golang.org/x/mod from 0.31.0 to 0.32.0 (#1079) 2026-04-10 19:16:37 +02:00
dependabot[bot]
9bafa5a4ce chore(deps): bump DavidAnson/markdownlint-cli2-action from 18 to 22 (#1083) 2026-04-10 19:16:23 +02:00
dependabot[bot]
fe811a53bc chore(deps): bump github.com/stretchr/testify from 1.10.0 to 1.11.1 (#1082) 2026-04-10 19:09:05 +02:00
Quentin McGaw
509f5da273 chore(deps): tidy dependencies 2026-04-10 17:06:00 +00:00
dependabot[bot]
982de66c2c chore(deps): bump github.com/breml/rootcerts from 0.2.19 to 0.3.3 (#1080) 2026-04-10 19:04:39 +02:00
Ehsan Shirvanian
c5ff9ec308 chore(ci): update CodeQL from v3 to v4 (#1069) 2026-04-10 19:01:32 +02:00
dependabot[bot]
7c4045c538 chore(deps): bump actions/setup-go from 5 to 6 (#1084) 2026-04-10 19:00:28 +02:00
Frederic R.
730e2dda36 perf(update/logclient): reuse HTTP client transport instead of cloning it (#1095)
Fixes #1094
2026-04-10 18:58:34 +02:00
dependabot[bot]
7ff11a705c chore(deps): bump goreleaser/goreleaser-action from 6 to 7 (#1101) 2026-04-10 18:53:33 +02:00
dependabot[bot]
ec46ffa140 chore(deps): bump crazy-max/ghaction-github-labeler from 5 to 6 (#1103) 2026-04-10 18:53:22 +02:00
dependabot[bot]
486b4a1201 chore(deps): bump github.com/go-chi/chi/v5 from 5.2.0 to 5.2.2 (#1078) 2026-04-10 18:53:06 +02:00
Quentin McGaw
d0562e2a8c chore(lint): add linters godoclint, iotamixing, modernize 2026-04-10 16:52:50 +00:00
Quentin McGaw
246e3f1b70 chore(build): bump Go from 1.25 to 1.26 2026-04-10 16:52:50 +00:00
Quentin McGaw
9f7ebbd99e chore(ci): bump Alpine from 3.22 to 3.23 2026-04-10 16:52:50 +00:00
Quentin McGaw
7e47fe314f chore(dev): upgrade golang/mock to uber-go/mock v0.6.0 2026-04-10 16:52:50 +00:00
Quentin McGaw
4fb8e92b9e chore(lint): upgrade from v2.4.0 to v2.11.4 2026-04-10 16:52:50 +00:00
Quentin McGaw
189ad1f71d chore(dev): upgrade godevcontainer to v0.22
See [release notes](https://github.com/qdm12/godevcontainer/releases/tag/v0.22.0)

Notably:
- Go upgraded from 1.25 to 1.26
- golangci-lint upgraded from v2.4.0 to v2.11.4
- Alpine upgraded from 3.22 to 3.23
2026-04-10 16:52:50 +00:00
Ehsan Shirvanian
48b40e2dcc fix: program hanging if all dns resolution fail consecutively (#1099) 2026-04-09 15:56:01 +02:00
foegra
347d144b58 docs(readme): fix url to #136 to work outside Github (#1075) 2026-04-08 18:34:43 +02:00
Ehsan Shirvanian
18af1bbde7 chore(porkbun): align name as "Porkbun" in the ui (#1066) 2026-04-08 18:33:42 +02:00
Quentin McGaw
40f8009e01 docs(readme): fix linting error 2026-01-17 08:17:30 +00:00
dependabot[bot]
f04b830420 chore(deps): bump peter-evans/dockerhub-description from 4 to 5 (#1085) 2026-01-14 17:11:17 +01:00
dependabot[bot]
3ab32f83c5 chore(deps): bump github/codeql-action from 3 to 4 (#1086) 2026-01-14 17:10:55 +01:00
Michael Kölle
61e282f959 docs(ionos): Add api-key structure clarification (#996) 2026-01-13 09:49:07 -08:00
jccint
f2adf3fc20 test(config): take into account the OS specific file separator (#985) 2026-01-13 09:42:53 -08:00
Mynacol
01d3cd1383 fix(njalla): add trailing slash to URL path (#978) 2026-01-13 09:40:50 -08:00
Jeroen
2455055d20 docs(readme): add missing link to Hetzner doc (#977) 2026-01-13 09:39:36 -08:00
dependabot[bot]
7e2b4f568f chore(deps): bump golang.org/x/net from 0.33.0 to 0.38.0 (#968) 2026-01-13 09:38:40 -08:00
dependabot[bot]
d898c654c9 chore(deps): bump github.com/go-chi/chi/v5 from 5.2.0 to 5.2.1 (#933) 2026-01-13 09:38:12 -08:00
dependabot[bot]
c70265a6d3 chore(deps): bump github.com/miekg/dns from 1.1.62 to 1.1.64 (#962) 2026-01-13 09:37:15 -08:00
Joris
487873ed57 fix(desec): only update specified ip version (#961) 2026-01-13 09:36:31 -08:00
Florian Traber
b148344096 docs(inwx): clarify DynDNS account requirement (#939) 2026-01-13 09:34:52 -08:00
dependabot[bot]
4c91ef0ef8 chore(deps): bump golang.org/x/oauth2 from 0.24.0 to 0.28.0 (#952) 2026-01-13 09:27:11 -08:00
Quentin McGaw
18a5493345 chore(ci): bump linter to v2.4.0 2026-01-13 17:24:15 +00:00
Quentin McGaw
de2e675ed2 chore(all): prefer using ghcr.io images 2026-01-13 17:16:04 +00:00
Quentin McGaw
177a9a2d76 chore(ci): bump xcputranslate to v0.9.0 2026-01-13 11:52:03 +00:00
Quentin McGaw
de142bbd30 chore(build): bump Go version from 1.23 to 1.25 2026-01-13 11:50:48 +00:00
Quentin McGaw
eed96495bd chore(dev): upgrade godevcontainer to v0.21
See [release notes](https://github.com/qdm12/godevcontainer/releases/tag/v0.21.0)

Notably:
- Go upgraded from 1.23 to 1.25
- golangci-lint upgraded to v2.4.0
- Alpine upgraded from 3.20 to 3.22
- Disable package comment requirement by gopls' staticcheck
- Pull container image from ghcr.io
2026-01-13 11:32:40 +00:00
Quentin McGaw
43891cf8a6 chore(ci): use Go version from go.mod in Github actions 2026-01-13 11:30:06 +00:00
Quentin McGaw
aa0b136a7b chore(build): bump Alpine from 3.20 to 3.22 2026-01-13 11:28:47 +00:00
Quentin McGaw
3dbdc339bc chore(log): log out <none> when no IP is given to ipsToString 2026-01-13 11:26:52 +00:00
Quentin McGaw
10cfe67060 docs(readme): fix invalid URLs
- remove no longer working dockeri.co badge
- remove outdated tokei badge link
- fix docker image tags link to use GitHub Packages
2026-01-13 11:24:59 +00:00
Jorge
212841c651 fix(dondominio): change key JSON paramater from apikey to password (#969)
Fix #923
2026-01-13 03:00:08 -08:00
Simon Marty
b2a446b8c1 docs(readme): fix outdated providers list (#1031) 2025-11-01 15:17:59 -04:00
jccint
cdaf05e4ab fix(ionos): use filepath/path for joining URL paths to avoid errors in Windows (#984) 2025-11-01 15:15:34 -04:00
Ehsan Shirvanian
95bcce2809 fix(route53): don't replace wildcard character * with any when sending update request (#1036)
Co-authored-by: Ehsan Shirvanian <ehsan@shirvanian.devm>
2025-10-07 16:27:43 +00:00
Justin K
3e63897769 updated the porkbun readme so that the example sk and example api_key had the correct prefix as to not confuse new users (#995) 2025-10-07 16:24:03 +00:00
Brian Shea
20ac110753 docs(namesilo): fix typo in namesilo.com (#888) 2024-12-24 21:57:00 +01:00
hyperring
78f30614b1 feat(providers): namesilo.com support (#866)
- Credits to @Zeustopher for writing most of the readme
2024-12-24 10:18:18 +01:00
Brian Shea
03154c35f8 feat(providers): add myaddr.tools (#885) 2024-12-24 09:51:51 +01:00
Quentin McGaw
be6679273f feat(custom): allow empty ipv4 and ipv6 keys
- Fix #875
2024-12-23 08:29:01 +00:00
dependabot[bot]
6b61b8c6b8 chore(deps): bump github.com/go-chi/chi/v5 from 5.1.0 to 5.2.0 (#881) 2024-12-22 19:08:58 +01:00
dependabot[bot]
b9227d5f9d chore(deps): bump github.com/stretchr/testify from 1.9.0 to 1.10.0 (#871) 2024-12-22 19:08:50 +01:00
dependabot[bot]
395686bb47 chore(deps): bump golang.org/x/net from 0.31.0 to 0.33.0 (#883) 2024-12-22 19:08:44 +01:00
dependabot[bot]
edb5cd85b1 chore(deps): bump github.com/breml/rootcerts from 0.2.18 to 0.2.19 (#870) 2024-12-22 19:08:32 +01:00
erri120
71191fa707 fix(log): use go-chi realip middleware (#874) 2024-12-03 16:54:01 +01:00
Quentin McGaw
89564b763b fix(update): fetch IPv6 AAAA records and not only IPv4 2024-11-21 22:25:14 +00:00
Quentin McGaw
6198b48a68 chore(update): use newer LookupNetIP resolver function to use netip.Addr directly 2024-11-21 22:06:44 +00:00
Quentin McGaw
9ba1633300 fix(update): do not update if public IP is part of multiple IPs found in records 2024-11-21 22:02:09 +00:00
Quentin McGaw
6c3490f5c5 fix(netcup): allow wildcard domains (#863) 2024-11-20 15:38:22 +01:00
dependabot[bot]
56370f9ef2 chore(deps): bump golang.org/x/mod from 0.21.0 to 0.22.0 (#854) 2024-11-19 13:36:53 +01:00
dependabot[bot]
8d66333415 chore(deps): bump golang.org/x/oauth2 from 0.23.0 to 0.24.0 (#855) 2024-11-19 13:36:34 +01:00
dependabot[bot]
a0b9ed5dcb chore(deps): bump golang.org/x/net from 0.30.0 to 0.31.0 (#857) 2024-11-19 13:18:42 +01:00
dependabot[bot]
b3a992c8f4 chore(deps): bump github.com/qdm12/gosettings from 0.4.4-rc1 to 0.4.4 (#859) 2024-11-19 13:18:33 +01:00
dependabot[bot]
c085cdae2c chore(deps): bump DavidAnson/markdownlint-cli2-action from 17 to 18 (#860) 2024-11-19 13:18:20 +01:00
Quentin McGaw
8b15e35575 docs(readme): specify duckdns and goip are handled differently for multiple domains 2024-11-19 10:44:36 +00:00
Quentin McGaw
e28b86f9c0 docs(goip): add multiple examples for the domain field 2024-11-19 10:44:12 +00:00
Quentin McGaw
a6f6f48df3 docs(duckdns): add multiple examples for the domain field 2024-11-19 10:43:58 +00:00
Quentin McGaw
c5041c27e2 docs(duckdns): fix domain documentation on eTLD being duckdns.org 2024-11-19 10:43:30 +00:00
Quentin McGaw
1138de5446 docs(goip): fix domain field documentation
- eTLD must be goip.de or goip.it, not eTLD+1
- remove old `domain` optional parameter documentation
2024-11-19 10:42:49 +00:00
Quentin McGaw
9a62043ff6 fix(aliyun): error context fixed when handling bad request errors 2024-11-12 13:19:30 +00:00
Quentin McGaw
c23da1518e fix(all): trim space and lower case all response plain bodies 2024-11-12 13:04:45 +00:00
Quentin McGaw
5c9d04eae4 fix(ovh): handling of invalid JSON error bodies 2024-11-12 12:50:06 +00:00
Quentin McGaw
80670048ed fix(noip): handle response body messages before checking status code 2024-11-12 12:34:11 +00:00
Quentin McGaw
d221522e02 fix(dondominio): build host with raw owner to support wildcards 2024-11-12 12:32:53 +00:00
Quentin McGaw
9caf1bef53 fix(gandi.net): leave ttl as it is if not user specified 2024-11-07 09:22:15 +00:00
Quentin McGaw
46e1edb8f2 fix(gandi.net): update API url fix #852 2024-11-07 09:18:49 +00:00
Fred Cox
bb231bc736 fix(gcp): prevent crash for missing record (#846) 2024-10-22 17:51:24 +02:00
Amr Essam
949dcd9a30 feat(provider): vultr.com (#829) 2024-10-20 19:45:34 +02:00
Quentin McGaw
bad113b290 hotfix(dev): pin godevcontainer image tag to v0.20-alpine 2024-10-20 16:19:53 +00:00
Quentin McGaw
691ed320fd chore(lint): add new linters fatcontext, intrange and mirror 2024-10-20 13:15:20 +00:00
Quentin McGaw
8b5da2c580 chore(lint): add canonicalheader linter and fix issues 2024-10-20 13:14:02 +00:00
Quentin McGaw
05d566c807 chore(all): format code using gofumpt 2024-10-20 13:12:33 +00:00
Quentin McGaw
4c7c794494 chore(lint): upgrade linter from v1.56.2 to v1.61.0 2024-10-20 13:10:31 +00:00
Quentin McGaw
777a5e224f chore(build): upgrade Go from 1.22 to 1.23 2024-10-20 13:03:44 +00:00
Quentin McGaw
2451609cdd chore(dev): pin godevcontainer image tag to v0.20.0 2024-10-20 13:01:40 +00:00
Quentin McGaw
75f342c9ee chore(dev): small readme fixes to devcontainer 2024-10-20 12:06:50 +00:00
niklasknoell
03f5d0b372 feat(providers): loopia.se (#842) 2024-10-20 13:53:26 +02:00
Quentin McGaw
5e4bd1611a docs(dev): improve devcontainer readme 2024-10-20 11:29:32 +00:00
Quentin McGaw
e177826ae8 chore(dev): update devcontainer readme 2024-10-20 11:29:29 +00:00
Quentin McGaw
6a683f5161 chore(provider): do not re-check the owner is not empty 2024-10-20 11:29:02 +00:00
Quentin McGaw
2f44cb2afd chore(dev): migrate docker-compose.yml settings to devcontainer.json
- works with codespaces
- does not need full paths for host home directory bind mounts
2024-10-20 10:44:56 +00:00
Quentin McGaw
8747f4bd01 chore(dev): change field source.organizeImports from true to "always" 2024-10-20 10:44:56 +00:00
dependabot[bot]
74077dffac chore(deps): bump golang.org/x/net from 0.29.0 to 0.30.0 (#833) 2024-10-12 19:47:57 +02:00
likeaninja5
118132f238 fix(porkbun): update API url (#837) 2024-10-12 19:46:46 +02:00
Jonas Jacobsen
94e008dc19 feat(provider): domene.shop (#810) 2024-10-09 08:19:35 +02:00
Benjamin Temple
4254600bcf Provider Porkbun: Delete Default Parked DNS Entry for *.domain.tld (#774)
* Provider Porkbun: Delete Default Parked DNS Entry for *.domain.tld

Description:

By default, Porkbun creates default ALIAS and CNAME domain records pointing to `pixie.porkbun.com` (Porkbun's parked domain website)

The current logic flow prior to this PR would look for an A or AAAA domain record, and if none exists, attempt to delete the ALIAS record for any subdomain.
This updates the logic flow to only look for a conflicting ALIAS record for the top level `domain.tld`, and a conflicting CNAME record for the `*.domain.tld`. Additionally, we verify that the content of this record matches `pixie.porkbun.com` and we only delete for the expected default values.
If the value does not match the expected `pixie.porkbun.com` we produce more helpful error messages.

Test-Plan:

Created a new domain.tld on Porkbun
Verified the default records were created:
`ALIAS domain.tld -> pixie.porkbun.com`
`CNAME *.domain.tld -> pixie.porkbun.com`
Started DDNS-Updater
Verified that both domain records were successfully deleted and updated

Reset the ALIAS domain record to point to `not-pixie.porkbun.com`
Reset the CNAME domain record to point to `not-pixie.porkbun.com`
Started DDNS-Updater
Verified that both domain records failed with the expected conflicting record error message.

![screenshot_2024-08-17-0210 20](https://github.com/user-attachments/assets/eb567401-ad4b-454d-a7aa-70ab1db1e3e9)

- add `deleteDefaultConflictingRecordsIfNeeded` method
- handle non conflicting errors from `deleteSingleMatchingRecord`
- simplify comments by linking to documentation
- improve error wrappings

---------

Co-authored-by: Quentin McGaw <quentin.mcgaw@gmail.com>
2024-10-07 21:46:12 -07:00
Quentin McGaw
e10d778e5f feat(health): possibility to run health server outside docker and to disable it in a container
- disable health server if the health listening address:port is empty
- default health listening address:port to the empty string
- Docker default health listening address:port to 127.0.0.1:9999
- Fix #824
2024-10-01 17:17:18 +00:00
Quentin McGaw
c16287e48a fix(unix): umask behavior for file creation
- remove bad calculation of "our own" umask
- remove umask "union effect"
- do not touch system umask unless `UMASK` is set
- set system umask only if `UMASK` is set
2024-09-19 14:21:55 +00:00
dependabot[bot]
0674864e54 chore(deps): bump github.com/miekg/dns from 1.1.61 to 1.1.62 (#815) 2024-09-17 22:55:03 +02:00
Quentin McGaw
039396b68c chore(deps): run go mod tidy and pin Go version to 1.22.0 2024-09-17 20:53:59 +00:00
Quentin McGaw
e46f2f2965 docs(readme): add v2.8 release to versioned docs links 2024-09-17 20:36:29 +00:00
Quentin McGaw
4ccd53da4d docs(readme): pin v2.7 docs links to v2.7.1 release 2024-09-17 20:35:57 +00:00
Quentin McGaw
4653a484ef docs(readme): do not pin docs version to bugfix release 2024-09-17 20:35:40 +00:00
Quentin McGaw
71139938f0 docs(porkbun): fix discrepancy in parameter fields, fix #816 2024-09-17 20:34:04 +00:00
Quentin McGaw
b3b4dad0a2 docs(readme): add AUR package name to features 2024-09-17 20:17:21 +00:00
dependabot[bot]
d5515cfb2c chore(deps): bump github.com/go-chi/chi/v5 from 5.0.12 to 5.1.0 (#755) 2024-09-17 10:23:23 +02:00
dependabot[bot]
ab22578c9e chore(deps): bump DavidAnson/markdownlint-cli2-action from 16 to 17 (#811) 2024-09-17 10:23:11 +02:00
Quentin McGaw
623cb536e1 fix(publicip/http): remove google provider which no longer works 2024-09-17 07:39:00 +00:00
dependabot[bot]
13035511bb chore(deps): bump golang.org/x/mod from 0.18.0 to 0.21.0 (#801) 2024-09-16 18:06:52 +02:00
dependabot[bot]
e019e371ea chore(deps): bump github.com/qdm12/gosplash from 0.1.0 to 0.2.0 (#783) 2024-09-16 18:06:11 +02:00
dependabot[bot]
c14324153c chore(deps): bump golang.org/x/oauth2 from 0.21.0 to 0.23.0 (#802) 2024-09-16 18:04:04 +02:00
dependabot[bot]
0c77340887 chore(deps): bump github.com/breml/rootcerts from 0.2.17 to 0.2.18 (#814) 2024-09-16 18:03:50 +02:00
Quentin McGaw
9787957b94 fix(ci): ignore duckdns.org for links check 2024-09-16 14:48:36 +00:00
Quentin McGaw
3a6262ef2c feat(files): configurable UMASK defaulting to system umask 2024-09-16 14:33:37 +00:00
Quentin McGaw
6b9ed56b18 chore(persistence/json): non embedded rw mutex 2024-09-16 13:34:59 +00:00
Quentin McGaw
8c1b3e556c chore(porkbun): refactor API code to use a generic httpPost function 2024-09-16 12:46:49 +00:00
Quentin McGaw
1627254667 docs(readme): remove outdated kanban board link 2024-09-16 12:36:44 +00:00
dependabot[bot]
a8edd5ccc3 chore(deps): bump docker/build-push-action from 5 to 6 (#747) 2024-09-16 07:54:36 +02:00
dependabot[bot]
4cdc052577 chore(deps): bump golang.org/x/net from 0.26.0 to 0.29.0 (#806) 2024-09-16 07:53:46 +02:00
Quentin McGaw
bc272e079e fix(config): upgrade qdm12/gosettings from v0.4.1 to v0.4.4-rc1 2024-08-28 14:13:56 +00:00
Quentin McGaw
c7dbbcbaa0 fix(dondominio): remove unneeded name field 2024-08-21 09:35:34 +00:00
Benjamin Temple
8f456977be fix(errors): 'agend' -> 'agent' typo in user agent banned error (#790) 2024-08-17 13:32:55 +02:00
Benjamin Temple
425da967a2 fix(porkbun): fix wildcard behavior (#773) 2024-08-07 17:44:09 +02:00
Benjamin Temple
1cd57d655e fix(porkbun): remove trailing '.' from alias delete request (#775) 2024-07-27 13:52:12 +02:00
Quentin McGaw
d04c67c7ab docs(ovh): link to page to retrieve consumer_key 2024-07-26 08:11:18 +00:00
Quentin McGaw
918df24488 change(all): deprecate provider_ip config field
- change should not affect any existing configurations
- change solves issues with dual stack updates (#767)
- was unneeded and adds unneeded complexity
2024-07-26 08:06:24 +00:00
Quentin McGaw
6d70ca078c chore(build): upgrade Alpine from 3.19 to 3.20 2024-07-26 07:36:14 +00:00
Quentin McGaw
9f0ca6ceaa hotfix(ionos): settings validation 2024-07-03 19:17:49 +00:00
Quentin McGaw
7ad0d8dc57 fix(noip): force useProviderIP to false for IPv6 2024-07-03 14:55:52 +00:00
Quentin McGaw
5b800cf278 docs(readme): instructions to build latest binary 2024-06-28 06:51:07 +00:00
Quentin McGaw
1d6053e528 chore(main): rename cmd/updater to cmd/ddns-updater 2024-06-28 06:49:19 +00:00
Quentin McGaw
c345a788e3 hotfix(duckdns): fix behavior for full domain strings 2024-06-27 17:03:05 +00:00
Quentin McGaw
158fed7c51 hotfix(goip): fix behavior for full domain strings 2024-06-27 16:53:13 +00:00
Quentin McGaw
07d7645d78 feat(config): read both owner and domain from domain
- retro-compatible change: keep using host/owner if set
- otherwise extract owner and eTLD+1 from domain field
- documentation updated to only use the `domain` field
2024-06-27 13:10:16 +00:00
Quentin McGaw
55d8c0d703 docs(readme): add readme and docs/ versioned links 2024-06-27 13:01:42 +00:00
Quentin McGaw
db07ed3759 chore(deps): drop dependency on github.com/chmike/domain 2024-06-27 09:18:28 +00:00
Quentin McGaw
dbd2f79760 chore(providers): move domain check to each provider validation 2024-06-27 08:37:04 +00:00
Quentin McGaw
da4791e2db chore(providers): separate settings validation in its own function 2024-06-27 08:26:36 +00:00
Quentin McGaw
32fafeca95 chore(all): rename host to owner
- Retro-compatible change, `host` field still works
- Documentation updated to use `owner` field
- Code updated to use owner variable name
2024-06-27 06:51:08 +00:00
Quentin McGaw
711b1acfc7 chore(models): remove unused DomainHost struct 2024-06-27 06:46:45 +00:00
Quentin McGaw
339f5001e1 docs(goip): fix documentation for the host field 2024-06-27 06:41:27 +00:00
Quentin McGaw
d37f05766b chore(providers): change ttl type to uint32 2024-06-17 19:01:08 +00:00
Quentin McGaw
ca85596e19 feat(config): CONFIG_FILEPATH option 2024-06-17 19:01:06 +00:00
Quentin McGaw
cf184070b8 docs(readme): better explain container directory and file creation 2024-06-16 15:39:34 +00:00
Quentin McGaw
012a6dddcd fix(namecom): update record using "" when the host is @ 2024-06-16 08:35:04 +00:00
Marcelo HP Ferreira
d3b689d0ef feat(provider): route53 simple routing (#715) 2024-06-15 17:02:22 +02:00
Quentin McGaw
a1a9fc0b62 docs(typo): fix identifiersuffix -> identifier suffix 2024-06-15 10:57:53 +00:00
Quentin McGaw
2f88d44af7 feat(provider): add changeip.com 2024-06-15 10:57:23 +00:00
Quentin McGaw
5d74d11835 docs(readme): fix missing allinkl document link 2024-06-15 10:27:27 +00:00
Quentin McGaw
2f2bef3d6c feat(publicip): add changeip ipv4v6 echo service 2024-06-15 10:24:17 +00:00
Quentin McGaw
3494cfbcbb docs(devcontainer): change 'remote containers extension' to new 'dev containers extension' 2024-06-15 09:55:29 +00:00
Quentin McGaw
b7986dd6b9 chore(ci): pin docker/build-push-action to v5 2024-06-15 09:54:41 +00:00
dependabot[bot]
3644e5cc21 chore(deps): bump goreleaser/goreleaser-action from 5 to 6 (#737) 2024-06-15 11:53:49 +02:00
Ryan Kupka
2f252a3ec1 docs(contributing): change 'remote containers extension' to new 'dev containers extension' (#696) 2024-06-15 11:52:08 +02:00
dependabot[bot]
270014ccda chore(deps): bump github.com/go-chi/chi/v5 from 5.0.11 to 5.0.12 (#654) 2024-06-15 11:49:00 +02:00
Gottfried Mayer
b70a91582c feat(ui): ui improvements and fixes (#687)
- UI theme improved
- Dark/Light themes depending on the user's browser settings
- Mobile UI (vertical table)
- Fix serving static files (favicon did not work before)
- Add readme UI gif and mobile screenshot
2024-06-15 11:48:16 +02:00
Quentin McGaw
5b384cbf18 hotfix(ui): redirect /rooturl to /rooturl/ 2024-06-15 09:38:21 +00:00
dependabot[bot]
ff3b661d66 chore(deps): bump golang.org/x/mod from 0.15.0 to 0.18.0 (#736) 2024-06-15 10:44:25 +02:00
dependabot[bot]
b3134c7770 chore(deps): bump github.com/miekg/dns from 1.1.58 to 1.1.61 (#745) 2024-06-15 10:44:10 +02:00
dependabot[bot]
b9bbbf26d2 chore(deps): bump github.com/breml/rootcerts from 0.2.16 to 0.2.17 (#741) 2024-06-15 10:44:01 +02:00
Quentin McGaw
dfd352b817 docs(readme): reference Qnap setup guide, fix #708 2024-06-15 08:42:15 +00:00
Quentin McGaw
a84a1d664a chore(gcp): drop google sdk dependency
- depend on golang.org/x/oauth2 only
- drop image size from 17MB to 11.5MB
2024-06-15 08:36:10 +00:00
Quentin McGaw
25472f43c3 fix(ui): list IPs in reverse chronological order
- Fix #730
2024-06-14 08:49:37 +00:00
Quentin McGaw
aa4a1f3813 fix(ionos): wildcard handling 2024-06-14 08:25:51 +00:00
Quentin McGaw
76afd8361e feat(server): serve root at /rooturl on top of /rooturl/ 2024-06-14 08:13:51 +00:00
Quentin McGaw
cc995a79c8 feat(config): add SERVER_ENABLED defaulting to yes 2024-06-13 19:51:52 +00:00
Quentin McGaw
776206eec8 feat(health): only run health server when running in Docker 2024-06-13 19:35:11 +00:00
Quentin McGaw
c1bf7a49c1 fix(namecom): detect existing root host domains 2024-06-13 19:15:59 +00:00
Quentin McGaw
987138dfc1 fix(porkbun): do not add * to URL path 2024-06-13 19:01:21 +00:00
Quentin McGaw
85780dc6e1 chore(lint): add multiple linters 2024-06-13 09:23:19 +00:00
Quentin McGaw
130ab008b5 chore(all): migrate to service architecture with github.com/qdm12/goservices (#743) 2024-06-13 11:16:32 +02:00
Quentin McGaw
20792e9460 chore(lint): remove invalid config fields 2024-06-13 09:11:55 +00:00
Quentin McGaw
8e802d45ae chore(lint): upgrade to v1.56.2 2024-06-13 09:10:33 +00:00
Quentin McGaw
2b02ac154e chore(main): split main function into smaller functions 2024-06-13 09:01:38 +00:00
Quentin McGaw
8e09cd6342 change(docker): rename /updater/app -> /updater/ddns-updater
- Clearer `ddns-updater` process name than `app`
- Refers to issue #729
2024-05-23 13:30:03 +00:00
Quentin McGaw
542e89536c fix(noip): useproviderip and no ip returned case 2024-05-06 11:13:07 +00:00
Quentin McGaw
093e8154f3 feat(gcp): validate credentials JSON object has type field 2024-05-05 10:01:00 +00:00
Quentin McGaw
b131c3d90b chore(build): upgrade Go from 1.21 to 1.22 2024-05-03 07:53:18 +00:00
Quentin McGaw
d3541da812 fix(godaddy): link to comment when 403 is encountered 2024-05-02 20:44:12 +00:00
Quentin McGaw
937a249ffa feat(healthchecksio): option HEALTH_HEALTHCHECKSIO_BASE_URL 2024-04-30 15:52:47 +00:00
Quentin McGaw
11575ee82a docs(readme): clarify HEALTH_HEALTHCHECKSIO_UUID description 2024-04-30 12:58:26 +00:00
Quentin McGaw
ae4ab39421 fix(settings): trim spaces from each host value 2024-04-29 14:27:28 +00:00
dependabot[bot]
3e6336fcbc chore(deps): bump DavidAnson/markdownlint-cli2-action from 15 to 16 (#688) 2024-04-29 16:11:36 +02:00
dependabot[bot]
6d4bb626aa chore(deps): bump google.golang.org/api from 0.175.0 to 0.176.1 (#700) 2024-04-29 16:11:10 +02:00
dependabot[bot]
9b142c99dc chore(deps): bump github.com/qdm12/gosettings from 0.4.0-rc9 to 0.4.1 (#683) 2024-04-29 16:11:00 +02:00
Quentin McGaw
7d627c8581 fix(cloudflare): prevent empty key value if email is set 2024-04-29 14:10:22 +00:00
dependabot[bot]
b72711b0de chore(deps): bump google.golang.org/api from 0.114.0 to 0.175.0 (#697) 2024-04-20 06:26:19 +02:00
dependabot[bot]
8f34c766c5 chore(deps): bump google.golang.org/protobuf from 1.30.0 to 1.33.0 (#671) 2024-04-20 06:23:07 +02:00
Quentin McGaw
ae2bcd55c8 fix(custom): keep url values and only set ip 2024-04-01 13:53:07 +00:00
dependabot[bot]
dcc66fb857 chore(deps): bump github.com/stretchr/testify from 1.8.4 to 1.9.0 (#662) 2024-04-01 14:36:41 +02:00
CyberAustin
b31d848d96 chore(params): validate domain strings for providers using it (#638) 2024-04-01 14:35:10 +02:00
Quentin McGaw
99d670f7f9 docs(readme): improve description of RESOLVER_ADDRESS 2024-04-01 12:09:28 +00:00
Gottfried Mayer
a5cc9da33c docs(infomaniak): add missing details and guide (#677)
- Precise credentials are dyndns ones, not infomaniak admin ones
- Add official guide link
- Precise to set IPv4 or IPv6 address depending on which IP version you want to use
2024-03-19 15:14:18 +01:00
Quentin McGaw
877531c3d9 fix(config): allow custom urls for http ip providers 2024-03-04 14:28:58 +00:00
Quentin McGaw
bfdae74925 docs: add contributing document with example provider
- add example provider in code and docs markdown file
- merge contributing guides together
- add contributing section on adding a new provider
2024-02-29 09:25:54 +00:00
Quentin McGaw
c09e01c6c7 docs(readme): fix public ip echo custom url prefix url:https:// instead of https:// 2024-02-29 07:32:18 +00:00
Quentin McGaw
6a6b1a8ebb feat(pkg/publicip/info): add ip2location.io provider 2024-02-13 10:42:07 +00:00
Quentin McGaw
346f4aa7f7 feat(publicip/http): add seeip.org for all ip versions 2024-02-13 10:35:59 +00:00
Quentin McGaw
5e0e3f4702 feat(publicip/http): add multiple providers for all IP versions
- icanhazip
- ident
- nnev
- wtfismyip
2024-02-13 10:33:34 +00:00
Quentin McGaw
d009ef3d3e fix(inwx): allow wildcard hosts 2024-02-12 14:20:11 +00:00
Quentin McGaw
1706443ecb chore(cloudflare): unexport createRecord method 2024-02-10 14:56:01 +00:00
Quentin McGaw
ec4411e12d feat(healthchecks.io): fail and exit codes support
- notify with `/fail` suffix if any update failed
- notify with `/0` on program exit with 0 code
- notify with `/1` on program exit with 1 code
2024-02-09 13:47:29 +00:00
Quentin McGaw
1697697b81 chore(porkbun): add context to top level errors 2024-02-09 10:00:13 +00:00
Quentin McGaw
cb3075ea32 fix(noip): IPv6 query parameter fixed 2024-02-09 08:10:09 +00:00
Quentin McGaw
4499d87e05 feat(version): print version
- When first argument is `version` or `-version` or `--version`
- Print release tag version, or latest-<commithash> otherwise
2024-02-08 20:03:53 +00:00
yorickdowne
e676983dba feat(docker): /updater/data built-in with correct ownership (#634) 2024-02-08 14:07:05 +01:00
dependabot[bot]
5d550980c1 chore(deps): bump github.com/breml/rootcerts from 0.2.15 to 0.2.16 (#631) 2024-02-08 09:47:12 +01:00
dependabot[bot]
983f41743d chore(deps): bump golang.org/x/mod from 0.14.0 to 0.15.0 (#637) 2024-02-08 09:47:03 +01:00
dependabot[bot]
7bbc860225 chore(deps): bump actions/setup-go from 2 to 5 (#630) 2024-02-08 09:46:42 +01:00
dependabot[bot]
9835d5a5d7 chore(deps): bump DavidAnson/markdownlint-cli2-action from 14 to 15 (#629) 2024-02-08 09:46:34 +01:00
Quentin McGaw
463ad6ac93 chore(ovh): add no host case handling 2024-02-08 08:39:07 +00:00
Quentin McGaw
133956f082 feat(publicip): better error messages stating the provider type if unknown 2024-02-04 14:42:24 +00:00
dependabot[bot]
7aa14c8737 chore(deps): bump github.com/breml/rootcerts from 0.2.14 to 0.2.15 (#612) 2024-02-04 14:00:46 +01:00
dependabot[bot]
845770f441 chore(deps): bump peter-evans/dockerhub-description from 3 to 4 (#610) 2024-02-04 14:00:35 +01:00
dependabot[bot]
8f709d7c90 chore(deps): bump github.com/miekg/dns from 1.1.57 to 1.1.58 (#600) 2024-02-04 14:00:28 +01:00
dependabot[bot]
2af4a1bf82 chore(deps): bump actions/checkout from 3 to 4 (#599) 2024-02-04 14:00:16 +01:00
dependabot[bot]
0bb717c55a chore(deps): bump docker/login-action from 2 to 3 (#597) 2024-02-04 14:00:09 +01:00
dependabot[bot]
d07ce88aa3 chore(deps): bump crazy-max/ghaction-github-labeler from 4 to 5 (#596) 2024-02-04 14:00:00 +01:00
dependabot[bot]
860820087f chore(deps): bump docker/setup-qemu-action from 2 to 3 (#584) 2024-02-04 13:59:52 +01:00
Quentin McGaw
e793c4926b docs(readme): update readme for standalone binaries
- Update description and title to be generic and non-specific to Docker
- Describe availability as container image and prebuilt binaries
- Split features specific to the container in their own features subsection
- Merge "Next steps" section in the container setup section
2024-02-04 11:08:47 +00:00
277 changed files with 8768 additions and 3368 deletions

View File

@@ -1 +1 @@
FROM qmcgaw/godevcontainer
FROM ghcr.io/qdm12/godevcontainer:v0.22.0-alpine

View File

@@ -2,60 +2,52 @@
Development container that can be used with VSCode.
It works on Linux, Windows and OSX.
It works on Linux, Windows (WSL2) and OSX.
## Requirements
- [VS code](https://code.visualstudio.com/download) installed
- [VS code remote containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) installed
- [VS code dev containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) installed
- [Docker](https://www.docker.com/products/docker-desktop) installed and running
- [Docker Compose](https://docs.docker.com/compose/install/) installed
## Setup
1. Create the following files on your host if you don't have them:
1. Create the following files and directory on your host if you don't have them:
```sh
touch ~/.gitconfig ~/.zsh_history
mkdir -p ~/.ssh
```
Note that the development container will create the empty directories `~/.docker`, `~/.ssh` and `~/.kube` if you don't have them.
1. **For Docker on OSX or Windows without WSL**: ensure your home directory `~` is accessible by Docker.
1. **For OSX hosts**: ensure the project directory and your home directory `~` are accessible by Docker.
1. Open the command palette in Visual Studio Code (CTRL+SHIFT+P).
1. Select `Remote-Containers: Open Folder in Container...` and choose the project directory.
1. Select `Dev-Containers: Open Folder in Container...` and choose the project directory.
## Customization
### Customize the image
For any customization to take effect, you should "rebuild and reopen":
You can make changes to the [Dockerfile](Dockerfile) and then rebuild the image. For example, your Dockerfile could be:
1. Open the command palette in Visual Studio Code (CTRL+SHIFT+P)
2. Select `Dev-Containers: Rebuild Container`
```Dockerfile
FROM qmcgaw/godevcontainer
RUN apk add curl
```
Changes you can make are notably:
To rebuild the image, either:
- Changes to the Docker image in [Dockerfile](Dockerfile)
- Changes to VSCode **settings** and **extensions** in [devcontainer.json](devcontainer.json).
- Change the entrypoint script by adding a bind mount in [devcontainer.json](devcontainer.json) of a shell script to `/root/.welcome.sh` to replace the [current welcome script](https://github.com/qdm12/godevcontainer/blob/master/shell/.welcome.sh). For example:
- With VSCode through the command palette, select `Remote-Containers: Rebuild and reopen in container`
- With a terminal, go to this directory and `docker-compose build`
```json
// Welcome script
{
"source": "/yourpath/.welcome.sh",
"target": "/root/.welcome.sh",
"type": "bind"
},
```
### Customize VS code settings
You can customize **settings** and **extensions** in the [devcontainer.json](devcontainer.json) definition file.
### Entrypoint script
You can bind mount a shell script to `/root/.welcome.sh` to replace the [current welcome script](https://github.com/qdm12/basedevcontainer/blob/master/shell/.welcome.sh).
### Publish a port
To access a port from your host to your development container, publish a port in [docker-compose.yml](docker-compose.yml). You can also now do it directly with VSCode without restarting the container.
### Run other services
1. Modify [docker-compose.yml](docker-compose.yml) to launch other services at the same time as this development container, such as a test database:
- Change the `vscode` service container configuration either in [docker-compose.yml](docker-compose.yml) or in [devcontainer.json](devcontainer.json).
- Add other services in [docker-compose.yml](docker-compose.yml) to run together with the development VSCode service container. For example to add a test database:
```yml
database:
@@ -65,5 +57,4 @@ To access a port from your host to your development container, publish a port in
POSTGRES_PASSWORD: password
```
1. In [devcontainer.json](devcontainer.json), change the line `"runServices": ["vscode"],` to `"runServices": ["vscode", "database"],`.
1. In the VS code command palette, rebuild the container.
- More options are documented in the [devcontainer.json reference](https://containers.dev/implementors/json_reference/).

View File

@@ -1,16 +1,51 @@
{
// See https://containers.dev/implementors/json_reference/
// User defined settings
"containerEnv": {
"TZ": ""
},
// Fixed settings
"name": "ddns-dev",
"dockerComposeFile": [
"docker-compose.yml"
],
"service": "vscode",
"runServices": [
"vscode"
],
"shutdownAction": "stopCompose",
"postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
"dockerComposeFile": "docker-compose.yml",
"overrideCommand": true,
"service": "vscode",
"workspaceFolder": "/workspace",
// "overrideCommand": "",
"mounts": [
// Source code
{
"source": "../",
"target": "/workspace",
"type": "bind"
},
// Zsh commands history persistence
{
"source": "${localEnv:HOME}/.zsh_history",
"target": "/root/.zsh_history",
"type": "bind"
},
// Git configuration file
{
"source": "${localEnv:HOME}/.gitconfig",
"target": "/root/.gitconfig",
"type": "bind"
},
// SSH directory for Linux, OSX and WSL
// On Linux and OSX, a symlink /mnt/ssh <-> ~/.ssh is
// created in the container. On Windows, files are copied
// from /mnt/ssh to ~/.ssh to fix permissions.
{
"source": "${localEnv:HOME}/.ssh",
"target": "/mnt/ssh",
"type": "bind"
},
// Docker socket to access the host Docker server
{
"source": "/var/run/docker.sock",
"target": "/var/run/docker.sock",
"type": "bind"
}
],
"customizations": {
"vscode": {
"extensions": [
@@ -58,12 +93,16 @@
"go.lintOnSave": "package",
"[go]": {
"editor.codeActionsOnSave": {
"source.organizeImports": true
"source.organizeImports": "always"
}
},
"gopls": {
"formatting.gofumpt": true,
"usePlaceholders": false,
"staticcheck": true,
"ui.diagnostic.analyses": {
"ST1000": false
},
"vulncheck": "Imports"
},
"remote.extensionKind": {

View File

@@ -1,27 +1,3 @@
version: "3.7"
services:
vscode:
build: .
volumes:
- ../:/workspace
# Docker socket to access Docker server
- /var/run/docker.sock:/var/run/docker.sock
# SSH directory for Linux, OSX and WSL
# On Linux and OSX, a symlink /mnt/ssh <-> ~/.ssh is
# created in the container. On Windows, files are copied
# from /mnt/ssh to ~/.ssh to fix permissions.
- ~/.ssh:/mnt/ssh
# Shell history persistence
- ~/.zsh_history:/root/.zsh_history
# Git config
- ~/.gitconfig:/root/.gitconfig
environment:
- TZ=
cap_add:
# For debugging with dlv
- SYS_PTRACE
security_opt:
# For debugging with dlv
- seccomp:unconfined
entrypoint: [ "zsh", "-c", "while sleep 1000; do :; done" ]

View File

@@ -1,17 +1,92 @@
# Contributing
Contributions are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [open source license of this project](../LICENSE).
## Table of content
1. [Submitting a pull request](#submitting-a-pull-request)
1. [Development setup](#development-setup)
1. [Commands available](#commands-available)
1. [Add a new DNS provider](#add-a-new-dns-provider)
1. [License](#license)
## Submitting a pull request
1. [Fork](https://github.com/qdm12/ddns-updater/fork) and clone the repository
1. Create a new branch `git checkout -b my-branch-name`
1. Modify the code
1. Ensure the docker build succeeds `docker build .`
1. Commit your modifications
1. Push to your fork and [submit a pull request](https://github.com/qdm12/ddns-updater/compare)
## Resources
Additional resources:
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
## Development setup
### Using VSCode and Docker
That should be easier and better than a local setup, although it might use more memory if you're not on Linux.
1. Install [Docker](https://docs.docker.com/install/)
- On Windows, share a drive with Docker Desktop and have the project on that partition
- On OSX, share your project directory with Docker Desktop
1. With [Visual Studio Code](https://code.visualstudio.com/download), install the [dev containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
1. In Visual Studio Code, press on `F1` and select `Dev Containers: Open Folder in Container...`
1. Your dev environment is ready to go!... and it's running in a container :+1:
### Locally
Install [Go](https://golang.org/dl/), [Docker](https://www.docker.com/products/docker-desktop) and [Git](https://git-scm.com/downloads); then:
```sh
go mod download
```
And finally install [golangci-lint](https://github.com/golangci/golangci-lint#install).
You might want to use an editor such as [Visual Studio Code](https://code.visualstudio.com/download) with the [Go extension](https://code.visualstudio.com/docs/languages/go).
## Commands available
- Test the code: `go test ./...`
- Lint the code `golangci-lint run`
- Build the program: `go build -o app cmd/ddns-updater/main.go`
- Build the Docker image (tests and lint included): `docker build -t ghcr.io/qdm12/ddns-updater .`
- Run the Docker container: `docker run -it --rm -v /yourpath/data:/updater/data ghcr.io/qdm12/ddns-updater`
## Add a new DNS provider
An "example" DNS provider is present in the code, you can simply copy paste it modify it to your needs.
In more detailed steps:
1. Copy the directory [`internal/provider/providers/example`](../internal/provider/providers/example) to `internal/provider/providers/yourprovider` where `yourprovider` is the name of the DNS provider you want to add, in a single word without spaces, dashes or underscores.
1. Modify the `internal/provider/providers/yourprovider/provider.go` file to fit the requirements of your DNS provider. There are many `// TODO` comments you can follow and **need to remove** when done.
1. Add the provider name constant to the `ProviderChoices` function in [`internal/provider/constants/providers.go`](../internal/provider/constants/providers.go). For example:
```go
func ProviderChoices() []models.Provider {
return []models.Provider{
// ...
Example,
// ...
}
}
```
1. Add a case for your provider in the `switch` statement in the `New` function in [`internal/provider/provider.go`](../internal/provider/provider.go). For example:
```go
case constants.Example:
return example.New(data, domain, owner, ipVersion, ipv6Suffix)
```
1. Copy the file [`docs/example.md`](../docs/example.md) to `docs/yourprovider.md` and modify it to fit the configuration and domain setup of your DNS provider. There are a few `<!-- ... -->` comments indicating what to change, please **remove them** when done.
1. In the [README.md](../README.md):
1. Add your provider name to the list of providers supported `- Your provider`
1. Add your provider name and link to its document to the second list: `- [Your provider](docs/yourprovider.md)`
1. Make sure to run the actual program (in Docker or directly) and check it updates your DNS records as expected, of course 😉 You can do this by setting a record to `127.0.0.1` manually and then run the updater to see if the update succeeds.
1. Profit 🎉 Don't forget to [open a pull request](https://github.com/qdm12/ddns-updater/compare)
## License
Contributions are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [open source license of this project](../LICENSE).

View File

@@ -37,7 +37,7 @@ jobs:
env:
DOCKER_BUILDKIT: "1"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
- uses: reviewdog/action-misspell@v1
with:
@@ -72,12 +72,15 @@ jobs:
contents: read
security-events: write
steps:
- uses: actions/checkout@v3
- uses: github/codeql-action/init@v3
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- uses: github/codeql-action/init@v4
with:
languages: go
- uses: github/codeql-action/autobuild@v3
- uses: github/codeql-action/analyze@v3
- uses: github/codeql-action/autobuild@v4
- uses: github/codeql-action/analyze@v4
publish:
if: |
@@ -94,7 +97,7 @@ jobs:
packages: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
with:
fetch-depth: 0 # for gorelease last step
@@ -102,7 +105,7 @@ jobs:
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
flavor: |
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
@@ -119,15 +122,15 @@ jobs:
type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
- uses: docker/setup-qemu-action@v2
- uses: docker/setup-buildx-action@v3
- uses: docker/setup-qemu-action@v4
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v2
- uses: docker/login-action@v4
with:
username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- uses: docker/login-action@v2
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -138,7 +141,7 @@ jobs:
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
- name: Build and push final image
uses: docker/build-push-action@v5.1.0
uses: docker/build-push-action@v7
with:
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/s390x,linux/ppc64le,linux/riscv64
labels: ${{ steps.meta.outputs.labels }}
@@ -150,12 +153,12 @@ jobs:
push: true
- if: github.event_name == 'release'
uses: actions/setup-go@v2
uses: actions/setup-go@v6
with:
go-version: 1.21
go-version-file: go.mod
- if: github.event_name == 'release'
uses: goreleaser/goreleaser-action@v5
uses: goreleaser/goreleaser-action@v7
with:
version: latest
args: release --clean --config .github/workflows/configs/.goreleaser.yaml

View File

@@ -2,7 +2,7 @@ before:
hooks:
- go mod download
builds:
- main: ./cmd/updater/main.go
- main: ./cmd/ddns-updater/main.go
flags:
- -trimpath
env:

View File

@@ -4,7 +4,7 @@
"pattern": "^http://localhost"
},
{
"pattern": "^https://api6.ipify.org$"
"pattern": "^https://api(6|64).ipify.org$"
},
{
"pattern": "^http://ip1.dynupdate6.no-ip.com$"
@@ -22,7 +22,19 @@
"pattern": "https://www.linode.com/docs/products/tools/api/guides/manage-api-tokens/"
},
{
"pattern": "https://ipv6.ipleak.net/json"
"pattern": "https://(ip|ipv|v)6.+"
},
{
"pattern": "https://github.com/qdm12/ddns-updater/pkgs/container/ddns-updater"
},
{
"pattern": "^https://www.duckdns.org/$"
},
{
"pattern": "^https://my.vultr.com/settings/#settingsapi$"
},
{
"pattern": "^https://www.namesilo.com"
}
],
"timeout": "20s",
@@ -30,6 +42,8 @@
"fallbackRetryDelay": "30s",
"aliveStatusCodes": [
200,
206
206,
403,
429
]
}

View File

@@ -10,9 +10,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v6
- name: Labeler
if: success()
uses: crazy-max/ghaction-github-labeler@v4
uses: crazy-max/ghaction-github-labeler@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -18,9 +18,9 @@ jobs:
actions: read
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: DavidAnson/markdownlint-cli2-action@v14
- uses: DavidAnson/markdownlint-cli2-action@v23
with:
globs: "**.md"
config: .markdownlint.json
@@ -37,7 +37,7 @@ jobs:
use-quiet-mode: yes
config-file: .github/workflows/configs/mlc-config.json
- uses: peter-evans/dockerhub-description@v3
- uses: peter-evans/dockerhub-description@v5
if: github.repository == 'qdm12/ddns-updater' && github.event_name == 'push'
with:
username: qmcgaw

View File

@@ -1,61 +1,99 @@
linters-settings:
maligned:
suggest-new: true
misspell:
locale: US
version: "2"
issues:
exclude-rules:
- path: _test\.go
linters:
- containedctx
- dupl
- goerr113
formatters:
enable:
- gci
- gofumpt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
linters:
settings:
misspell:
locale: US
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- containedctx
- dupl
- err113
path: _test\.go
- linters:
- contextcheck
text: Function `exitHealthchecksio` should pass the context parameter
- linters:
- lll
source: See https://
- linters:
- revive
text: "var-naming: avoid meaningless package names"
path: internal\/provider\/utils\/
paths:
- third_party$
- builtin$
- examples$
enable:
# - cyclop
- asasalint
- asciicheck
- bidichk
- bodyclose
- canonicalheader
- containedctx
- contextcheck
- copyloopvar
- decorder
- dogsled
- dupl
- dupword
- durationcheck
- err113
- errchkjson
- errname
- errorlint
- execinquery
- exhaustive
- exportloopref
- fatcontext
- forcetypeassert
- gci
- gocheckcompilerdirectives
- gochecknoglobals
- gochecknoinits
- gochecksumtype
- gocognit
- goconst
- gocritic
- gocyclo
- godoclint
- godot
- goerr113
- goheader
- goimports
- gomnd
- gomoddirectives
- goprintffuncname
- gosec
- gosmopolitan
- grouper
- importas
- inamedparam
- interfacebloat
- intrange
- iotamixing
- ireturn
- lll
- maintidx
- makezero
- mirror
- misspell
- mnd
- modernize
- musttag
- nakedret
- nestif
@@ -65,14 +103,17 @@ linters:
- nolintlint
- nosprintfhostport
- paralleltest
- perfsprint
- prealloc
- predeclared
- promlinter
- protogetter
- reassign
- revive
- rowserrcheck
- sloglint
- sqlclosecheck
- tenv
- tagalign
- thelper
- tparallel
- unconvert
@@ -80,8 +121,4 @@ linters:
- usestdlibvars
- wastedassign
- whitespace
run:
skip-dirs:
- .devcontainer
- .github
- zerologlint

View File

@@ -1,13 +1,13 @@
ARG BUILDPLATFORM=linux/amd64
ARG ALPINE_VERSION=3.19
ARG GO_VERSION=1.21
ARG XCPUTRANSLATE_VERSION=v0.6.0
ARG GOLANGCI_LINT_VERSION=v1.55.2
ARG MOCKGEN_VERSION=v1.6.0
ARG ALPINE_VERSION=3.23
ARG GO_VERSION=1.26
ARG XCPUTRANSLATE_VERSION=v0.9.0
ARG GOLANGCI_LINT_VERSION=v2.11.4
ARG MOCKGEN_VERSION=v0.6.0
FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:golangci-lint-${GOLANGCI_LINT_VERSION} AS golangci-lint
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:mockgen-${MOCKGEN_VERSION} AS mockgen
FROM --platform=${BUILDPLATFORM} ghcr.io/qdm12/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
FROM --platform=${BUILDPLATFORM} ghcr.io/qdm12/binpot:golangci-lint-${GOLANGCI_LINT_VERSION} AS golangci-lint
FROM --platform=${BUILDPLATFORM} ghcr.io/qdm12/binpot:mockgen-${MOCKGEN_VERSION} AS mockgen
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS base
WORKDIR /tmp/gobuild
@@ -50,6 +50,7 @@ RUN git init && \
rm -rf .git/
FROM --platform=$BUILDPLATFORM base AS build
RUN mkdir -p /tmp/data
ARG VERSION=unknown
ARG CREATED="an unknown date"
ARG COMMIT=unknown
@@ -60,15 +61,17 @@ RUN GOARCH="$(xcputranslate translate -targetplatform ${TARGETPLATFORM} -field a
-X 'main.version=$VERSION' \
-X 'main.date=$CREATED' \
-X 'main.commit=$COMMIT' \
" -o app cmd/updater/main.go
" -o app cmd/ddns-updater/main.go
FROM scratch
EXPOSE 8000
HEALTHCHECK --interval=60s --timeout=5s --start-period=10s --retries=2 CMD ["/updater/app", "healthcheck"]
HEALTHCHECK --interval=60s --timeout=5s --start-period=10s --retries=2 CMD ["/updater/ddns-updater", "healthcheck"]
ARG UID=1000
ARG GID=1000
USER ${UID}:${GID}
ENTRYPOINT ["/updater/app"]
WORKDIR /updater
ENTRYPOINT ["/updater/ddns-updater"]
COPY --from=build --chown=${UID}:${GID} /tmp/data /updater/data
ENV \
# Core
CONFIG= \
@@ -82,9 +85,11 @@ ENV \
PUBLICIP_DNS_TIMEOUT=3s \
HTTP_TIMEOUT=10s \
DATADIR=/updater/data \
CONFIG_FILEPATH=/updater/data/config.json \
RESOLVER_ADDRESS= \
RESOLVER_TIMEOUT=5s \
# Web UI
SERVER_ENABLED=yes \
LISTENING_ADDRESS=:8000 \
ROOT_URL=/ \
# Backup
@@ -96,7 +101,10 @@ ENV \
SHOUTRRR_ADDRESSES= \
SHOUTRRR_DEFAULT_TITLE="DDNS Updater" \
TZ= \
# UMASK left empty so it dynamically defaults to the OS current umask
UMASK= \
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
HEALTH_HEALTHCHECKSIO_BASE_URL=https://hc-ping.com \
HEALTH_HEALTHCHECKSIO_UUID=
ARG VERSION=unknown
ARG CREATED="an unknown date"
@@ -111,4 +119,4 @@ LABEL \
org.opencontainers.image.source="https://github.com/qdm12/ddns-updater" \
org.opencontainers.image.title="ddns-updater" \
org.opencontainers.image.description="Universal DNS updater with WebUI"
COPY --from=build --chown=${UID}:${GID} /tmp/gobuild/app /updater/app
COPY --from=build --chown=${UID}:${GID} /tmp/gobuild/app /updater/ddns-updater

216
README.md
View File

@@ -1,15 +1,13 @@
# Lightweight universal DDNS Updater with Docker and web UI
# Lightweight universal DDNS Updater program
Light container updating DNS A and/or AAAA records periodically for multiple DNS providers
Program to keep DNS A and/or AAAA records updated for multiple DNS providers
<img height="200" alt="DDNS Updater logo" src="https://raw.githubusercontent.com/qdm12/ddns-updater/master/readme/ddnsgopher.svg">
[![Build status](https://github.com/qdm12/ddns-updater/actions/workflows/build.yml/badge.svg)](https://github.com/qdm12/ddns-updater/actions/workflows/build.yml)
[![dockeri.co](https://dockeri.co/image/qmcgaw/ddns-updater)](https://hub.docker.com/r/qmcgaw/ddns-updater)
![Last release](https://img.shields.io/github/release/qdm12/ddns-updater?label=Last%20release)
![Last Docker tag](https://img.shields.io/docker/v/qmcgaw/ddns-updater?sort=semver&label=Last%20Docker%20tag)
![Last Docker tag](https://img.shields.io/github/v/release/qdm12/ddns-updater?sort=semver&label=Last%20Docker%20tag)
[![Last release size](https://img.shields.io/docker/image-size/qmcgaw/ddns-updater?sort=semver&label=Last%20released%20image)](https://hub.docker.com/r/qmcgaw/ddns-updater/tags?page=1&ordering=last_updated)
![GitHub last release date](https://img.shields.io/github/release-date/qdm12/ddns-updater?label=Last%20release%20date)
![Commits since release](https://img.shields.io/github/commits-since/qdm12/ddns-updater/latest?sort=semver)
@@ -22,7 +20,6 @@ Light container updating DNS A and/or AAAA records periodically for multiple DNS
[![GitHub issues](https://img.shields.io/github/issues/qdm12/ddns-updater.svg)](https://github.com/qdm12/ddns-updater/issues)
[![GitHub closed issues](https://img.shields.io/github/issues-closed/qdm12/ddns-updater.svg)](https://github.com/qdm12/ddns-updater/issues?q=is%3Aissue+is%3Aclosed)
[![Lines of code](https://img.shields.io/tokei/lines/github/qdm12/ddns-updater)](https://github.com/qdm12/ddns-updater)
![Code size](https://img.shields.io/github/languages/code-size/qdm12/ddns-updater)
![GitHub repo size](https://img.shields.io/github/repo-size/qdm12/ddns-updater)
![Go version](https://img.shields.io/github/go-mod/go-version/qdm12/ddns-updater)
@@ -30,16 +27,33 @@ Light container updating DNS A and/or AAAA records periodically for multiple DNS
[![MIT](https://img.shields.io/github/license/qdm12/ddns-updater)](LICENSE)
![Visitors count](https://visitor-badge.laobi.icu/badge?page_id=ddns-updater.readme)
## Versioned documentation
This readme and the [docs/](docs/) directory are **versioned** to match the program version:
| Version | Readme link | Docs link |
| --- | --- | --- |
| Latest | [README](https://github.com/qdm12/ddns-updater/blob/master/README.md) | [docs/](https://github.com/qdm12/ddns-updater/tree/master/docs) |
| `v2.8` | [README](https://github.com/qdm12/ddns-updater/blob/v2.8.0/README.md) | [docs/](https://github.com/qdm12/ddns-updater/blob/v2.8.0/docs) |
| `v2.7` | [README](https://github.com/qdm12/ddns-updater/blob/v2.7.1/README.md) | [docs/](https://github.com/qdm12/ddns-updater/blob/v2.7.1/docs) |
| `v2.6` | [README](https://github.com/qdm12/ddns-updater/blob/v2.6.1/README.md) | [docs/](https://github.com/qdm12/ddns-updater/blob/v2.6.1/docs) |
| `v2.5` | [README](https://github.com/qdm12/ddns-updater/blob/v2.5.0/README.md) | [docs/](https://github.com/qdm12/ddns-updater/blob/v2.5.0/docs) |
## Features
- Available as a Docker image [`ghcr.io/qdm12/ddns-updater`]((https://github.com/qdm12/ddns-updater/pkgs/container/ddns-updater)) and [`qmcgaw/ddns-updater`](https://hub.docker.com/r/qmcgaw/ddns-updater)
- Available as [zero-dependency binaries for Linux, Windows and MacOS](https://github.com/qdm12/ddns-updater/releases)
- 🆕 Available in the AUR as [`ddns-updater`](https://aur.archlinux.org/packages/ddns-updater) - see [#808](https://github.com/qdm12/ddns-updater/discussions/808)
- Updates periodically A records for different DNS providers:
- Aliyun
- AllInkl
- ChangeIP
- Cloudflare
- DD24
- DDNSS.de
- deSEC
- DigitalOcean
- Domeneshop
- DonDominio
- DNSOMatic
- DNSPod
@@ -47,6 +61,7 @@ Light container updating DNS A and/or AAAA records periodically for multiple DNS
- DuckDNS
- DynDNS
- Dynu
- DynV6
- EasyDNS
- FreeDNS
- Gandi
@@ -54,14 +69,19 @@ Light container updating DNS A and/or AAAA records periodically for multiple DNS
- GoDaddy
- GoIP.de
- He.net
- Hetzner
- Hetzner (legacy API)
- Hetzner Cloud
- Infomaniak
- INWX
- Ionos
- Ionos
- ipv64
- Linode
- Loopia
- LuaDNS
- Myaddr
- Name.com
- Namecheap
- NameSilo
- Netcup
- NoIP
- Now-DNS
@@ -69,51 +89,90 @@ Light container updating DNS A and/or AAAA records periodically for multiple DNS
- OpenDNS
- OVH
- Porkbun
- Route53
- Scaleway
- Selfhost.de
- Servercow.de
- Spdyn
- Strato.de
- Variomedia.de
- Vultr
- Zoneedit
- **Want more?** [Create an issue for it](https://github.com/qdm12/ddns-updater/issues/new/choose)!
- Web User interface
- Web user interface (Desktop)
![Web UI](https://raw.githubusercontent.com/qdm12/ddns-updater/master/readme/webui.png)
![Web UI](readme/webui-desktop.gif)
- Web user interface (Mobile)
![Mobile Web UI](readme/webui-mobile.png)
- 11MB Docker image based on a Go static binary in a Scratch Docker image
- Persistence with a JSON file *updates.json* to store old IP addresses with change times for each record
- Docker healthcheck verifying the DNS resolution of your domains
- Highly configurable
- Send notifications with [**Shoutrrr**](https://containrrr.dev/shoutrrr/v0.8/services/overview/) using `SHOUTRRR_ADDRESSES`
- Compatible with `amd64`, `386`, `arm64`, `armv7`, `armv6`, `s390x`, `ppc64le`, `riscv64` CPU architectures.
- Container (Docker/K8s) specific features:
- Lightweight 12MB Docker image based on the Scratch Docker image
- Docker healthcheck verifying the DNS resolution of your domains
- Images compatible with `amd64`, `386`, `arm64`, `armv7`, `armv6`, `s390x`, `ppc64le`, `riscv64` CPU architectures
- Persistence with a JSON file *updates.json* to store old IP addresses with change times for each record
## Setup
The program reads the configuration from a JSON object, either from a file or from an environment variable.
### Binary programs
1. Create a directory of your choice, say *data* with a file named **config.json** inside:
```sh
mkdir data
touch data/config.json
# Owned by user ID of Docker container (1000)
chown -R 1000 data
# all access (for creating json database file data/updates.json)
chmod 700 data
# read access only
chmod 400 data/config.json
```
If you want to use another user ID, [build the image yourself](#build-the-image) with `--build-arg UID=<your-uid>`. You could also just run the container as root with `--user="0"` but this is not advised security wise.
1. Write a JSON configuration in *data/config.json*, for example:
1. Download the pre-built program for your platform from the assets of a release in the [releases page](https://github.com/qdm12/ddns-updater/releases). You can alternatively download, build and install the latest version of the program by installing [Go](https://golang.org/doc/install) and then run `go install github.com/qdm12/ddns-updater/cmd/ddns-updater@latest`.
1. For Linux and MacOS, make the program executable with `chmod +x ddns-updater`.
1. In the directory where the program is saved, create a directory `data`.
1. Write a JSON configuration in `data/config.json`, for example:
```json
{
"settings": [
{
"provider": "namecheap",
"domain": "example.com",
"host": "@",
"domain": "sub.example.com",
"password": "e5322165c1d74692bfa6d807100c0310"
}
]
}
```
You can find more information in the [configuration section](#configuration) to customize it.
1. Run the program with `./ddns-updater` (`./ddns-updater.exe` on Windows) or by double-clicking on it.
1. The following is **optional**.
- You can customize the program behavior using either [environment variables](#environment-variables) or flags. For flags, there is a flag corresponding to each environment variable, where it's all lowercase and underscores are replaced with dashes. For example the environment variable `LOG_LEVEL` translates into `--log-level`.
### Container
[➡️ Qnap guide by @Araminta](https://github.com/qdm12/ddns-updater/issues/708)
1. Create a directory, for example, *data* which is:
- owned by user id `1000`, which is the built-in user ID of the ddns-updater container
- has user read+write+execute permissions
```sh
mkdir data
chown 1000 data
chmod u+r+w+x data
```
If you want to use another user ID, [build the image yourself](#build-the-image) with `--build-arg UID=<your-uid>`. You could also just run the container as root with `--user="0"` but this is not advised security wise.
1. Similarly, create a *data/config.json* file which is:
- owned by user id `1000`
- has user read permissions
```sh
touch data/config.json
chmod u+r data/config.json
```
1. Edit *data/config.json*, for example:
```json
{
"settings": [
{
"provider": "namecheap",
"domain": "sub.example.com",
"password": "e5322165c1d74692bfa6d807100c0310"
}
]
@@ -125,30 +184,16 @@ The program reads the configuration from a JSON object, either from a file or fr
1. Run the container with
```sh
docker run -d -p 8000:8000/tcp -v "$(pwd)"/data:/updater/data qmcgaw/ddns-updater
docker run -d -p 8000:8000/tcp -v "$(pwd)"/data:/updater/data ghcr.io/qdm12/ddns-updater
```
1. (Optional) You can also set your JSON configuration as a single environment variable line (i.e. `{"settings": [{"provider": "namecheap", ...}]}`), which takes precedence over config.json. Note however that if you don't bind mount the `/updater/data` directory, there won't be a persistent database file `/updater/updates.json` but it will still work.
### Next steps
#### Docker-Compose
You can also use [docker-compose.yml](docker-compose.yml) with:
```sh
docker-compose up -d
```
You can update the image with `docker pull qmcgaw/ddns-updater`. Other [Docker image tags are available](https://hub.docker.com/repository/docker/qmcgaw/ddns-updater/tags).
#### Kubernetes
Check out the [k8s directory](k8s) for an installation guide and examples.
### GHCR
Images are also added to the Github Container Registry. To use the GHCR container replace `qmcgaw/ddns-updater` to `ghcr.io/qdm12/ddns-updater`, further details are available [here](https://github.com/qdm12/ddns-updater/pkgs/container/ddns-updater)
1. The following is **optional**.
- You can customize the program behavior using [environment variables](#environment-variables)
- You can use [docker-compose.yml](docker-compose.yml) with `docker-compose up -d`
- **Kubernetes**: check out the [k8s directory](k8s) for an installation guide and examples.
- Other [Docker image tags are available](https://github.com/qdm12/ddns-updater/pkgs/container/ddns-updater)
- You can update the image with `docker pull ghcr.io/qdm12/ddns-updater`
- You can set your JSON configuration as a single environment variable line (i.e. `{"settings": [{"provider": "namecheap", ...}]}`), which takes precedence over config.json. Note however that if you don't bind mount the `/updater/data` directory, there won't be a persistent database file `/updater/updates.json` but it will still work.
## Configuration
@@ -171,12 +216,14 @@ For each setting, you need to fill in parameters.
Check the documentation for your DNS provider:
- [Aliyun](docs/aliyun.md)
- [Allinkl](docs/allinkl.md)
- [ChangeIP](docs/changeip.md)
- [Cloudflare](docs/cloudflare.md)
- [Custom](docs/custom.md)
- [DD24](docs/dd24.md)
- [DDNSS.de](docs/ddnss.de.md)
- [deSEC](docs/desec.md)
- [DigitalOcean](docs/digitalocean.md)
- [DD24](docs/dd24.md)
- [Domeneshop](docs/domeneshop.md)
- [DonDominio](docs/dondominio.md)
- [DNSOMatic](docs/dnsomatic.md)
- [DNSPod](docs/dnspod.md)
@@ -192,13 +239,19 @@ Check the documentation for your DNS provider:
- [GoDaddy](docs/godaddy.md)
- [GoIP.de](docs/goip.md)
- [He.net](docs/he.net.md)
- [Hetzner](docs/hetzner.md)
- [HetznerCloud](docs/hetznercloud.md)
- [Infomaniak](docs/infomaniak.md)
- [INWX](docs/inwx.md)
- [Ionos](docs/ionos.md)
- [IPv64](docs/ipv64.md)
- [Linode](docs/linode.md)
- [Loopia](docs/loopia.md)
- [LuaDNS](docs/luadns.md)
- [Myaddr](docs/myaddr.md)
- [Name.com](docs/name.com.md)
- [Namecheap](docs/namecheap.md)
- [NameSilo](docs/namesilo.md)
- [Netcup](docs/netcup.md)
- [NoIP](docs/noip.md)
- [Now-DNS](docs/nowdns.md)
@@ -206,20 +259,25 @@ Check the documentation for your DNS provider:
- [OpenDNS](docs/opendns.md)
- [OVH](docs/ovh.md)
- [Porkbun](docs/porkbun.md)
- [Route53](docs/route53.md)
- [Scaleway](docs/scaleway.md)
- [Selfhost.de](docs/selfhost.de.md)
- [Servercow.de](docs/servercow.md)
- [Spdyn](docs/spdyn.md)
- [Strato.de](docs/strato.md)
- [Variomedia.de](docs/variomedia.md)
- [Vultr](docs/vultr.md)
- [Zoneedit](docs/zoneedit.md)
- [Custom](docs/custom.md)
Note that:
- you can specify multiple hosts for the same domain using a comma separated list. For example with `"host": "@,subdomain1,subdomain2",`.
- you can specify multiple owners/hosts for the same domain using a comma separated list. For example with `"domain": "example.com,sub.example.com,sub2.example.com",`.
⚠️ this is a bit different for DuckDNS and GoIP, see their respective documentation.
### Environment variables
🆕 There are now flags equivalent for each variable below, for example `--ipv6-prefix`.
🆕 There are now flags equivalent for each variable below, for example `--log-level`.
| Environment variable | Default | Description |
| --- | --- | --- |
@@ -233,19 +291,23 @@ Note that:
| `PUBLICIP_DNS_TIMEOUT` | `3s` | Public IP DNS query timeout |
| `UPDATE_COOLDOWN_PERIOD` | `5m` | Duration to cooldown between updates for each record. This is useful to avoid being rate limited or banned. |
| `HTTP_TIMEOUT` | `10s` | Timeout for all HTTP requests |
| `SERVER_ENABLED` | `yes` | Enable the web server and web UI |
| `LISTENING_ADDRESS` | `:8000` | Internal TCP listening port for the web UI |
| `ROOT_URL` | `/` | URL path to append to all paths to the webUI (i.e. `/ddns` for accessing `https://example.com/ddns` through a proxy) |
| `HEALTH_SERVER_ADDRESS` | `127.0.0.1:9999` | Health server listening address |
| `HEALTH_HEALTHCHECKSIO_UUID` | | UUID for [healthchecks.io](https://healthchecks.io) to send a heartbeat on every update check |
| `HEALTH_HEALTHCHECKSIO_BASE_URL` | `https://hc-ping.com` | Base URL for the [healthchecks.io](https://healthchecks.io) server |
| `HEALTH_HEALTHCHECKSIO_UUID` | | UUID to idenfity with the [healthchecks.io](https://healthchecks.io) server |
| `DATADIR` | `/updater/data` | Directory to read and write data files from internally |
| `CONFIG_FILEPATH` | `/updater/data/config.json` | Path to the JSON configuration file |
| `BACKUP_PERIOD` | `0` | Set to a period (i.e. `72h15m`) to enable zip backups of data/config.json and data/updates.json in a zip file |
| `BACKUP_DIRECTORY` | `/updater/data` | Directory to write backup zip files to if `BACKUP_PERIOD` is not `0`. |
| `RESOLVER_ADDRESS` | Your network DNS | A plaintext DNS address to use, such as `1.1.1.1:53`. This is useful for split dns, see [#389](https://github.com/qdm12/ddns-updater/issues/389) |
| `RESOLVER_ADDRESS` | Your network DNS | A plaintext DNS address to use to resolve your domain names defined in your settings only. For example it can be `1.1.1.1:53`. This is useful for split dns, see [#389](https://github.com/qdm12/ddns-updater/issues/389) |
| `LOG_LEVEL` | `info` | Level of logging, `debug`, `info`, `warning` or `error` |
| `LOG_CALLER` | `hidden` | Show caller per log line, `hidden` or `short` |
| `SHOUTRRR_ADDRESSES` | | (optional) Comma separated list of [Shoutrrr addresses](https://containrrr.dev/shoutrrr/v0.8/services/overview/) (notification services) |
| `SHOUTRRR_ADDRESSES` | | (optional) Comma separated list of [Shoutrrr addresses](https://containrrr.dev/shoutrrr/v0.8/services/overview/) (notification services) |
| `SHOUTRRR_DEFAULT_TITLE` | `DDNS Updater` | Default title for Shoutrrr notifications |
| `TZ` | | Timezone to have accurate times, i.e. `America/Montreal` |
| `UMASK` | System current umask | Umask to set for the program in octal, i.e. `0022` |
#### Public IP
@@ -261,19 +323,34 @@ You can otherwise customize it with the following:
- `ipify` using [https://api64.ipify.org](https://api64.ipify.org)
- `ifconfig` using [https://ifconfig.io/ip](https://ifconfig.io/ip)
- `ipinfo` using [https://ipinfo.io/ip](https://ipinfo.io/ip)
- `google` using [https://domains.google.com/checkip](https://domains.google.com/checkip)
- `spdyn` using [https://checkip.spdyn.de](https://checkip.spdyn.de/)
- `ipleak` using [https://ipleak.net/json](https://ipleak.net/json)
- You can also specify an HTTPS URL such as `https://ipinfo.io/ip`
- `icanhazip` using [https://icanhazip.com](https://icanhazip.com)
- `ident` using [https://ident.me](https://ident.me)
- `nnev` using [https://ip.nnev.de](https://ip.nnev.de)
- `wtfismyip` using [https://wtfismyip.com/text](https://wtfismyip.com/text)
- `seeip` using [https://api.seeip.org](https://api.seeip.org)
- `changeip` using [https://ip.changeip.com](https://ip.changeip.com)
- You can also specify an HTTPS URL with prefix `url:` for example `url:https://ipinfo.io/ip`
- `PUBLICIPV4_HTTP_PROVIDERS` gets your public IPv4 address only. It can be one or more of the following:
- `ipleak` using [https://ipv4.ipleak.net/json](https://ipv4.ipleak.net/json)
- `ipify` using [https://api.ipify.org](https://api.ipify.org)
- You can also specify an HTTPS URL such as `https://ipinfo.io/ip`
- `icanhazip` using [https://ipv4.icanhazip.com](https://ipv4.icanhazip.com)
- `ident` using [https://v4.ident.me](https://v4.ident.me)
- `nnev` using [https://ip4.nnev.de](https://ip4.nnev.de)
- `wtfismyip` using [https://ipv4.wtfismyip.com/text](https://ipv4.wtfismyip.com/text)
- `seeip` using [https://ipv4.seeip.org](https://ipv4.seeip.org)
- You can also specify an HTTPS URL with prefix `url:` for example `url:https://ipinfo.io/ip`
- `PUBLICIPV6_HTTP_PROVIDERS` gets your public IPv6 address only. It can be one or more of the following:
- `ipleak` using [https://ipv6.ipleak.net/json](https://ipv6.ipleak.net/json)
- `ipify` using [https://api6.ipify.org](https://api6.ipify.org)
- You can also specify an HTTPS URL such as `https://ipinfo.io/ip`
- `PUBLICIP_DNS_PROVIDERS` gets your public IPv4 address only or IPv6 address only or one of them (see #136). It can be one or more of the following:
- `icanhazip` using [https://ipv6.icanhazip.com](https://ipv6.icanhazip.com)
- `ident` using [https://v6.ident.me](https://v6.ident.me)
- `nnev` using [https://ip6.nnev.de](https://ip6.nnev.de)
- `wtfismyip` using [https://ipv6.wtfismyip.com/text](https://ipv6.wtfismyip.com/text)
- `seeip` using [https://ipv6.seeip.org](https://ipv6.seeip.org)
- You can also specify an HTTPS URL with prefix `url:` for example `url:https://ipinfo.io/ip`
- `PUBLICIP_DNS_PROVIDERS` gets your public IPv4 address only or IPv6 address only or one of them (see [#136](https://github.com/qdm12/ddns-updater/issues/136)). It can be one or more of the following:
- `cloudflare`
- `opendns`
@@ -333,7 +410,7 @@ We could do an API call to get the record IP address every period, but that woul
You can build the image yourself with:
```sh
docker build -t qmcgaw/ddns-updater https://github.com/qdm12/ddns-updater.git
docker build -t ghcr.io/qdm12/ddns-updater https://github.com/qdm12/ddns-updater.git
```
You can use optional build arguments with `--build-arg KEY=VALUE` from the table below:
@@ -348,10 +425,9 @@ You can use optional build arguments with `--build-arg KEY=VALUE` from the table
## Development and contributing
- [Contribute with code](docs/contributing.md)
- [Contribute with code](.github/CONTRIBUTING.md)
- [Github workflows to know what's building](https://github.com/qdm12/ddns-updater/actions)
- [List of issues and feature requests](https://github.com/qdm12/ddns-updater/issues)
- [Kanban board](https://github.com/qdm12/ddns-updater/projects/1)
## License

View File

@@ -6,7 +6,6 @@ import (
"net/http"
"os"
"os/signal"
"path/filepath"
"strconv"
"syscall"
"time"
@@ -19,16 +18,19 @@ import (
"github.com/qdm12/ddns-updater/internal/health"
"github.com/qdm12/ddns-updater/internal/healthchecksio"
"github.com/qdm12/ddns-updater/internal/models"
"github.com/qdm12/ddns-updater/internal/noop"
jsonparams "github.com/qdm12/ddns-updater/internal/params"
persistence "github.com/qdm12/ddns-updater/internal/persistence/json"
"github.com/qdm12/ddns-updater/internal/provider"
recordslib "github.com/qdm12/ddns-updater/internal/records"
"github.com/qdm12/ddns-updater/internal/resolver"
"github.com/qdm12/ddns-updater/internal/server"
"github.com/qdm12/ddns-updater/internal/shoutrrr"
"github.com/qdm12/ddns-updater/internal/system"
"github.com/qdm12/ddns-updater/internal/update"
"github.com/qdm12/ddns-updater/pkg/publicip"
"github.com/qdm12/goservices"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/goshutdown"
"github.com/qdm12/gosplash"
"github.com/qdm12/log"
)
@@ -44,7 +46,7 @@ func main() {
buildInfo := models.BuildInformation{
Version: version,
Commit: commit,
Date: date,
Created: date,
}
logger := log.New()
@@ -97,59 +99,41 @@ func main() {
}
func _main(ctx context.Context, reader *reader.Reader, args []string, logger log.LoggerInterface,
buildInfo models.BuildInformation, timeNow func() time.Time) (err error) {
if health.IsClientMode(args) {
// Running the program in a separate instance through the Docker
// built-in healthcheck, in an ephemeral fashion to query the
// long running instance of the program about its status
buildInfo models.BuildInformation, timeNow func() time.Time,
) (err error) {
if len(args) > 1 {
switch args[1] {
case "version", "-version", "--version":
fmt.Println(buildInfo.VersionString())
return nil
case "healthcheck":
// Running the program in a separate instance through the Docker
// built-in healthcheck, in an ephemeral fashion to query the
// long running instance of the program about its status
var healthSettings config.Health
healthSettings.Read(reader)
healthSettings.SetDefaults()
err = healthSettings.Validate()
if err != nil {
return fmt.Errorf("health settings: %w", err)
var healthSettings config.Health
healthSettings.Read(reader)
healthSettings.SetDefaults()
err = healthSettings.Validate()
if err != nil {
return fmt.Errorf("health settings: %w", err)
}
client := health.NewClient()
return client.Query(ctx, *healthSettings.ServerAddress)
}
client := health.NewClient()
return client.Query(ctx, *healthSettings.ServerAddress)
}
announcementExp, err := time.Parse(time.RFC3339, "2023-07-15T00:00:00Z")
printSplash(buildInfo)
config, err := readConfig(reader, logger)
if err != nil {
return err
}
splashSettings := gosplash.Settings{
User: "qdm12",
Repository: "ddns-updater",
Emails: []string{"quentin.mcgaw@gmail.com"},
Version: buildInfo.Version,
Commit: buildInfo.Commit,
BuildDate: buildInfo.Date,
Announcement: "Public IP dns provider GOOGLE, see https://github.com/qdm12/ddns-updater/issues/492",
AnnounceExp: announcementExp,
// Sponsor information
PaypalUser: "qmcgaw",
GithubSponsor: "qdm12",
}
for _, line := range gosplash.MakeLines(splashSettings) {
fmt.Println(line)
}
var config config.Config
err = config.Read(reader, logger)
if err != nil {
return fmt.Errorf("reading settings: %w", err)
if *config.Paths.Umask > 0 {
system.SetUmask(*config.Paths.Umask)
}
config.SetDefaults()
err = config.Validate()
if err != nil {
return fmt.Errorf("settings validation: %w", err)
}
logger.Patch(config.Logger.ToOptions()...)
logger.Info(config.String())
shoutrrrSettings := shoutrrr.Settings{
Addresses: config.Shoutrrr.Addresses,
@@ -168,8 +152,7 @@ func _main(ctx context.Context, reader *reader.Reader, args []string, logger log
}
jsonReader := jsonparams.NewReader(logger)
jsonFilepath := filepath.Join(*config.Paths.DataDir, "config.json")
providers, warnings, err := jsonReader.JSONProviders(jsonFilepath)
providers, warnings, err := jsonReader.JSONProviders(*config.Paths.Config)
for _, w := range warnings {
logger.Warn(w)
shoutrrrClient.Notify(w)
@@ -179,45 +162,22 @@ func _main(ctx context.Context, reader *reader.Reader, args []string, logger log
return err
}
L := len(providers)
switch L {
case 0:
logger.Warn("Found no setting to update record")
case 1:
logger.Info("Found single setting to update record")
default:
logger.Info("Found " + fmt.Sprint(len(providers)) + " settings to update records")
}
logProvidersCount(len(providers), logger)
client := &http.Client{Timeout: config.Client.Timeout}
defer client.CloseIdleConnections()
err = health.CheckHTTP(ctx, client)
if err != nil {
logger.Warn(err.Error())
}
records := make([]recordslib.Record, len(providers))
for i, provider := range providers {
logger.Info("Reading history from database: domain " +
provider.Domain() + " host " + provider.Host() +
" " + provider.IPVersion().String())
events, err := persistentDB.GetEvents(provider.Domain(),
provider.Host(), provider.IPVersion())
if err != nil {
shoutrrrClient.Notify(err.Error())
return err
}
records[i] = recordslib.New(provider, events)
records, err := readRecords(providers, persistentDB, logger, shoutrrrClient)
if err != nil {
return fmt.Errorf("reading records: %w", err)
}
defer client.CloseIdleConnections()
db := data.NewDatabase(records, persistentDB)
defer func() {
err := db.Close()
if err != nil {
logger.Error(err.Error())
}
}()
httpSettings := publicip.HTTPSettings{
Enabled: *config.PubIP.HTTPEnabled,
@@ -243,84 +203,178 @@ func _main(ctx context.Context, reader *reader.Reader, args []string, logger log
return fmt.Errorf("creating resolver: %w", err)
}
hioClient := healthchecksio.New(client, *config.Health.HealthchecksioUUID)
hioClient := healthchecksio.New(client, config.Health.HealthchecksioBaseURL,
*config.Health.HealthchecksioUUID)
updater := update.NewUpdater(db, client, shoutrrrClient, logger, timeNow)
runner := update.NewRunner(db, updater, ipGetter, config.Update.Period,
updaterService := update.NewService(db, updater, ipGetter, config.Update.Period,
config.Update.Cooldown, logger, resolver, timeNow, hioClient)
runnerHandler, runnerCtx, runnerDone := goshutdown.NewGoRoutineHandler("runner")
go runner.Run(runnerCtx, runnerDone)
healthServer, err := createHealthServer(db, resolver, logger, *config.Health.ServerAddress)
if err != nil {
return fmt.Errorf("creating health server: %w", err)
}
server, err := createServer(ctx, config.Server, logger, db, updaterService)
if err != nil {
return fmt.Errorf("creating server: %w", err)
}
var backupService goservices.Service
backupLogger := logger.New(log.SetComponent("backup"))
backupService = backup.New(*config.Backup.Period, *config.Paths.DataDir,
*config.Backup.Directory, backupLogger)
backupService, err = goservices.NewRestarter(goservices.RestarterSettings{Service: backupService})
if err != nil {
return fmt.Errorf("creating backup restarter: %w", err)
}
servicesSequence, err := goservices.NewSequence(goservices.SequenceSettings{
ServicesStart: []goservices.Service{db, updaterService, healthServer, server, backupService},
ServicesStop: []goservices.Service{server, healthServer, updaterService, backupService, db},
})
if err != nil {
return fmt.Errorf("creating services sequence: %w", err)
}
runError, startErr := servicesSequence.Start(ctx)
if startErr != nil {
return fmt.Errorf("starting services: %w", startErr)
}
// note: errors are logged within the goroutine,
// no need to collect the resulting errors.
go runner.ForceUpdate(ctx)
go updaterService.ForceUpdate(ctx)
isHealthy := health.MakeIsHealthy(db, resolver)
healthLogger := logger.New(log.SetComponent("healthcheck server"))
healthServer := health.NewServer(*config.Health.ServerAddress,
healthLogger, isHealthy)
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler("health server")
go healthServer.Run(healthServerCtx, healthServerDone)
serverLogger := logger.New(log.SetComponent("http server"))
server := server.New(ctx, config.Server.ListeningAddress, config.Server.RootURL,
db, serverLogger, runner)
serverHandler, serverCtx, serverDone := goshutdown.NewGoRoutineHandler("server")
go server.Run(serverCtx, serverDone)
shoutrrrClient.Notify("Launched with " + strconv.Itoa(len(records)) + " records to watch")
backupHandler, backupCtx, backupDone := goshutdown.NewGoRoutineHandler("backup")
backupLogger := logger.New(log.SetComponent("backup"))
go backupRunLoop(backupCtx, backupDone, *config.Backup.Period, *config.Paths.DataDir,
*config.Backup.Directory, backupLogger, timeNow)
shutdownGroup := goshutdown.NewGroupHandler("")
shutdownGroup.Add(runnerHandler, healthServerHandler, serverHandler, backupHandler)
<-ctx.Done()
err = shutdownGroup.Shutdown(context.Background())
if err != nil {
select {
case <-ctx.Done():
case err = <-runError:
exitHealthchecksio(hioClient, logger, healthchecksio.Exit1)
shoutrrrClient.Notify(err.Error())
return err
return fmt.Errorf("exiting due to critical error: %w", err)
}
err = servicesSequence.Stop()
if err != nil {
exitHealthchecksio(hioClient, logger, healthchecksio.Exit1)
shoutrrrClient.Notify(err.Error())
return fmt.Errorf("stopping failed: %w", err)
}
exitHealthchecksio(hioClient, logger, healthchecksio.Exit0)
return nil
}
type InfoErroer interface {
Info(s string)
Error(s string)
func printSplash(buildInfo models.BuildInformation) {
announcementExp, err := time.Parse(time.RFC3339, "2024-10-15T00:00:00Z")
if err != nil {
panic(err)
}
splashSettings := gosplash.Settings{
User: "qdm12",
Repository: "ddns-updater",
Emails: []string{"quentin.mcgaw@gmail.com"},
Version: buildInfo.Version,
Commit: buildInfo.Commit,
Created: buildInfo.Created,
Announcement: "Public IP http provider GOOGLE is no longer working",
AnnounceExp: announcementExp,
// Sponsor information
PaypalUser: "qmcgaw",
GithubSponsor: "qdm12",
}
for _, line := range gosplash.MakeLines(splashSettings) {
fmt.Println(line)
}
}
func backupRunLoop(ctx context.Context, done chan<- struct{}, backupPeriod time.Duration,
dataDir, outputDir string, logger InfoErroer, timeNow func() time.Time) {
defer close(done)
if backupPeriod == 0 {
logger.Info("disabled")
return
func readConfig(reader *reader.Reader, logger log.LoggerInterface) (
config config.Config, err error,
) {
err = config.Read(reader, logger)
if err != nil {
return config, fmt.Errorf("reading settings: %w", err)
}
logger.Info("each " + backupPeriod.String() +
"; writing zip files to directory " + outputDir)
ziper := backup.NewZiper()
timer := time.NewTimer(backupPeriod)
for {
fileName := "ddns-updater-backup-" + strconv.Itoa(int(timeNow().UnixNano())) + ".zip"
zipFilepath := filepath.Join(outputDir, fileName)
err := ziper.ZipFiles(
zipFilepath,
filepath.Join(dataDir, "updates.json"),
filepath.Join(dataDir, "config.json"),
)
if err != nil {
logger.Error(err.Error())
}
select {
case <-timer.C:
timer.Reset(backupPeriod)
case <-ctx.Done():
timer.Stop()
return
}
config.SetDefaults()
err = config.Validate()
if err != nil {
return config, fmt.Errorf("settings validation: %w", err)
}
logger.Patch(config.Logger.ToOptions()...)
logger.Info(config.String())
return config, nil
}
func logProvidersCount(providersCount int, logger log.LeveledLogger) {
switch providersCount {
case 0:
logger.Warn("Found no setting to update record")
case 1:
logger.Info("Found single setting to update record")
default:
logger.Info("Found " + strconv.Itoa(providersCount) + " settings to update records")
}
}
func readRecords(providers []provider.Provider, persistentDB *persistence.Database,
logger log.LoggerInterface, shoutrrrClient *shoutrrr.Client) (
records []recordslib.Record, err error,
) {
records = make([]recordslib.Record, len(providers))
for i, provider := range providers {
logger.Info("Reading history from database: domain " +
provider.Domain() + " owner " + provider.Owner() +
" " + provider.IPVersion().String())
events, err := persistentDB.GetEvents(provider.Domain(),
provider.Owner(), provider.IPVersion())
if err != nil {
shoutrrrClient.Notify(err.Error())
return nil, err
}
records[i] = recordslib.New(provider, events)
}
return records, nil
}
func exitHealthchecksio(hioClient *healthchecksio.Client,
logger log.LoggerInterface, state healthchecksio.State,
) {
const timeout = 3 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
err := hioClient.Ping(ctx, state)
if err != nil {
logger.Error(err.Error())
}
}
//nolint:ireturn
func createHealthServer(db health.AllSelecter, resolver health.LookupIPer,
logger log.LoggerInterface, serverAddress string) (
healthServer goservices.Service, err error,
) {
if serverAddress == "" {
return noop.New("healthcheck server"), nil
}
isHealthy := health.MakeIsHealthy(db, resolver)
healthLogger := logger.New(log.SetComponent("healthcheck server"))
return health.NewServer(serverAddress, healthLogger, isHealthy)
}
//nolint:ireturn
func createServer(ctx context.Context, config config.Server,
logger log.LoggerInterface, db server.Database,
updaterService server.UpdateForcer) (
service goservices.Service, err error,
) {
if !*config.Enabled {
return noop.New("server"), nil
}
serverLogger := logger.New(log.SetComponent("http server"))
return server.New(ctx, config.ListeningAddress, config.RootURL,
db, serverLogger, updaterService)
}

View File

@@ -3,7 +3,6 @@
{
"provider": "namecheap",
"domain": "example.com",
"host": "@",
"password": "e5322165c1d74692bfa6d807100c0310"
},
{
@@ -13,8 +12,7 @@
},
{
"provider": "godaddy",
"domain": "example.org",
"host": "subdomain",
"domain": "subdomain.example.org",
"key": "aaaaaaaaaaaaaaaa",
"secret": "aaaaaaaaaaaaaaaa"
},

View File

@@ -1,7 +1,7 @@
version: "3.7"
services:
ddns-updater:
image: qmcgaw/ddns-updater
image: ghcr.io/qdm12/ddns-updater
container_name: ddns-updater
network_mode: bridge
ports:

View File

@@ -10,7 +10,6 @@
{
"provider": "aliyun",
"domain": "domain.com",
"host": "@",
"access_key_id": "your access_key_id",
"access_secret": "your access_secret",
"ip_version": "ipv4",
@@ -22,14 +21,13 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"`
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
- `"access_key_id"`
- `"access_secret"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -9,8 +9,7 @@
"settings": [
{
"provider": "allinkl",
"domain": "domain.com",
"host": "host",
"domain": "sub.domain.com",
"username": "dynXXXXXXX",
"password": "password",
"ip_version": "ipv4",
@@ -22,14 +21,13 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host (subdomain)
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
- `"username"` username (usually starts with dyn followed by numbers)
- `"password"` password in plain text
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

33
docs/changeip.md Normal file
View File

@@ -0,0 +1,33 @@
# ChangeIP
## Configuration
### Example
```json
{
"settings": [
{
"provider": "changeip",
"domain": "sub.domain.com",
"username": "dynXXXXXXX",
"password": "password",
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
```
### Compulsory parameters
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
- `"username"`
- `"password"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -11,7 +11,6 @@
"provider": "cloudflare",
"zone_identifier": "some id",
"domain": "domain.com",
"host": "@",
"ttl": 600,
"token": "yourtoken",
"ip_version": "ipv4",
@@ -24,8 +23,7 @@
### Compulsory parameters
- `"zone_identifier"` is the Zone ID of your site, from the domain overview page written as *Zone ID*
- `"domain"`
- `"host"` is your host and can be `"@"`, a subdomain or the wildcard `"*"`.
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
See [this issue comment for context](https://github.com/qdm12/ddns-updater/issues/243#issuecomment-928313949). This is left as is for compatibility.
- `"ttl"` integer value for record TTL in seconds (specify 1 for automatic)
- One of the following ([how to find API keys](https://developers.cloudflare.com/fundamentals/api/get-started/)):
@@ -37,6 +35,6 @@ See [this issue comment for context](https://github.com/qdm12/ddns-updater/issue
- `"proxied"` can be set to `true` to use the proxy services of Cloudflare
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
Special thanks to @Starttoaster for helping out with the [documentation](https://gist.github.com/Starttoaster/07d568c2a99ad7631dd776688c988326) and testing.

View File

@@ -1,52 +0,0 @@
# Contributing
## Table of content
1. [Setup](#setup)
1. [Commands available](#commands-available)
1. [Guidelines](#guidelines)
## Setup
### Using VSCode and Docker
That should be easier and better than a local setup, although it might use more memory if you're not on Linux.
1. Install [Docker](https://docs.docker.com/install/)
- On Windows, share a drive with Docker Desktop and have the project on that partition
- On OSX, share your project directory with Docker Desktop
1. With [Visual Studio Code](https://code.visualstudio.com/download), install the [remote containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
1. In Visual Studio Code, press on `F1` and select `Remote-Containers: Open Folder in Container...`
1. Your dev environment is ready to go!... and it's running in a container :+1:
### Locally
Install [Go](https://golang.org/dl/), [Docker](https://www.docker.com/products/docker-desktop) and [Git](https://git-scm.com/downloads); then:
```sh
go mod download
```
And finally install [golangci-lint](https://github.com/golangci/golangci-lint#install).
You might want to use an editor such as [Visual Studio Code](https://code.visualstudio.com/download) with the [Go extension](https://code.visualstudio.com/docs/languages/go).
## Build and Run
```sh
go build -o app cmd/updater/main.go
./app
```
## Commands available
- Test the code: `go test ./...`
- Lint the code `golangci-lint run`
- Build the Docker image (tests and lint included): `docker build -t qmcgaw/ddns-updater .`
- Run the Docker container: `docker run -it --rm -v /yourpath/data:/updater/data qmcgaw/ddns-updater`
## Guidelines
The Go code is in the Go file [cmd/updater/main.go](../cmd/updater/main.go) and the [internal directory](../internal), you might want to start reading the main.go file.
See the [Contributing document](../.github/CONTRIBUTING.md) for more information on how to contribute to this repository.

View File

@@ -15,7 +15,6 @@ Feel free to open issues to extend its configuration options.
{
"provider": "custom",
"domain": "example.com",
"host": "@",
"url": "https://example.com/update?domain=example.com&host=@&username=username&client_key=client_key",
"ipv4key": "ipv4",
"ipv6key": "ipv6",
@@ -29,14 +28,13 @@ Feel free to open issues to extend its configuration options.
### Compulsory parameters
- `"domain"` is the domain name to update
- `"host"` is the host to update, which can be `"@"` (root), `"*"` or a subdomain
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"url"` is the URL to update your records and should contain all the information EXCEPT the IP address to update
- `"ipv4key"` is the URL query parameter name for the IPv4 address, for example `ipv4` will be added to the URL with `&ipv4=1.2.3.4`.
- `"ipv6key"` is the URL query parameter name for the IPv6 address, for example `ipv6` will be added to the URL with `&ipv6=::aaff`. Even if you don't use IPv6, this must be set to something.
- `"ipv4key"` is the URL query parameter name for the IPv4 address, for example `ipv4` will be added to the URL with `&ipv4=1.2.3.4`. In the rare case you do not wish to send an IPv4 query parameter to the registrar API when updating your A (IPv4) record, leave it to `"ipv4key": ""`.
- `"ipv6key"` is the URL query parameter name for the IPv6 address, for example `ipv6` will be added to the URL with `&ipv6=::aaff`. In the rare case you do not wish to send an IPv6 query parameter to the registrar API when updating your AAAA (IPv6) record, leave it to `"ipv6key": ""`.
- `"success_regex"` is a regular expression to match the response from the server to determine if the update was successful. You can use [regex101.com](https://regex101.com/) to find the regular expression you want. For example `good` would match any response containing the word "good".
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.

View File

@@ -10,7 +10,6 @@
{
"provider": "dd24",
"domain": "domain.com",
"host": "@",
"password": "password",
"ip_version": "ipv4",
"ipv6_suffix": ""
@@ -21,11 +20,10 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"`
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
- `"password"` is your password
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.

View File

@@ -9,9 +9,7 @@
"settings": [
{
"provider": "ddnss",
"provider_ip": true,
"domain": "domain.com",
"host": "@",
"username": "user",
"password": "password",
"dual_stack": false,
@@ -24,17 +22,16 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"`
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
- `"username"`
- `"password"`
### Optional parameters
- `"dual_stack"` can be set to `true` **if you have turn on dual stack for your record** to update both IPv4 and IPv6 addresses. Note it is ignored if `"provider_ip": true`. More precisely:
- `"dual_stack"` can be set to `true` **if you have turn on dual stack for your record** to update both IPv4 and IPv6 addresses. More precisely:
- if it is `false`, the updates are done using the `ip` parameter and only one IP address can be set (ipv4 or ipv6, whichever is last sent).
- if it is `true`, the updates are done using the `ip` and `ip6` parameters, for IPv4 and IPv6 respectively, and both can be set on the same record
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -9,12 +9,10 @@
"settings": [
{
"provider": "desec",
"domain": "dedyn.io",
"host": "host",
"domain": "sub.dedyn.io",
"token": "token",
"ip_version": "ipv4",
"ipv6_suffix": "",
"provider_ip": false
"ipv6_suffix": ""
}
]
}
@@ -22,15 +20,13 @@
### Compulsory parameters
- `"domain"`
- `"host"` can be `@` for the root domain or a subdomain or a wildcard subdomain (`*`), defaults to `@`
- `"token"` is your token that you can create [here](https://desec.io/tokens)
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"token"` is your token that you can create at [desec.io/tokens](https://desec.io/tokens)
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -10,7 +10,6 @@
{
"provider": "digitalocean",
"domain": "domain.com",
"host": "@",
"token": "yourtoken",
"ip_version": "ipv4",
"ipv6_suffix": ""
@@ -21,13 +20,12 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"` or `"*"`
- `"token"` is your token that you can create [here](https://cloud.digitalocean.com/settings/applications)
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"token"` is your token that you can create at [cloud.digitalocean.com/settings/applications](https://cloud.digitalocean.com/settings/applications)
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -10,10 +10,8 @@
{
"provider": "dnsomatic",
"domain": "domain.com",
"host": "@",
"username": "username",
"password": "password",
"provider_ip": true,
"ip_version": "ipv4",
"ipv6_suffix": ""
}
@@ -23,15 +21,13 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"` or `"*"`
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"username"`
- `"password"`
- `"provider_ip"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -10,7 +10,6 @@
{
"provider": "dnspod",
"domain": "domain.com",
"host": "@",
"token": "yourtoken",
"ip_version": "ipv4",
"ipv6_suffix": ""
@@ -21,13 +20,12 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"`
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
- `"token"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

31
docs/domeneshop.md Normal file
View File

@@ -0,0 +1,31 @@
# Domeneshop.no
## Configuration
### Example
```json
{
"settings": [
{
"provider": "domeneshop",
"domain": "domain.com,seconddomain.com",
"token": "token",
"secret": "secret",
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
```
### Compulsory parameters
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`)
- `"token"` See [api.domeneshop.no/docs/](https://api.domeneshop.no/docs/) for instructions on how to generate credentials.
- `"secret"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.

View File

@@ -10,8 +10,6 @@
{
"provider": "dondominio",
"domain": "domain.com",
"host": "@",
"name": "something",
"username": "username",
"key": "key",
"ip_version": "ipv4",
@@ -23,16 +21,14 @@
### Compulsory parameters
- `"domain"`
- `"host"` is the subdomain to update which can be `@`, `*` or a subdomain
- `"name"` is the name of the service/hosting
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"username"`
- `"password"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -10,7 +10,6 @@
{
"provider": "dreamhost",
"domain": "domain.com",
"host": "@",
"key": "key",
"ip_version": "ipv4",
"ipv6_suffix": ""
@@ -26,8 +25,8 @@
### Optional parameters
- `"host"` is your host and can be a subdomain or `"@"`. It defaults to `"@"`.
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -9,11 +9,10 @@
"settings": [
{
"provider": "duckdns",
"host": "host",
"domain": "sub.duckdns.org",
"token": "token",
"ip_version": "ipv4",
"ipv6_suffix": "",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -21,14 +20,16 @@
### Compulsory parameters
- `"host"` is your host, for example `subdomain` for `subdomain.duckdns.org`
- `"domain"` is the domain to update. The [eTLD](https://developer.mozilla.org/en-US/docs/Glossary/eTLD) must be `duckdns.org`. For example:
- for the root owner/host `@`, it would be `mydomain.duckdns.org`
- for the owner/host `sub`, it would be `sub.mydomain.duckdns.org`
- for multiple domains, it can be `sub1.mydomain.duckdns.org,sub2.mydomain.duckdns.org` BUT it cannot be `a.duckdns.org,b.duckdns.org`, since the effective domains would be `a.duckdns.org` and `b.duckdns.org`
- `"token"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (**NOT** your IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -10,7 +10,6 @@
{
"provider": "dyn",
"domain": "domain.com",
"host": "@",
"username": "username",
"client_key": "client_key",
"ip_version": "ipv4",
@@ -22,14 +21,13 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"` or `"*"`
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"username"`
- `"client_key"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -10,13 +10,11 @@
{
"provider": "dynu",
"domain": "domain.com",
"host": "@",
"group": "group",
"username": "username",
"password": "password",
"ip_version": "ipv4",
"ipv6_suffix": "",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -24,16 +22,14 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"`
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
- `"username"`
- `"password"` could be plain text or password in MD5 or SHA256 format (There's also an option for setting a password for IP Update only)
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
- `"group"` specify the Group for which you want to set the IP (will update any domains and subdomains in the same group)
## Domain setup

View File

@@ -10,11 +10,9 @@
{
"provider": "dynv6",
"domain": "domain.com",
"host": "@",
"token": "token",
"ip_version": "ipv4",
"ipv6_suffix": "",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -22,14 +20,12 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"`
- `"token"` that you can obtain [here](https://dynv6.com/keys#token)
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
- `"token"` that you can obtain from [dynv6.com/keys#token](https://dynv6.com/keys#token)
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -10,12 +10,10 @@
{
"provider": "easydns",
"domain": "domain.com",
"host": "@",
"username": "username",
"token": "token",
"ip_version": "ipv4",
"ipv6_suffix": "",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -23,15 +21,13 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"` or `"*"`
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"username"`
- `"token"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

41
docs/example.md Normal file
View File

@@ -0,0 +1,41 @@
# Example.com
## Configuration
### Example
<!-- UPDATE THIS JSON EXAMPLE -->
```json
{
"settings": [
{
"provider": "example",
"domain": "domain.com",
"username": "username",
"password": "password",
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
```
### Compulsory parameters
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"username"`
- `"password"`
<!-- UPDATE THIS IF NEEDED -->
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
<!-- UPDATE THIS IF NEEDED -->
## Domain setup
<!-- FILL THIS UP WITH A FEW NUMBERED STEPS -->

View File

@@ -9,8 +9,7 @@
"settings": [
{
"provider": "freedns",
"domain": "domain.com",
"host": "host",
"domain": "sub.domain.com",
"token": "token",
"ip_version": "ipv4",
"ipv6_suffix": ""
@@ -21,14 +20,13 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host (subdomain)
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
- `"token"` is the randomized update token you use to update your record
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -12,7 +12,6 @@ This provider uses Gandi v5 API
{
"provider": "gandi",
"domain": "domain.com",
"host": "@",
"personal_access_token": "token",
"ttl": 3600,
"ip_version": "ipv4",
@@ -24,14 +23,13 @@ This provider uses Gandi v5 API
### Compulsory parameters
- `"domain"`
- `"host"` which can be a subdomain, `@` or a wildcard `*`
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"personal_access_token"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
- `"ttl"` default is `3600`
## Domain setup

View File

@@ -17,7 +17,6 @@
// ...
},
"domain": "domain.com",
"host": "@",
"ip_version": "ipv4",
"ipv6_suffix": ""
}
@@ -29,11 +28,10 @@
- `"project"` is the id of your Google Cloud project
- `"zone"` is the zone, that your DNS record is located in
- `"credentials"` is the JSON credentials for your Google Cloud project. This is usually downloaded as a JSON file, which you can copy paste the content as the value of the `"credentials"` key. More information on how to get it is available [here](https://cloud.google.com/docs/authentication/getting-started). Please ensure your service account has all necessary permissions to create/update/list/get DNS records within your project.
- `"domain"` is the TLD of you DNS record (without a trailing dot)
- `"host"` is your host and can be a subdomain or `"@"` or `"*"`
- `"credentials"` is the JSON credentials for your Google Cloud project. This is usually downloaded as a JSON file, which you can copy paste the content as the value of the `"credentials"` key. More information on how to get it is available at [cloud.google.com/docs/authentication/getting-started](https://cloud.google.com/docs/authentication/getting-started). Please ensure your service account has all necessary permissions to create/update/list/get DNS records within your project.
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.

View File

@@ -10,7 +10,6 @@
{
"provider": "godaddy",
"domain": "domain.com",
"host": "@",
"key": "key",
"secret": "secret",
"ip_version": "ipv4",
@@ -22,15 +21,14 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain, `"@"` or `"*"` generally
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"key"`
- `"secret"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -9,11 +9,9 @@
"settings": [
{
"provider": "goip",
"domain": "goip.de",
"host": "mysubdomain",
"domain": "sub.mydomain.goip.de",
"username": "username",
"password": "password",
"provider_ip": true,
"ip_version": "",
"ipv6_suffix": ""
@@ -24,13 +22,15 @@
### Compulsory parameters
- `"host"` is the full FQDN of your ddns address. sample.goip.de or something.goip.it
- `"domain"` is the domain to update. For example, for the owner/host `sub`, it would be `sub.goip.de`. The [eTLD](https://developer.mozilla.org/en-US/docs/Glossary/eTLD) must be `goip.de` or `goip.it`.
- `"domain"` is the domain to update. The [eTLD](https://developer.mozilla.org/en-US/docs/Glossary/eTLD) must be `goip.de` or `goip.it`. For example:
- for the root owner/host `@`, it would be `mydomain.goip.de`
- for the owner/host `sub`, it would be `sub.mydomain.goip.de`
- for multiple domains, it can be `sub1.mydomain.goip.de,sub2.mydomain.goip.de` BUT it cannot be `a.goip.de,b.goip.de`, since the effective domains would be `a.goip.de` and `b.goip.de`
- `"username"` is your goip.de username listed under "Routers"
- `"password"` is your router account password
### Optional parameters
- `"domain"` is the domain name which can be `goip.de` or `goip.it`, and defaults to `goip.de` if left unset.
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request. This is automatically disabled for an IPv6 public address since it is not supported.
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.

View File

@@ -10,9 +10,7 @@
{
"provider": "he",
"domain": "domain.com",
"host": "@",
"password": "password",
"provider_ip": true,
"ip_version": "ipv4",
"ipv6_suffix": ""
}
@@ -22,14 +20,12 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"` or `"*"` (untested)
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard. (untested)
- `"password"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -1,5 +1,7 @@
# Hetzner
⚠️ You should use [Hetzner Cloud](./hetznercloud.md) with the new Hetzner Cloud console instead, given this legacy Hetzner API is going to be shutdown soon.
## Configuration
### Example
@@ -11,7 +13,6 @@
"provider": "hetzner",
"zone_identifier": "some id",
"domain": "domain.com",
"host": "@",
"ttl": 600,
"token": "yourtoken",
"ip_version": "ipv4",
@@ -24,8 +25,7 @@
### Compulsory parameters
- `"zone_identifier"` is the Zone ID of your site, from the domain overview page written as *Zone ID*
- `"domain"`
- `"host"` is your host and can be `"@"`, a subdomain or the wildcard `"*"`.
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"ttl"` optional integer value corresponding to a number of seconds
- One of the following ([how to find API keys](https://docs.hetzner.com/cloud/api/getting-started/generating-api-token)):
- API Token `"token"`, configured with DNS edit permissions for your DNS name's zone
@@ -33,4 +33,4 @@
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.

32
docs/hetznercloud.md Normal file
View File

@@ -0,0 +1,32 @@
# Hetzner Cloud
This provider uses the Hetzner Cloud API `https://api.hetzner.cloud/v1/` which is different from the legacy Hetzner DNS API.
## Configuration
### Example
```json
{
"settings": [
{
"provider": "hetznercloud",
"domain": "example.com",
"token": "yourtoken",
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
```
### Compulsory parameters
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"token"` is your API token configured with DNS write permissions for your DNS zone, see [the Authentication section](https://docs.hetzner.com/cloud/api/getting-started/generating-api-token)
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
- `"ttl"` time to live for the DNS record in seconds. It is only used to add a record to the rrset, and is not used to update an existing record. If left empty, it defaults to the existing zone TTL.

View File

@@ -10,12 +10,10 @@
{
"provider": "infomaniak",
"domain": "domain.com",
"host": "@",
"username": "username",
"password": "password",
"ip_version": "ipv4",
"ipv6_suffix": "",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -23,15 +21,17 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"`
- `"username"`
- `"password"`
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
- `"username"` for dyndns (**not** your infomaniak admin username!)
- `"password"` for dyndns (**not** your infomaniak admin password!)
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup
Follow [this guide](https://www.infomaniak.com/en/support/faq/2357/getting-started-guide-dyndns-with-an-infomaniak-domain) to set up your subdomain including `username` and `password` for use in the configuration. **do not use your infomaniak admin username and password in the configuration!**
If you only plan on using IPv4, add your current IPv4 Address. If you only plan on using IPv6, add your current IPv6 Address. If you plan to use dual-stack (IPv4 and IPv6) addresses, it does not matter what ip-address you put in the dialog.

View File

@@ -2,6 +2,8 @@
## Configuration
You must configure a [DynDNS Account in INWX](https://www.inwx.com/en/offer/dyndns) and use the configured account and hostname.
### Example
```json
@@ -10,7 +12,6 @@
{
"provider": "inwx",
"domain": "domain.com",
"host": "@",
"username": "username",
"password": "password",
"ip_version": "ipv4",
@@ -22,14 +23,13 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"`
- `"domain"` is the domain configured for the DynDNS Account. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
- `"username"`
- `"password"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -10,7 +10,6 @@
{
"provider": "ionos",
"domain": "domain.com",
"host": "@",
"api_key": "api_key",
"ip_version": "ipv4",
"ipv6_suffix": ""
@@ -21,11 +20,10 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"` or `"*"`
- `"api_key"` is your API key, obtained from [creating an API key](https://www.ionos.com/help/domains/configuring-your-ip-address/set-up-dynamic-dns-with-company-name/#c181598)
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"api_key"` is your API key, consisting of two parts — a prefix and the key itself — and can be obtained by [creating an API key](https://www.ionos.com/help/domains/configuring-your-ip-address/set-up-dynamic-dns-with-company-name/#c181598). It must follow the format `<prefix>.<key>`.
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.

31
docs/ipv64.md Normal file
View File

@@ -0,0 +1,31 @@
# IPv64
## Configuration
### Example
```json
{
"settings": [
{
"provider": "ipv64",
"domain": "domain.com",
"key": "key",
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
```
### Compulsory parameters
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
- `"key"` that you can obtain at [ipv64.net/account](https://ipv64.net/account)
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -10,7 +10,6 @@
{
"provider": "linode",
"domain": "domain.com",
"host": "@",
"token": "token",
"ip_version": "ipv4",
"ipv6_suffix": ""
@@ -21,14 +20,13 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"` or `"*"`
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"token"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

31
docs/loopia.md Normal file
View File

@@ -0,0 +1,31 @@
# Loopia
## Configuration
### Example
```json
{
"settings": [
{
"provider": "loopia",
"domain": "domain.com",
"username": "username",
"password": "password",
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
```
### Compulsory parameters
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`). It cannot be a wildcard domain.
- `"username"`
- `"password"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.

View File

@@ -10,7 +10,6 @@
{
"provider": "luadns",
"domain": "domain.com",
"host": "@",
"email": "email",
"token": "token",
"ip_version": "ipv4",
@@ -22,15 +21,14 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"` or `"*"`
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"email"`
- `"token"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

33
docs/myaddr.md Normal file
View File

@@ -0,0 +1,33 @@
# [Myaddr](https://myaddr.tools/)
## Configuration
### Example
```json
{
"settings": [
{
"provider": "myaddr",
"domain": "your-name.myaddr.tools",
"key": "key",
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
```
### Compulsory parameters
- `"domain"` - the **single** domain to update; note the `key` below updates all records and subdomains for this domain. It should be `your-name.myaddr.tools`.
- `"key"` - the private key corresponding to the domain to update
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup
Claim a subdomain at [myaddr.tools](https://myaddr.tools/)

View File

@@ -12,7 +12,6 @@
{
"provider": "name.com",
"domain": "domain.com",
"host": "@",
"username": "username",
"token": "token",
"ttl": 300,
@@ -25,8 +24,7 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain, `"@"` or `"*"` generally
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"username"` is your account username
- `"token"` which you can obtain from [www.name.com/account/settings/api](https://www.name.com/account/settings/api)
@@ -34,4 +32,4 @@
- `"ttl"` is the time this record can be cached for in seconds. Name.com allows a minimum TTL of 300, or 5 minutes. Name.com defaults to 300 if not provided.
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.

View File

@@ -10,9 +10,7 @@
{
"provider": "namecheap",
"domain": "domain.com",
"host": "@",
"password": "password",
"provider_ip": true
"password": "password"
}
]
}
@@ -20,14 +18,11 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain, `"@"` or `"*"` generally
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"password"`
### Optional parameters
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
Note that Namecheap only supports ipv4 addresses for now.
## Domain setup

51
docs/namesilo.md Normal file
View File

@@ -0,0 +1,51 @@
# NameSilo
[![NameSilo Website](../readme/namesilo.jpg)](https://www.namesilo.com)
## Configuration
### Example
```json
{
"settings": [
{
"provider": "namesilo",
"domain": "sub.example.com",
"key": "71dZaE8c2Aa926Ca2E8c1",
"ttl": 7207,
"ip_version": "ipv4"
}
]
}
```
### Compulsory parameters
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"key"` is the NameSilo API Key obtained using the domain setup instructions below. For example: `71dZaE8c2Aa926Ca2E8c1`.
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
- `"ttl"` is the record's Time to Live (TTL), which defaults to `7207` seconds. It must be numeric, less than `2592001`, and greater than or equal to `3600`. TTL values of `3603` or `7207` may be subject to NameSilo's [Automatic TTL Adjustments](https://www.namesilo.com/support/v2/articles/domain-manager/dns-manager#auto_ttl).
## Domain setup
1. Login to the [Namesilo API Manager](https://www.namesilo.com/account/api-manager) with your account credentials.
1. Generate an API key. The generated API key will look similar to `71dZaE8c2Aa926Ca2E8c1`.
- (do _not_ check the "Generate key for read-only access" box)
[![Before NameSilo API Key](../readme/namesilo1.jpg)](https://www.namesilo.com/account/api-manager)
[![After NameSilo API Key](../readme/namesilo2.jpg)](https://www.namesilo.com/account/api-manager)
## Testing
1. Go to the [NameSilo Domain Manager](https://www.namesilo.com/account_domains.php).
1. Choose "Manage DNS for this domain" (the globe icon) for the domain you wish to test.
[![Manage DNS for this domain](../readme/namesilo3.jpg)](https://www.namesilo.com/account_domains.php)
1. Change the IP address of the host to `127.0.0.1`.
1. Run the ddns-updater.
1. Refresh the Namesilo webpage to check the update occurred.

View File

@@ -14,7 +14,6 @@ Also keep in mind, that TTL, Expire, Retry and Refresh values of the given Domai
{
"provider": "netcup",
"domain": "domain.com",
"host": "host",
"api_key": "xxxxx",
"password": "yyyyy",
"customer_number": "111111",
@@ -27,8 +26,7 @@ Also keep in mind, that TTL, Expire, Retry and Refresh values of the given Domai
### Compulsory parameters
- `"domain"` is your domain
- `"host"` is your host (subdomain) or `"@"` for the root of the domain. It cannot be the wildcard.
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`) or the wildcard `*.example.com`.
- `"api_key"` is your api key (generated in the [customercontrolpanel](https://www.customercontrolpanel.de))
- `"password"` is your api password (generated in the [customercontrolpanel](https://www.customercontrolpanel.de)). Netcup only allows one ApiPassword. This is not the account password. This password is used for all api keys.
- `"customer_number"` is your customer number (viewable in the [customercontrolpanel](https://www.customercontrolpanel.de) next to your name). As seen in the example above, provide the number as string value.
@@ -36,4 +34,4 @@ Also keep in mind, that TTL, Expire, Retry and Refresh values of the given Domai
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.

View File

@@ -10,11 +10,9 @@
{
"provider": "njalla",
"domain": "domain.com",
"host": "@",
"key": "key",
"ip_version": "ipv4",
"ipv6_suffix": "",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -22,15 +20,13 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"` or `"*"`
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"key"` is the key for your record
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -10,12 +10,10 @@
{
"provider": "noip",
"domain": "domain.com",
"host": "@",
"username": "username",
"password": "password",
"ip_version": "ipv4",
"ipv6_suffix": "",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -23,15 +21,13 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"`
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
- `"username"`
- `"password"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -28,4 +28,4 @@
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.

View File

@@ -10,12 +10,10 @@
{
"provider": "opendns",
"domain": "domain.com",
"host": "@",
"username": "username",
"password": "password",
"ip_version": "ipv4",
"ipv6_suffix": "",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -23,15 +21,13 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"`
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
- `"username"`
- `"password"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -10,12 +10,10 @@
{
"provider": "ovh",
"domain": "domain.com",
"host": "@",
"username": "username",
"password": "password",
"ip_version": "ipv4",
"ipv6_suffix": "",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -23,8 +21,7 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"`
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
#### Using DynHost
@@ -36,15 +33,14 @@
- `"api_endpoint"` default value is `"ovh-eu"`
- `"app_key"` which you can create at [eu.api.ovh.com/createApp](https://eu.api.ovh.com/createApp/)
- `"app_secret"`
- `"consumer_key"`
- `"consumer_key"` which you can get at [www.ovh.com/auth/api/createToken](https://www.ovh.com/auth/api/createToken)
The ZoneDNS implementation allows you to update any record name including *.yourdomain.tld
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
- `"mode"` select between two modes, OVH's dynamic hosting service (`"dynamic"`) or OVH's API (`"api"`). Default is `"dynamic"`
## Domain setup

View File

@@ -10,9 +10,8 @@
{
"provider": "porkbun",
"domain": "domain.com",
"host": "@",
"api_key": "sk1_7d119e3f656b00ae042980302e1425a04163c476efec1833q3cb0w54fc6f5022",
"secret_api_key": "pk1_5299b57125c8f3cdf347d2fe0e713311ee3a1e11f11a14942b26472593e35368",
"api_key": "pk1_5299b57125c8f3cdf347d2fe0e713311ee3a1e11f11a14942b26472593e35368",
"secret_api_key": "sk1_7d119e3f656b00ae042980302e1425a04163c476efec1833q3cb0w54fc6f5022",
"ip_version": "ipv4",
"ipv6_suffix": ""
}
@@ -22,16 +21,15 @@
### Compulsory Parameters
- `"domain"`
- `"host"` is your host and can be a subdomain, `"*"` or `"@"`
- `"apikey"`
- `"secretapikey"`
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"api_key"`
- `"secret_api_key"`
- `"ttl"` optional integer value corresponding to a number of seconds
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup
@@ -43,5 +41,12 @@
## Record creation
In case you don't have an A or AAAA record for your host and domain combination, it will be created by DDNS-Updater.
However, to do so, the corresponding ALIAS record, that is automatically created by Porkbun, is automatically deleted to allow this.
Porkbun creates default DNS entries for new domains, which can conflict with creating a root or wildcard A/AAAA record. Therefore, ddns-updater automatically removes any conflicting default record before creating records, as described in the table below:
| Record type | Owner | Record value | Situation requiring a removal |
| --- | --- | --- | --- |
| `ALIAS` | `@` | pixie.porkbun.com | Creating A or AAAA record for the root domain **or** wildcard domain |
| `CNAME` | `*` | pixie.porkbun.com | Creating A or AAAA record for the wildcard domain |
More details is in [this comment by @everydaycombat](https://github.com/qdm12/ddns-updater/issues/546#issuecomment-1773960193).

56
docs/route53.md Normal file
View File

@@ -0,0 +1,56 @@
# AWS
## Configuration
### Example
```json
{
"settings": [
{
"provider": "route53",
"domain": "domain.com",
"ip_version": "ipv4",
"ipv6_suffix": "",
"access_key": "ffffffffffffffffffff",
"secret_key": "ffffffffffffffffffffffffffffffffffffffff",
"zone_id": "A30888735ZF12K83Z6F00",
"ttl": 300
}
]
}
```
### Compulsory parameters
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"access_key"` is the `AWS_ACCESS_KEY`
- `"secret_key"` is the `AWS_SECRET_ACCESS_KEY`
- `"zone_id"` is identification of your hosted zone
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"ttl"` amount of time, in seconds, that you want DNS recursive resolvers to cache information about this record. Defaults to `300`.
## Domain setup
Amazon has [an extensive documentation on registering or tranfering your domain to route53](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/Welcome.html).
### User permissions
Create a policy to grant access to change record sets, you can use a wildcard `*` in case you want to grant access to all your hosted zones.
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "route53:ChangeResourceRecordSets",
"Resource": "arn:aws:route53:::hostedzone/A30888735ZF12K83Z6F00"
}
]
}
```

36
docs/scaleway.md Normal file
View File

@@ -0,0 +1,36 @@
# Example.com
## Configuration
If something is unclear in the documentation below, please refer to the [scaleway API documentation](https://www.scaleway.com/en/developers/api/domains-and-dns/#path-records-update-records-within-a-dns-zone).
### Example
```json
{
"settings": [
{
"provider": "scaleway",
"domain": "domain.com",
"secret_key": "<SECRET_KEY>",
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
```
### Compulsory parameters
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"secret_key"` is your secret key
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
- `"ttl"` is the TTL of the DNS record to update, if you want to specify it.
## Domain setup
If you need more information about how to configure your domain, you can check the [scaleway official documentation](https://www.scaleway.com/en/docs/network/domains-and-dns/).

View File

@@ -10,10 +10,8 @@
{
"provider": "selfhost.de",
"domain": "domain.com",
"host": "@",
"username": "username",
"password": "password",
"provider_ip": true,
"ip_version": "ipv4",
"ipv6_suffix": ""
}
@@ -23,15 +21,13 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"`
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
- `"username"` is your DynDNS username
- `"password"` is your DynDNS password
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -10,11 +10,9 @@
{
"provider": "servercow",
"domain": "domain.com",
"host": "",
"username": "servercow_username",
"password": "servercow_password",
"ttl": 600,
"provider_ip": true,
"ip_version": "ipv4",
"ipv6_suffix": ""
}
@@ -24,8 +22,7 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be `""`, a subdomain or `"*"` generally
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"username"` is the username for your DNS API User
- `"password"` is the password for your DNS API User
@@ -33,9 +30,8 @@
- `"ttl"` can be set to an integer value for record TTL in seconds (if not set the default is 120)
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup
See [their article](https://cp.servercow.de/en/plugin/support_manager/knowledgebase/view/34/dns-api-v1/7/)
See [their article](https://wiki.servercow.de/en/domains/dns_api/api-syntax/)

View File

@@ -10,13 +10,11 @@
{
"provider": "spdyn",
"domain": "domain.com",
"host": "@",
"user": "user",
"password": "password",
"token": "token",
"ip_version": "ipv4",
"ipv6_suffix": "",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -24,8 +22,7 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"`
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
#### Using user and password
@@ -39,5 +36,4 @@
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (**not IPv6**)automatically when you send an update request, without sending the new IP address detected by the program in the request.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.

View File

@@ -10,11 +10,9 @@
{
"provider": "strato",
"domain": "domain.com",
"host": "@",
"password": "password",
"ip_version": "ipv4",
"ipv6_suffix": "",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -22,15 +20,13 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"`
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
- `"password"` is your dyndns password
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

View File

@@ -10,12 +10,10 @@
{
"provider": "variomedia",
"domain": "domain.com",
"host": "@",
"email": "email@domain.com",
"password": "password",
"ip_version": "ipv4",
"ipv6_suffix": "",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -23,16 +21,14 @@
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"`
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
- `"email"`
- `"password"` is your DNS settings password, not your account password ⚠️
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

39
docs/vercel.md Normal file
View File

@@ -0,0 +1,39 @@
# Vercel
## Configuration
### Example
```json
{
"settings": [
{
"provider": "vercel",
"domain": "domain.com",
"token": "yourtoken",
"ip_version": "ipv4",
"ipv6_suffix": "",
"team_id": ""
}
]
}
```
### Compulsory parameters
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"token"` is your Vercel API token. You can create one at [Vercel Account Settings > Tokens](https://vercel.com/account/tokens).
### Optional parameters
- `"team_id"` is your Vercel team ID. Required if the domain belongs to a team rather than your personal account. You can find this in your team settings.
- `"ttl"` is the TTL in seconds for the DNS record. It defaults to being not set, using the existing TTL configured for the record in Vercel or the default TTL determined by Vercel.
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup
1. Ensure your domain is added to Vercel. You can do this via the [Vercel Dashboard](https://vercel.com/dashboard) under your project's Domain settings, or via the API.
1. Your domain must be using Vercel's nameservers or have DNS management delegated to Vercel for this provider to work.
1. Create an API token with appropriate permissions at [Vercel Account Settings > Tokens](https://vercel.com/account/tokens).
1. If the domain belongs to a team, make sure to include the `team_id` parameter in your configuration.

30
docs/vultr.md Normal file
View File

@@ -0,0 +1,30 @@
# Vultr
## Configuration
### Example
```json
{
"settings": [
{
"provider": "vultr",
"domain": "potato.example.com",
"apikey": "AAAAAAAAAAAAAAA",
"ttl": 300,
"ip_version": "ipv4"
}
]
}
```
### Compulsory parameters
- `"domain"` is the domain to update. It can be a root domain (i.e. `example.com`) or a subdomain (i.e. `potato.example.com`) or a wildcard (i.e. `*.example.com`). In case of a wildcard, it only works if there is no existing wildcard records of any record type.
- `"apikey"` is your API key which can be obtained from [my.vultr.com/settings/](https://my.vultr.com/settings/#settingsapi).
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
- `"ttl"` is the record TTL which defaults to 900 seconds.

View File

@@ -16,12 +16,10 @@ set the environment variable as `PERIOD=11m` to check your public IP address and
{
"provider": "zoneedit",
"domain": "domain.com",
"host": "@",
"username": "username",
"token": "token",
"ip_version": "ipv4",
"ipv6_suffix": "",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -29,16 +27,14 @@ set the environment variable as `PERIOD=11m` to check your public IP address and
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"` or `"*"`
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
- `"username"`
- `"token"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
- `"ipv6_suffix"` is the IPv6 interface identifiersuffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw temporary IPv6 address of the machine is used in the record updating. You might want to set this to use your permanent IPv6 address instead of your temporary IPv6 address.
## Domain setup

61
go.mod
View File

@@ -1,48 +1,39 @@
module github.com/qdm12/ddns-updater
go 1.21
go 1.26.0
require (
github.com/breml/rootcerts v0.2.14
github.com/breml/rootcerts v0.3.5
github.com/containrrr/shoutrrr v0.8.0
github.com/go-chi/chi/v5 v5.0.11
github.com/golang/mock v1.6.0
github.com/miekg/dns v1.1.57
github.com/qdm12/gosettings v0.4.0-rc9
github.com/qdm12/goshutdown v0.3.0
github.com/qdm12/gosplash v0.1.0
github.com/qdm12/gotree v0.2.0
github.com/go-chi/chi/v5 v5.2.5
github.com/miekg/dns v1.1.72
github.com/qdm12/goservices v0.1.0
github.com/qdm12/gosettings v0.4.4
github.com/qdm12/gosplash v0.2.0
github.com/qdm12/gotree v0.3.0
github.com/qdm12/log v0.1.0
github.com/stretchr/testify v1.8.4
golang.org/x/mod v0.14.0
google.golang.org/api v0.114.0
github.com/stretchr/testify v1.11.1
go.uber.org/mock v0.6.0
golang.org/x/mod v0.35.0
golang.org/x/net v0.53.0
golang.org/x/oauth2 v0.36.0
)
require (
cloud.google.com/go/compute v1.19.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.7.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/oauth2 v0.7.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.15.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/grpc v1.56.3 // indirect
google.golang.org/protobuf v1.30.0 // indirect
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/tools v0.43.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
kernel.org/pub/linux/libs/security/libcap/cap v1.2.69 // indirect
kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 // indirect
kernel.org/pub/linux/libs/security/libcap/cap v1.2.77 // indirect
kernel.org/pub/linux/libs/security/libcap/psx v1.2.77 // indirect
)

241
go.sum
View File

@@ -1,210 +1,79 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/breml/rootcerts v0.2.14 h1:Bu0Ullru+/GTr/S582LCzP1P57WgncIEFylXkBBXgEI=
github.com/breml/rootcerts v0.2.14/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
github.com/breml/rootcerts v0.3.5 h1:oi7YiZ25HH52+mrKyjrMkcAFfnRDUf6HO8aUDr7RlJI=
github.com/breml/rootcerts v0.3.5/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
github.com/containrrr/shoutrrr v0.8.0 h1:mfG2ATzIS7NR2Ec6XL+xyoHzN97H8WPjir8aYzJUSec=
github.com/containrrr/shoutrrr v0.8.0/go.mod h1:ioyQAyu1LJY6sILuNyKaQaw+9Ttik5QePU8atnAdO2o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A=
github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/qdm12/gosettings v0.4.0-rc9 h1:MEVPYQLZfzg3BJgp+DDuY6/9LPAWIlGvPtQ0BeCq9+4=
github.com/qdm12/gosettings v0.4.0-rc9/go.mod h1:uItKwGXibJp2pQ0am6MBKilpjfvYTGiH+zXHd10jFj8=
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g=
github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw=
github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c=
github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
github.com/qdm12/goservices v0.1.0 h1:9sODefm/yuIGS7ynCkEnNlMTAYn9GzPhtcK4F69JWvc=
github.com/qdm12/goservices v0.1.0/go.mod h1:/JOFsAnHFiSjyoXxa5FlfX903h20K5u/3rLzCjYVMck=
github.com/qdm12/gosettings v0.4.4 h1:SM6tOZDf6k8qbjWU8KWyBF4mWIixfsKCfh9DGRLHlj4=
github.com/qdm12/gosettings v0.4.4/go.mod h1:CPrt2YC4UsURTrslmhxocVhMCW03lIrqdH2hzIf5prg=
github.com/qdm12/gosplash v0.2.0 h1:DOxCEizbW6ZG+FgpH2oK1atT6bM8MHL9GZ2ywSS4zZY=
github.com/qdm12/gosplash v0.2.0/go.mod h1:k+1PzhO0th9cpX4q2Nneu4xTsndXqrM/x7NTIYmJ4jo=
github.com/qdm12/gotree v0.3.0 h1:Q9f4C571EFK7ZEsPkEL2oGZX7I+ZhVxhh1ZSydW+5yI=
github.com/qdm12/gotree v0.3.0/go.mod h1:iz06uXmRR4Aq9v6tX7mosXStO/yGHxRA1hbyD0UVeYw=
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
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/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE=
google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
kernel.org/pub/linux/libs/security/libcap/cap v1.2.69 h1:N0m3tKYbkRMmDobh/47ngz+AWeV7PcfXMDi8xu3Vrag=
kernel.org/pub/linux/libs/security/libcap/cap v1.2.69/go.mod h1:Tk5Ip2TuxaWGpccL7//rAsLRH6RQ/jfqTGxuN/+i/FQ=
kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 h1:IdrOs1ZgwGw5CI+BH6GgVVlOt+LAXoPyh7enr8lfaXs=
kernel.org/pub/linux/libs/security/libcap/psx v1.2.69/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=
kernel.org/pub/linux/libs/security/libcap/cap v1.2.77 h1:iQtQTjFUOcTT19fI8sTCzYXsjeVs56et3D8AbKS2Uks=
kernel.org/pub/linux/libs/security/libcap/cap v1.2.77/go.mod h1:oV+IO8kGh0B7TxErbydDe2+BRmi9g/W0CkpVV+QBTJU=
kernel.org/pub/linux/libs/security/libcap/psx v1.2.77 h1:Z06sMOzc0GNCwp6efaVrIrz4ywGJ1v+DP0pjVkOfDuA=
kernel.org/pub/linux/libs/security/libcap/psx v1.2.77/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=

View File

@@ -0,0 +1,5 @@
package backup
type Logger interface {
Info(message string)
}

View File

@@ -0,0 +1,99 @@
package backup
import (
"context"
"path/filepath"
"strconv"
"time"
)
type Service struct {
// Injected fields
backupPeriod time.Duration
dataDir string
outputDir string
logger Logger
// Internal fields
stopCh chan<- struct{}
done <-chan struct{}
}
func New(backupPeriod time.Duration,
dataDir, outputDir string, logger Logger,
) *Service {
return &Service{
logger: logger,
backupPeriod: backupPeriod,
dataDir: dataDir,
outputDir: outputDir,
}
}
func (s *Service) String() string {
return "backup"
}
func makeZipFileName() string {
return "ddns-updater-backup-" + strconv.Itoa(int(time.Now().UnixNano())) + ".zip"
}
func (s *Service) Start(ctx context.Context) (runError <-chan error, startErr error) {
ready := make(chan struct{})
runErrorCh := make(chan error)
stopCh := make(chan struct{})
s.stopCh = stopCh
done := make(chan struct{})
s.done = done
go run(ready, runErrorCh, stopCh, done,
s.outputDir, s.dataDir, s.backupPeriod, s.logger)
select {
case <-ready:
case <-ctx.Done():
return nil, s.Stop()
}
return runErrorCh, nil
}
func run(ready chan<- struct{}, runError chan<- error, stopCh <-chan struct{},
done chan<- struct{}, outputDir, dataDir string, backupPeriod time.Duration,
logger Logger,
) {
defer close(done)
if backupPeriod == 0 {
close(ready)
logger.Info("disabled")
return
}
logger.Info("each " + backupPeriod.String() +
"; writing zip files to directory " + outputDir)
timer := time.NewTimer(backupPeriod)
close(ready)
for {
select {
case <-timer.C:
case <-stopCh:
_ = timer.Stop()
return
}
err := zipFiles(
filepath.Join(outputDir, makeZipFileName()),
filepath.Join(dataDir, "config.json"),
filepath.Join(dataDir, "updates.json"),
)
if err != nil {
runError <- err
return
}
timer.Reset(backupPeriod)
}
}
func (s *Service) Stop() (err error) {
close(s.stopCh)
<-s.done
return nil
}

View File

@@ -6,28 +6,8 @@ import (
"os"
)
var _ FileZiper = (*Ziper)(nil)
type FileZiper interface {
ZipFiles(outputFilepath string, inputFilepaths ...string) error
}
type Ziper struct {
createFile func(name string) (*os.File, error)
openFile func(name string) (*os.File, error)
ioCopy func(dst io.Writer, src io.Reader) (written int64, err error)
}
func NewZiper() *Ziper {
return &Ziper{
createFile: os.Create,
openFile: os.Open,
ioCopy: io.Copy,
}
}
func (z *Ziper) ZipFiles(outputFilepath string, inputFilepaths ...string) error {
f, err := z.createFile(outputFilepath)
func zipFiles(outputFilepath string, inputFilepaths ...string) error {
f, err := os.Create(outputFilepath)
if err != nil {
return err
}
@@ -35,7 +15,7 @@ func (z *Ziper) ZipFiles(outputFilepath string, inputFilepaths ...string) error
w := zip.NewWriter(f)
defer w.Close()
for _, filepath := range inputFilepaths {
err = z.addFile(w, filepath)
err = addFile(w, filepath)
if err != nil {
return err
}
@@ -43,8 +23,8 @@ func (z *Ziper) ZipFiles(outputFilepath string, inputFilepaths ...string) error
return nil
}
func (z *Ziper) addFile(w *zip.Writer, filepath string) error {
f, err := z.openFile(filepath)
func addFile(w *zip.Writer, filepath string) error {
f, err := os.Open(filepath)
if err != nil {
return err
}
@@ -65,6 +45,6 @@ func (z *Ziper) addFile(w *zip.Writer, filepath string) error {
if err != nil {
return err
}
_, err = z.ioCopy(ioWriter, f)
_, err = io.Copy(ioWriter, f)
return err
}

View File

@@ -2,6 +2,7 @@ package config
import (
"fmt"
"net/url"
"os"
"github.com/qdm12/gosettings"
@@ -11,19 +12,31 @@ import (
)
type Health struct {
ServerAddress *string
HealthchecksioUUID *string
// ServerAddress is the listening address:port of the
// health server, which defaults to the empty string,
// meaning the server will not run.
ServerAddress *string
HealthchecksioBaseURL string
HealthchecksioUUID *string
}
func (h *Health) SetDefaults() {
h.ServerAddress = gosettings.DefaultPointer(h.ServerAddress, "127.0.0.1:9999")
h.ServerAddress = gosettings.DefaultPointer(h.ServerAddress, "")
h.HealthchecksioBaseURL = gosettings.DefaultComparable(h.HealthchecksioBaseURL, "https://hc-ping.com")
h.HealthchecksioUUID = gosettings.DefaultPointer(h.HealthchecksioUUID, "")
}
func (h Health) Validate() (err error) {
err = validate.ListeningAddress(*h.ServerAddress, os.Getuid())
if *h.ServerAddress != "" {
err = validate.ListeningAddress(*h.ServerAddress, os.Getuid())
if err != nil {
return fmt.Errorf("server listening address: %w", err)
}
}
_, err = url.Parse(h.HealthchecksioBaseURL)
if err != nil {
return fmt.Errorf("server listening address: %w", err)
return fmt.Errorf("healthchecks.io base URL: %w", err)
}
return nil
@@ -35,8 +48,13 @@ func (h Health) String() string {
func (h Health) toLinesNode() *gotree.Node {
node := gotree.New("Health")
node.Appendf("Server listening address: %s", *h.ServerAddress)
if *h.ServerAddress == "" {
node.Appendf("Server is disabled")
} else {
node.Appendf("Server listening address: %s", *h.ServerAddress)
}
if *h.HealthchecksioUUID != "" {
node.Appendf("Healthchecks.io base URL: %s", h.HealthchecksioBaseURL)
node.Appendf("Healthchecks.io UUID: %s", *h.HealthchecksioUUID)
}
return node
@@ -44,5 +62,6 @@ func (h Health) toLinesNode() *gotree.Node {
func (h *Health) Read(reader *reader.Reader) {
h.ServerAddress = reader.Get("HEALTH_SERVER_ADDRESS")
h.HealthchecksioBaseURL = reader.String("HEALTH_HEALTHCHECKSIO_BASE_URL")
h.HealthchecksioUUID = reader.Get("HEALTH_HEALTHCHECKSIO_UUID")
}

View File

@@ -1,6 +1,11 @@
package config
import (
"fmt"
"io/fs"
"path/filepath"
"strconv"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree"
@@ -8,10 +13,18 @@ import (
type Paths struct {
DataDir *string
Config *string
// Umask is the custom umask to use for the system, if different than zero.
// If it is set to zero, the system umask is unchanged.
// It cannot be nil in the internal state.
Umask *fs.FileMode
}
func (p *Paths) setDefaults() {
p.DataDir = gosettings.DefaultPointer(p.DataDir, "./data")
defaultConfig := filepath.Join(*p.DataDir, "config.json")
p.Config = gosettings.DefaultPointer(p.Config, defaultConfig)
p.Umask = gosettings.DefaultPointer(p.Umask, fs.FileMode(0))
}
func (p Paths) Validate() (err error) {
@@ -25,9 +38,36 @@ func (p Paths) String() string {
func (p Paths) toLinesNode() *gotree.Node {
node := gotree.New("Paths")
node.Appendf("Data directory: %s", *p.DataDir)
node.Appendf("Config file: %s", *p.Config)
umaskString := "system default"
if *p.Umask != 0 {
umaskString = p.Umask.String()
}
node.Appendf("Umask: %s", umaskString)
return node
}
func (p *Paths) read(reader *reader.Reader) {
func (p *Paths) read(reader *reader.Reader) (err error) {
p.DataDir = reader.Get("DATADIR")
p.Config = reader.Get("CONFIG_FILEPATH")
umaskString := reader.String("UMASK")
if umaskString != "" {
umask, err := parseUmask(umaskString)
if err != nil {
return fmt.Errorf("parse umask: %w", err)
}
p.Umask = &umask
}
return nil
}
func parseUmask(s string) (umask fs.FileMode, err error) {
const base, bitSize = 8, 32
umaskUint64, err := strconv.ParseUint(s, base, bitSize)
if err != nil {
return 0, err
}
return fs.FileMode(umaskUint64), nil
}

View File

@@ -0,0 +1,46 @@
package config
import (
"io/fs"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_parseUmask(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
s string
umask fs.FileMode
errMessage string
}{
"invalid": {
s: "a",
errMessage: `strconv.ParseUint: parsing "a": invalid syntax`,
},
"704": {
s: "704",
umask: 0o704,
},
"0704": {
s: "0704",
umask: 0o0704,
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()
umask, err := parseUmask(testCase.s)
if testCase.errMessage != "" {
assert.EqualError(t, err, testCase.errMessage)
} else {
assert.NoError(t, err)
}
assert.Equal(t, testCase.umask, umask)
})
}
}

View File

@@ -70,28 +70,28 @@ func (p *PubIP) toLinesNode() (node *gotree.Node) {
node.Appendf("HTTP enabled: %s", gosettings.BoolToYesNo(p.HTTPEnabled))
if *p.HTTPEnabled {
childNode := node.Appendf("HTTP IP providers")
childNode := node.Append("HTTP IP providers")
for _, provider := range p.HTTPIPProviders {
childNode.Appendf(provider)
childNode.Append(provider)
}
childNode = node.Appendf("HTTP IPv4 providers")
childNode = node.Append("HTTP IPv4 providers")
for _, provider := range p.HTTPIPv4Providers {
childNode.Appendf(provider)
childNode.Append(provider)
}
childNode = node.Appendf("HTTP IPv6 providers")
childNode = node.Append("HTTP IPv6 providers")
for _, provider := range p.HTTPIPv6Providers {
childNode.Appendf(provider)
childNode.Append(provider)
}
}
node.Appendf("DNS enabled: %s", gosettings.BoolToYesNo(p.DNSEnabled))
if *p.DNSEnabled {
node.Appendf("DNS timeout: %s", p.DNSTimeout)
childNode := node.Appendf("DNS over TLS providers")
childNode := node.Append("DNS over TLS providers")
for _, provider := range p.DNSProviders {
childNode.Appendf(provider)
childNode.Append(provider)
}
}
@@ -111,7 +111,8 @@ func (p *PubIP) ToHTTPOptions() (options []http.Option) {
}
func stringsToHTTPProviders(providers []string, ipVersion ipversion.IPVersion) (
updatedProviders []http.Provider) {
updatedProviders []http.Provider,
) {
updatedProvidersSet := make(map[string]struct{}, len(providers))
for _, provider := range providers {
if provider != all {
@@ -158,9 +159,7 @@ func (p *PubIP) ToDNSPOptions() (options []dns.Option) {
}
}
var (
ErrNoPublicIPDNSProvider = errors.New("no public IP DNS provider specified")
)
var ErrNoPublicIPDNSProvider = errors.New("no public IP DNS provider specified")
func (p PubIP) validateDNSProviders() (err error) {
if len(p.DNSProviders) == 0 {
@@ -190,10 +189,12 @@ func (p PubIP) validateHTTPIPv6Providers() (err error) {
var (
ErrNoPublicIPHTTPProvider = errors.New("no public IP HTTP provider specified")
ErrURLIsNotValidHTTPS = errors.New("URL is not valid or not HTTPS")
)
func validateHTTPIPProviders(providerStrings []string,
version ipversion.IPVersion) (err error) {
version ipversion.IPVersion,
) (err error) {
if len(providerStrings) == 0 {
return fmt.Errorf("%w", ErrNoPublicIPHTTPProvider)
}
@@ -215,8 +216,11 @@ func validateHTTPIPProviders(providerStrings []string,
}
// Custom URL check
url, err := url.Parse(providerString)
if err == nil && url != nil && url.Scheme == "https" {
if strings.HasPrefix(providerString, "url:") {
url, err := url.Parse(providerString[4:])
if err != nil || url.Scheme != "https" {
return fmt.Errorf("%w: %s", ErrURLIsNotValidHTTPS, providerString)
}
continue
}
@@ -259,7 +263,12 @@ func (p *PubIP) read(r *reader.Reader, warner Warner) (err error) {
copy(httpIPProvidersTemp, p.HTTPIPProviders)
p.HTTPIPProviders = make([]string, 0, len(p.HTTPIPProviders))
for _, provider := range httpIPProvidersTemp {
if provider != "opendns" {
switch provider {
case "opendns": // no longer available, for a long time
case "google": // found no longer working on 2024.09.17
warner.Warnf("http provider google will be ignored " +
"since it is no longer supported by Google")
default:
p.HTTPIPProviders = append(p.HTTPIPProviders, provider)
}
}

View File

@@ -1,7 +1,7 @@
package config
type Warner interface {
Warnf(format string, a ...interface{})
Warnf(format string, a ...any)
}
func handleDeprecated(warner Warner, oldKey, newKey string) {

View File

@@ -11,11 +11,13 @@ import (
)
type Server struct {
Enabled *bool
ListeningAddress string
RootURL string
}
func (s *Server) setDefaults() {
s.Enabled = gosettings.DefaultPointer(s.Enabled, true)
s.ListeningAddress = gosettings.DefaultComparable(s.ListeningAddress, ":8000")
s.RootURL = gosettings.DefaultComparable(s.RootURL, "/")
}
@@ -36,6 +38,9 @@ func (s Server) String() string {
}
func (s Server) toLinesNode() *gotree.Node {
if !*s.Enabled {
return gotree.New("Server: disabled")
}
node := gotree.New("Server")
node.Appendf("Listening address: %s", s.ListeningAddress)
node.Appendf("Root URL: %s", s.RootURL)
@@ -43,6 +48,11 @@ func (s Server) toLinesNode() *gotree.Node {
}
func (s *Server) read(reader *reader.Reader, warner Warner) (err error) {
s.Enabled, err = reader.BoolPtr("SERVER_ENABLED")
if err != nil {
return err
}
s.RootURL = reader.String("ROOT_URL")
// Retro-compatibility

View File

@@ -80,7 +80,8 @@ func (c Config) toLinesNode() *gotree.Node {
}
func (c *Config) Read(reader *reader.Reader,
warner Warner) (err error) {
warner Warner,
) (err error) {
err = c.Client.read(reader)
if err != nil {
return fmt.Errorf("reading client settings: %w", err)
@@ -107,7 +108,10 @@ func (c *Config) Read(reader *reader.Reader,
}
c.Health.Read(reader)
c.Paths.read(reader)
err = c.Paths.read(reader)
if err != nil {
return fmt.Errorf("reading paths settings: %w", err)
}
err = c.Backup.read(reader)
if err != nil {

View File

@@ -1,6 +1,7 @@
package config
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@@ -14,7 +15,7 @@ func Test_Settings_String(t *testing.T) {
s := defaultSettings.String()
const expected = `Settings summary:
expected := `Settings summary:
├── HTTP client
| └── Timeout: 20s
├── Update
@@ -37,9 +38,11 @@ func Test_Settings_String(t *testing.T) {
| ├── Listening address: :8000
| └── Root URL: /
├── Health
| └── Server listening address: 127.0.0.1:9999
| └── Server is disabled
├── Paths
| ── Data directory: ./data
| ── Data directory: ./data
| ├── Config file: ` + filepath.Join("data", "config.json") + `
| └── Umask: system default
├── Backup: disabled
└── Logger
├── Level: INFO

View File

@@ -43,7 +43,7 @@ func (s Shoutrrr) ToLinesNode() *gotree.Node {
childNode := node.Appendf("Addresses")
for _, address := range s.Addresses {
childNode.Appendf(address)
childNode.Append(address)
}
return node

View File

@@ -1,6 +1,7 @@
package data
import (
"context"
"sync"
"github.com/qdm12/ddns-updater/internal/records"
@@ -19,3 +20,17 @@ func NewDatabase(data []records.Record, persistentDB PersistentDatabase) *Databa
persistentDB: persistentDB,
}
}
func (db *Database) String() string {
return "database"
}
func (db *Database) Start(_ context.Context) (_ <-chan error, err error) {
return nil, nil //nolint:nilnil
}
func (db *Database) Stop() (err error) {
db.Lock() // ensure write operation finishes
defer db.Unlock()
return db.persistentDB.Close()
}

View File

@@ -7,5 +7,5 @@ import (
type PersistentDatabase interface {
Close() error
StoreNewIP(domain, host string, ip netip.Addr, t time.Time) (err error)
StoreNewIP(domain, owner string, ip netip.Addr, t time.Time) (err error)
}

View File

@@ -12,7 +12,7 @@ var ErrRecordNotFound = errors.New("record not found")
func (db *Database) Select(id uint) (record records.Record, err error) {
db.RLock()
defer db.RUnlock()
if int(id) > len(db.data)-1 {
if id > uint(len(db.data))-1 {
return record, fmt.Errorf("%w: for id %d", ErrRecordNotFound, id)
}
return db.data[id], nil

View File

@@ -9,7 +9,7 @@ import (
func (db *Database) Update(id uint, record records.Record) (err error) {
db.Lock()
defer db.Unlock()
if int(id) > len(db.data)-1 {
if id > uint(len(db.data))-1 {
return fmt.Errorf("%w: for id %d", ErrRecordNotFound, id)
}
currentCount := len(db.data[id].History)
@@ -19,7 +19,7 @@ func (db *Database) Update(id uint, record records.Record) (err error) {
if newCount > currentCount {
if err := db.persistentDB.StoreNewIP(
record.Provider.Domain(),
record.Provider.Host(),
record.Provider.Owner(),
record.History.GetCurrentIP(),
record.History.GetSuccessTime(),
); err != nil {
@@ -28,9 +28,3 @@ func (db *Database) Update(id uint, record records.Record) (err error) {
}
return nil
}
func (db *Database) Close() (err error) {
db.Lock() // ensure write operation finishes
defer db.Unlock()
return db.persistentDB.Close()
}

View File

@@ -10,9 +10,9 @@ import (
"github.com/qdm12/ddns-updater/internal/constants"
)
func MakeIsHealthy(db AllSelecter, resolver LookupIPer) func() error {
return func() (err error) {
return isHealthy(db, resolver)
func MakeIsHealthy(db AllSelecter, resolver LookupIPer) func(ctx context.Context) error {
return func(ctx context.Context) (err error) {
return isHealthy(ctx, db, resolver)
}
}
@@ -23,7 +23,7 @@ var (
)
// isHealthy checks all the records were updated successfully and returns an error if not.
func isHealthy(db AllSelecter, resolver LookupIPer) (err error) {
func isHealthy(ctx context.Context, db AllSelecter, resolver LookupIPer) (err error) {
records := db.SelectAll()
for _, record := range records {
if record.Status == constants.FAIL {
@@ -39,7 +39,7 @@ func isHealthy(db AllSelecter, resolver LookupIPer) (err error) {
return fmt.Errorf("%w: for hostname %s", ErrRecordIPNotSet, hostname)
}
lookedUpNetIPs, err := resolver.LookupIP(context.Background(), "ip", hostname)
lookedUpNetIPs, err := resolver.LookupIP(ctx, "ip", hostname)
if err != nil {
return err
}

View File

@@ -10,10 +10,6 @@ import (
"time"
)
func IsClientMode(args []string) bool {
return len(args) > 1 && args[1] == "healthcheck"
}
type Client struct {
*http.Client
}

View File

@@ -1,17 +1,18 @@
package health
import (
"context"
"net/http"
)
func newHandler(healthcheck func() error) http.Handler {
func newHandler(healthcheck func(context.Context) error) http.Handler {
return &handler{
healthcheck: healthcheck,
}
}
type handler struct {
healthcheck func() error
healthcheck func(context.Context) error
}
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -19,7 +20,7 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
}
err := h.healthcheck()
err := h.healthcheck(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return

View File

@@ -7,9 +7,7 @@ import (
"net/http"
)
var (
ErrHTTPStatusCodeNotOK = errors.New("status code is not OK")
)
var ErrHTTPStatusCodeNotOK = errors.New("status code is not OK")
func CheckHTTP(ctx context.Context, client *http.Client) (err error) {
const url = "https://github.com"

View File

@@ -2,51 +2,18 @@ package health
import (
"context"
"net/http"
"time"
"github.com/qdm12/goservices/httpserver"
)
type Server struct {
address string
logger Logger
handler http.Handler
}
func NewServer(address string, logger Logger, healthcheck func() error) *Server {
handler := newHandler(healthcheck)
return &Server{
address: address,
logger: logger,
handler: handler,
}
}
func (s *Server) Run(ctx context.Context, done chan<- struct{}) {
defer close(done)
server := http.Server{
Addr: s.address,
Handler: s.handler,
ReadHeaderTimeout: time.Second,
ReadTimeout: time.Second,
}
go func() {
<-ctx.Done()
s.logger.Warn("shutting down (context canceled)")
defer s.logger.Warn("shut down")
const shutdownGraceDuration = 2 * time.Second
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownGraceDuration)
defer cancel()
err := server.Shutdown(shutdownCtx)
if err != nil {
s.logger.Error("failed shutting down: " + err.Error())
}
}()
for ctx.Err() == nil {
s.logger.Info("listening on " + s.address)
err := server.ListenAndServe()
if err != nil && ctx.Err() == nil { // server crashed
s.logger.Error(err.Error())
s.logger.Info("restarting")
}
}
func NewServer(address string, logger Logger, healthcheck func(context.Context) error) (
server *httpserver.Server, err error,
) {
name := "health"
return httpserver.New(httpserver.Settings{
Handler: newHandler(healthcheck),
Name: &name,
Address: &address,
Logger: logger,
})
}

View File

@@ -9,29 +9,43 @@ import (
// New creates a new healthchecks.io client.
// If passed an empty uuid string, it acts as no-op implementation.
func New(httpClient *http.Client, uuid string) *Client {
func New(httpClient *http.Client, baseURL, uuid string) *Client {
return &Client{
httpClient: httpClient,
baseURL: baseURL,
uuid: uuid,
}
}
type Client struct {
httpClient *http.Client
baseURL string
uuid string
}
var (
ErrStatusCode = errors.New("bad status code")
var ErrStatusCode = errors.New("bad status code")
type State string
const (
Ok State = "ok"
Start State = "start"
Fail State = "fail"
Exit0 State = "0"
Exit1 State = "1"
)
func (c *Client) Ping(ctx context.Context) (err error) {
func (c *Client) Ping(ctx context.Context, state State) (err error) {
if c.uuid == "" {
return nil
}
request, err := http.NewRequestWithContext(ctx, http.MethodGet,
"https://hc-ping.com/"+c.uuid, nil)
url := c.baseURL + "/" + c.uuid
if state != Ok {
url += "/" + string(state)
}
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return fmt.Errorf("creating request: %w", err)
}

View File

@@ -3,5 +3,16 @@ package models
type BuildInformation struct {
Version string `json:"version"`
Commit string `json:"commit"`
Date string `json:"buildDate"`
Created string `json:"buildDate"`
}
func (b BuildInformation) VersionString() string {
if b.Version != "latest" {
return b.Version
}
const commitShortHashLength = 7
if len(b.Commit) != commitShortHashLength {
return "latest"
}
return b.Version + "-" + b.Commit[:7]
}

View File

@@ -18,16 +18,16 @@ type HistoryEvent struct { // current and previous ips
// GetPreviousIPs returns an antichronological list of previous
// IP addresses if there is any.
func (h History) GetPreviousIPs() []netip.Addr {
func (h History) GetPreviousIPs() (previousIPs []netip.Addr) {
if len(h) <= 1 {
return nil
}
IPs := make([]netip.Addr, len(h)-1)
const two = 2
for i := len(h) - two; i >= 0; i-- {
IPs[i] = h[i].IP
previousIPs = make([]netip.Addr, len(h)-1)
mostRecentPreviousIPIndex := len(h) - 2 //nolint:mnd
for i := range previousIPs {
previousIPs[i] = h[mostRecentPreviousIPIndex-i].IP
}
return IPs
return previousIPs
}
// GetCurrentIP returns the current IP address (latest in history).

View File

@@ -1,12 +1,57 @@
package models
import (
"net/netip"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func Test_GetPreviousIPs(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
h History
previousIPs []netip.Addr
}{
"empty_history": {
h: History{},
},
"single_event": {
h: History{
{IP: netip.MustParseAddr("1.2.3.4")},
},
},
"two_events": {
h: History{
{IP: netip.MustParseAddr("1.2.3.4")},
{IP: netip.MustParseAddr("5.6.7.8")}, // last one
},
previousIPs: []netip.Addr{
netip.MustParseAddr("1.2.3.4"),
},
},
"three_events": {
h: History{
{IP: netip.MustParseAddr("1.2.3.4")},
{IP: netip.MustParseAddr("5.6.7.8")},
{IP: netip.MustParseAddr("9.6.7.8")}, // last one
},
previousIPs: []netip.Addr{
netip.MustParseAddr("5.6.7.8"),
netip.MustParseAddr("1.2.3.4"),
},
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()
previousIPs := testCase.h.GetPreviousIPs()
assert.Equal(t, testCase.previousIPs, previousIPs)
})
}
}
func Test_GetDurationSinceSuccess(t *testing.T) {
t.Parallel()
tests := map[string]struct {
@@ -27,7 +72,6 @@ func Test_GetDurationSinceSuccess(t *testing.T) {
},
}
for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
now, _ := time.Parse("2006-01-02", "2000-01-01")

Some files were not shown because too many files have changed in this diff Show More