Compare commits

...

399 Commits

Author SHA1 Message Date
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
Quentin McGaw
c338c28ce3 fix(ci): set write contents permissions for goreleaser 2024-02-03 18:50:31 +00:00
CyberAustin
aa12ccc0b9 feat(provider): goip.de support (#623) 2024-02-03 19:28:46 +01:00
Quentin McGaw
cfdc1f3dbe docs(readme): use relative links in readme and make it compatible with docker hub
- use `enable-url-completion` in docker hub description step
2024-02-03 09:16:36 +00:00
Quentin McGaw
85b64cbeeb feat(publicip/http): add ipleak support for all IP versions 2024-02-03 09:16:33 +00:00
Quentin McGaw
1c76c84bc3 docs(readme): add missing ipify option for HTTP IPv4 or IPv6 public IP fetching 2024-02-03 08:18:17 +00:00
Quentin McGaw
2afec6c0ac feat(publicip/http): add Spdyn provider 2024-02-03 08:13:37 +00:00
Quentin McGaw
7b573266e9 chore(config): remove pointer from validate function receivers 2024-02-03 07:59:40 +00:00
Quentin McGaw
3107e00048 chore(config): fix providers slice capacity to right size 2024-02-03 07:59:08 +00:00
Quentin McGaw
1c80aaa3ec chore(ci): rename created -> date versioning variable
- to match goreleaser default ldflags `main.date`
- rename Dockerfile ldflag from created to date
- remove goreleaser custom ldflags and use defaults ones
2024-02-03 07:55:33 +00:00
Quentin McGaw
3935b3640b feat(logs): split logs for cooldown and ban
- cooldown happens sometimes, especially after the first program run. It is logged at the debug level.
- ban should not happen often and is logged at the info level
- adds more information to both log calls
2024-02-02 18:25:25 +00:00
Quentin McGaw
0247d5ca0a chore(update): better log message when succeeding fetching the public ip after a failure 2024-02-02 15:55:09 +00:00
Quentin McGaw
c494662976 fix(publicip): remove noip due to only http availability 2024-02-02 15:51:07 +00:00
Quentin McGaw
6f71982a7e feat(update): getip return explicit IPv6 not supported error
- only if all tries result in an error pointing to IPv6 being not supported
2024-02-01 16:46:57 +00:00
Quentin McGaw
a018b75609 chore(update): remove unneeded ip validity check 2024-02-01 16:36:17 +00:00
Quentin McGaw
e3ed94ff43 feat(update): more accurate time of events
- update call may take some time
- event time should be AFTER update is finished
- new ban time should be set to time AFTER update is finished
2024-02-01 16:36:17 +00:00
Quentin McGaw
2aaf02537a chore(update): requery current time on each update need check
- Checking with DNS resolution each record can take some time
- More "exact", although impact should be none
2024-02-01 16:36:17 +00:00
Quentin McGaw
f7db1ac7cf chore(update): requery current time after checking which records need an update
- Checking with DNS resolution each record can take some time
- More "exact", although impact should be none
2024-02-01 16:36:17 +00:00
Quentin McGaw
ebd852fcc1 fix(update): set initial fail status if public ip fetching failed 2024-02-01 16:36:17 +00:00
Quentin McGaw
443daad0fa chore(getip): change log messages
- log each error as they happen at the debug level
- return all errors joined if all tries fail and log at the error level
2024-02-01 16:36:13 +00:00
Quentin McGaw
b6e8f699a6 hotfix(ipv6): restore compatibility with old IPV6_PREFIX 2024-02-01 09:23:28 +00:00
Quentin McGaw
8d282cd6d9 hotfix(params): only log warning if ipv6 suffix is set for ipv4 2024-02-01 09:07:16 +00:00
Quentin McGaw
ed0a0d07ff chore(ci): move config files to .github/workflows/configs 2024-02-01 07:13:20 +00:00
Quentin McGaw
875cd027d7 fix(publicip/dns): use address of Cloudflare for ipv4 or ipv6 2024-01-31 14:31:35 +00:00
Quentin McGaw
dc2b7aaa0b chore(publicip/dns): set dial timeout to fetcher timeout 2024-01-31 14:18:07 +00:00
Quentin McGaw
816406dc41 feat(publicip/http): add api64.ipify.org 2024-01-31 14:18:02 +00:00
Quentin McGaw
621c3a13be docs(typo): indentifier -> identifier 2024-01-29 17:11:37 +00:00
Quentin McGaw
bad0d3aeda fix(ipv6): add JSON IPv6 suffix parameter (#611)
- Remove `IPV6_PREFIX` environment variable (unneeded) and remove associated code
- Update all documentation for each provider supporting IPv6
- Build IPv6 as prefix:suffix when getting it from a public IP source for each record IPv6 suffix parameter
- Automatically disable provider_ip if public ip is IPv6 and IPv6 suffix is set (they are not compatible with each other)
2024-01-29 17:31:07 +01:00
Quentin McGaw
25b8e02acc fix(spdyn): validate host value even if the token is set 2024-01-29 16:26:34 +00:00
Quentin McGaw
9b1c98ce49 chore(servercow): validation of host within main switch block 2024-01-29 16:26:03 +00:00
Quentin McGaw
9e6b7e5b99 feat(config): add :53 to resolver address if no port is given 2024-01-29 14:08:40 +00:00
Quentin McGaw
00a9653e95 hotfix(ci): increase markdown link check timeout to 20s 2024-01-29 10:02:53 +00:00
Quentin McGaw
b771b5c9b5 chore(namecheap): make code only IPv4 compatible
- Namecheap does not support IPv6 dynamic updates still
2024-01-29 09:43:01 +00:00
Quentin McGaw
67b83d402a docs(ip_version): add missing parameter for 4 providers
- allinkl, name.com, netcup, nowdns, porkbun
2024-01-29 09:42:57 +00:00
Quentin McGaw
fc500f7443 docs(ip_version): update description of all providers 2024-01-29 09:37:33 +00:00
Quentin McGaw
2658f609da chore(servercow): use injected domain instead of re-parsing it 2024-01-29 09:06:07 +00:00
Quentin McGaw
f60f7212c3 hotfix(dondominio): use DDNS only endpoint dondns.dondominio.com/json 2024-01-29 08:12:30 +00:00
Quentin McGaw
eee8485543 hotfix(dondominio): update API endpoint to simple-api.dondominio.net 2024-01-29 07:42:42 +00:00
Quentin McGaw
5c166a6ab6 hotfix(markdown): fix links and add link check config 2024-01-28 12:10:49 +00:00
Quentin McGaw
417a26282e feat(dondominio): allow any host values including wildcard 2024-01-28 09:42:39 +00:00
Quentin McGaw
8839db93dc feat(dondominio): create record if it does not exist 2024-01-28 09:41:03 +00:00
Quentin McGaw
7eee3fcccf fix(dondominio): use endpoint api.dondominio.com 2024-01-28 09:40:28 +00:00
Quentin McGaw
8888694b9a hotfix(ci): fix markdown issues 2024-01-28 08:31:41 +00:00
Quentin McGaw
b3cae52145 feat(build): workflow to have binaries on releases
- Fixes #294
2024-01-28 08:22:21 +00:00
Quentin McGaw
005c0c8f04 feat(ci): add markdown workflow
- Fixes #592
- Remove docker hub description workflow
- Build workflow ignores md files for spellcheck
2024-01-28 08:22:21 +00:00
Quentin McGaw
bc54ba5aca feat(update): skip update if public IP not found 2024-01-28 07:28:03 +00:00
Quentin McGaw
481321b5d3 chore(update): simplify shouldUpdateRecord functions
- `shouldUpdateRecordNoLookup`
- `shouldUpdateRecordWithLookup`
2024-01-28 07:28:03 +00:00
Slavik
115ce8faac docs(readme): fix shoutrrr bad link (#609) 2024-01-27 22:29:16 +01:00
Quentin McGaw
e665684fa4 fix(google): removed since no longer functional
- Logs error if provider google is used
- Documentation updated
- Fixes #605
2024-01-27 21:24:27 +00:00
Quentin McGaw
74168ad4ab hotfix(database): fix historical events reading
- Regression introduced in 7ed63a036e (PR #514)
2024-01-27 21:17:19 +00:00
Quentin McGaw
7f0e858fa0 fix(update): do not write empty IP on fetch fail 2024-01-24 20:02:07 +00:00
Quentin McGaw
41de082ffc fix(log): ipv4 no lookup update log 2024-01-24 19:55:53 +00:00
Quentin McGaw
6521103359 fix(infomaniak): handle new response prefixes
- Fixes #604
2024-01-23 15:39:24 +00:00
Quentin McGaw
af9d45702d chore(deps): tidy go modules dependencies 2024-01-23 15:38:43 +00:00
Quentin McGaw
f8bb927de6 fix(dnsomatic): remove username validation regex
- Fixes #398
2024-01-20 08:53:56 +00:00
Quentin McGaw
0c561d4378 feat(custom): add custom provider
- Sends HTTP GET request to url given with ip information
- Configurable ipv4 and ipv6 query parameter keys
- Configurable response success detection with a regex
- Treat non status OK 200 responses as failures
2024-01-19 19:54:43 +00:00
Quentin McGaw
12c46e7635 hotfix(settings): allow /proc/sys/net/ipv4/ip_unprivileged_port_start to be absent
- Assume default unprivileged start port is 1024
2024-01-19 17:01:14 +00:00
Quentin McGaw
c51a41e1a4 fix(settings): web ui listening port validation
- Check start of unprivileged ports on Linux
- Check if running program has bind capability on Linux
- Fixes #335
2024-01-19 14:17:30 +00:00
Quentin McGaw
1ff838e0ae fix(dnsomatic): treat 'all' host as proxied to reduce updates
Every period, compare the public IP address with the last IP address stored in updates.json. Only update when a difference is found
2024-01-19 11:51:06 +00:00
Quentin McGaw
010634db28 chore(github): additional labels
- Good idea
- Motivated!
- Foolproof settings
- Wildcard
2024-01-19 11:42:56 +00:00
Quentin McGaw
ce792ac753 chore(github): change order of category labels 2024-01-19 11:42:56 +00:00
Quentin McGaw
59d8791b8d fix(dyndns): allow wildcard hosts 2024-01-19 11:42:54 +00:00
Quentin McGaw
c8ba7edcb7 fix(dyn): always give myip parameter, provider_ip setting disabled 2024-01-19 11:42:50 +00:00
Quentin McGaw
8096944623 fix(http): bump timeout from 10s to 20s 2024-01-19 11:42:48 +00:00
Quentin McGaw
869b010853 fix(porkbun): delete ALIAS record before creating an A or AAAA record 2024-01-18 17:15:41 +00:00
Quentin McGaw
46054fb631 chore(porkbun): split API specific code to api.go 2024-01-18 17:03:59 +00:00
Quentin McGaw
28b2e121da feat(porkbun): json decode error messages 2024-01-18 16:58:49 +00:00
Quentin McGaw
37f473160c fix(desec): allow wildcard hosts 2024-01-18 14:18:59 +00:00
Quentin McGaw
b1e71b7a77 fix(desec): default host to "@" if empty 2024-01-18 14:18:59 +00:00
Quentin McGaw
d02fe17d55 chore(github): remove labels from_name fields 2024-01-18 14:18:59 +00:00
dependabot[bot]
f9f5d590c3 chore(deps): bump github/codeql-action from 2 to 3 (#583) 2024-01-18 15:07:30 +01:00
dependabot[bot]
80d0755bfb chore(deps): bump docker/build-push-action from 4.0.0 to 5.1.0 (#582) 2024-01-18 15:07:16 +01:00
dependabot[bot]
3d619566c1 chore(deps): bump docker/metadata-action from 4 to 5 (#585) 2024-01-18 15:06:56 +01:00
dependabot[bot]
68b0dbf5f1 chore(deps): bump docker/setup-buildx-action from 2 to 3 (#587) 2024-01-18 15:06:30 +01:00
Quentin McGaw
1a9cf0e0a6 chore(github): update labels 2024-01-18 14:05:17 +00:00
Quentin McGaw
706dcc7ba2 feat(shoutrrr): bump from v0.7.0 to v0.8.0 2024-01-18 11:21:32 +00:00
Quentin McGaw
ec1ee7803b docs(inwx,nowdns): remove trail comma in JSON example 2024-01-18 11:11:16 +00:00
Quentin McGaw
8dc39453e1 hotfix(config): change Dockerfile default LISTENING_PORT -> LISTENING_ADDRESS 2024-01-18 11:08:02 +00:00
Quentin McGaw
3e638326ed feat(config): LISTENING_ADDRESS configuration key (#590) 2024-01-18 08:18:15 +01:00
Quentin McGaw
015d3fca55 docs: fix documentation for SHOUTRRR_DEFAULT_TITLE 2024-01-16 10:18:28 +00:00
Quentin McGaw
0a41ff5b3b feat(config): read config from flags then environment variables 2024-01-16 10:15:22 +00:00
Quentin McGaw
ab6908b8cc chore(config): upgrade to qdm12/gosettings v0.4.0-rc6 2024-01-16 10:13:28 +00:00
Quentin McGaw
e100656d6f chore(deps): remove dependency on golang.org/x/net 2024-01-15 14:40:21 +00:00
Quentin McGaw
d36384744b chore(deps): bump golang.org/x/mod from v0.12.0 to v0.14.0 2024-01-15 14:39:12 +00:00
Quentin McGaw
1eaa51d871 chore(deps): bump github.com/miekg/dns from v1.1.54 to v1.1.57 2024-01-15 14:38:35 +00:00
Quentin McGaw
343ab1cf96 chore(github): add dependabot configuration 2024-01-15 14:38:24 +00:00
Quentin McGaw
bb807330bb chore(deps): bump breml/rootcerts from v0.2.11 to v0.2.14 2024-01-15 14:38:22 +00:00
Quentin McGaw
f132a82c0a chore(deps): upgrade chi from v4 to v5.0.11 2024-01-15 14:35:58 +00:00
Quentin McGaw
f16945e128 chore(build): bump Go from 1.20 to 1.21 2024-01-15 14:25:19 +00:00
Quentin McGaw
e97ced5608 chore(Docker): review environment variables set in Dockerfile
- Remove outdated `SHOUTRRR_DEFAULT_TITLE`
- Add `IPV6_PREFIX=/128`
- Add `HEALTH_SERVER_ADDRESS=127.0.0.1:9999`
- Add `HEALTH_HEALTHCHECKSIO_UUID=`
2024-01-15 14:24:28 +00:00
Quentin McGaw
f98f0874a0 chore(lint): bump linter from v1.53.2 to v1.55.2 2024-01-15 14:21:14 +00:00
Quentin McGaw
e50b38b331 chore(internal/update): simplify log calls 2024-01-15 14:21:09 +00:00
Quentin McGaw
9d5c48dbf3 chore(build): bump Alpine from 3.18 to 3.19 2024-01-15 14:02:56 +00:00
Quentin McGaw
91741a5aad feat(health): HEALTH_HEALTHCHECKSIO_UUID for healthchecks.io 2024-01-15 13:56:56 +00:00
Quentin McGaw (desktop)
ff5767aa38 feat(provider): add Ionos provider 2024-01-15 13:11:02 +00:00
Felix Wirth
7ed63a036e fix(internal/persistence/json): get events by IP version (#514) 2024-01-15 10:46:06 +01:00
Quentin McGaw
cdae4fcced chore(persistence/json): simplify StoreNewIP method 2024-01-15 09:40:58 +00:00
Quentin McGaw
44c77ef848 chore(persistence/json): improve data check error messages 2024-01-15 09:40:21 +00:00
Quentin McGaw
45881d137b chore(main): rename s -> provider in providers for loop 2024-01-15 09:38:57 +00:00
Quentin McGaw
1c982f7d8d chore(internal/data): remove unused GetEvents method 2024-01-15 09:13:16 +00:00
Quentin McGaw
658fe5be2a chore: fix data .gitignore path 2024-01-15 09:12:59 +00:00
3deep5me
679a1d1b70 docs: add Kubernetes examples & documentation (#402)
Fix #396
2024-01-14 22:28:31 +01:00
varner-owl
ee495bbfea fix(gandi): personal access token support (#568)
- `"token"` replaced with `"personal_access_token"` (retro-compatible)
2024-01-14 20:44:45 +01:00
dependabot[bot]
ba2e77798a chore(deps): bump google.golang.org/grpc from 1.53.0 to 1.56.3 (#552) 2023-11-17 15:05:19 +01:00
Lieblinger
301e4d6cc4 feat(provider): support Hetzner (#503) 2023-11-17 15:04:05 +01:00
Quentin McGaw
496781ac34 fix(dnsomatic): allow email aliases for username 2023-11-17 14:02:04 +00:00
dependabot[bot]
e7558b7e2b chore(deps): bump golang.org/x/net from 0.10.0 to 0.17.0 (#540) 2023-11-17 14:52:56 +01:00
Quentin McGaw
daa5edb56f hotfix(dynv6): fix lint error for nested if 2023-11-17 13:51:11 +00:00
Profiles
6669b39d45 fix(dynv6): set url ip field as 'auto' when provider_ip is true (#549) 2023-11-17 14:04:57 +01:00
michelwi
e635b19af6 fix(duckdns): support for ipv6 (#542)
* fix `ip6` -> `ipv6` url parameter key name
* fix ipv6 response handling
2023-10-14 11:52:53 +02:00
guangwu
c7c5468e5f docs: fix typos (#532) 2023-09-28 16:42:06 +02:00
Artur Pragacz
13ccb4ffd9 fix(ui): fix current ip ipinfo.io href link (#529) 2023-09-13 21:43:53 +02:00
Felix Wirth
7fac178b7d fix(ipv6): mask IPv6 address fetched (#515) 2023-08-08 09:07:39 +02:00
Felix Wirth
5c0b2012b7 fix(ui): include ip version for all providers (#512) 2023-08-08 09:06:08 +02:00
dependabot[bot]
a20cc6e1c2 chore(deps): bump google.golang.org/grpc from 1.50.1 to 1.53.0 (#500) 2023-08-04 16:27:44 +02:00
CyberAustin
5c3e407272 feat(providers): support now-dns (#504) 2023-08-04 16:26:56 +02:00
Felix Wirth
b92274347a fix(log): IPv4 and IPv6 only debug logging (#513) 2023-08-04 15:56:18 +02:00
biochron
7b3b6609fd feat(provider): support deSEC (#496) 2023-06-30 09:09:44 +02:00
Quentin McGaw
6f75aa1457 feat(ci): tagged Docker images without v prefix 2023-06-19 06:35:23 +00:00
Quentin McGaw
27456b628d fix(inwx): fix success codes detection 2023-06-17 14:08:18 +00:00
Quentin McGaw
0c98229588 hotfix(publicip/dns): fix error for unsupported any answer 2023-06-17 13:57:41 +00:00
Quentin McGaw
320d91d8e3 change(publicip/dns): use DNS over TLS only
- Fix critical issue #492
- Remove `google` dns provider since it does not support DNS over TLS
2023-06-17 13:57:41 +00:00
nils måsén
828373da7f docs(readme): pin shoutrrr link to go.mod shoutrrr version (#491) 2023-06-17 10:37:24 +02:00
Quentin McGaw
336bf057ab chore(update): rename run.go to service.go 2023-06-16 09:18:38 +00:00
Quentin McGaw
5a353c1b66 chore(models): remove unneeded HTML string alias type 2023-06-16 09:14:43 +00:00
Quentin McGaw
a5e49eb866 fix(server/ui): favicon href link fixed 2023-06-16 06:34:43 +00:00
Quentin McGaw
e4bb82d316 feat(shoutrrr): add SHOUTRRR_DEFAULT_TITLE
- Disable `SHOUTRRR_PARAMS` and log a warning if used
- Only add `&title=` parameter to shoutrrr address if it's not set
2023-06-16 06:31:33 +00:00
Quentin McGaw
3fdda01509 hotfix(sources/env): UPDATE_PERIOD back to PERIOD 2023-06-16 05:57:07 +00:00
Quentin McGaw
330fae1469 chore(ovh): remove unneeded sentinel errors 2023-06-15 07:13:19 +00:00
Quentin McGaw
1033711ab4 chore(provider): review update errors and wrappings
- `ErrBadHTTPStatus` -> `ErrHTTPStatusNotValid`
- `ErrNumberOfResultsReceived` -> `ErrResultsCountReceived`
- `ErrNoResultReceived` -> `ErrReceivedNoResult`
- `ErrAbuse` -> `ErrBannedAbuse`
- `ErrInvalidSystemParam` -> `ErrSystemParamNotValid`
- `ErrNoIPInResponse` -> `ErrReceivedNoIP`
- `ErrUnsuccessfulResponse` -> `ErrUnsuccessful`
- Remove `ErrRequestEncode` and `ErrRequestMarshal` and wrap errors with string instead
- Remove `ErrUnmarshalResponse` and wrap errors with string instead
- Add `ErrResponseTooShort`
- Remove `ErrNotFound` and replace with more precise not found errors
- Add `ErrRecordResourceSetNotFound` error
- Sort errors list alphabetically
2023-06-15 07:13:14 +00:00
Quentin McGaw
f2b56afda7 chore(providers): review error wrappings for client.Do
- Do not wrap with sentinel error ErrUnsuccesfulResponse
- Wrap with text `doing http request`
2023-06-15 06:42:38 +00:00
Quentin McGaw
c26b3fc0d3 chore(provider/errors): review error names and text
- Move problem part at end of error variable name for better autocompletion
- Move problem part at end of error message to match error variable name
- Change `Empty` -> `NotSet`
- Change `Malformed` -> `NotValid`
- Sort all errors alphabetically
2023-06-15 06:31:29 +00:00
Quentin McGaw
99f83f3f12 chore(providers): review error wrappings for validation errors 2023-06-15 06:23:39 +00:00
Quentin McGaw
9f6e9750c3 chore(provider): remove intermediary errors
- Wrap errors with strings instead of sentinel errors
2023-06-15 06:15:25 +00:00
Quentin McGaw
05473044a6 fix(providers): review all http headers
- Add missing header(s) for some providers
- Remove provider receiver on `setHeaders` function when not needed
- Inline `setHeaders` function body when used only once for provider
- Remove unneeded headers such as content-type for GET requests
2023-06-15 06:14:13 +00:00
Quentin McGaw
e5188906bf fix(duckdns): send ip address when provider_ip is false 2023-06-15 06:04:46 +00:00
Quentin McGaw
6e65c4f3a5 chore(providers): use errors.BadRequest for badrequest responses only
- Replace HTTP request creation error wrappings with string
- Add missing HTTP request creation error wrappings
2023-06-15 05:48:07 +00:00
Joseph Diekhoff
06b6288e58 feat(providers): support name.com (#474) 2023-06-15 07:29:36 +02:00
Quentin McGaw
4ac2bd7933 docs(zoneedit): warn about minimum update period 2023-06-14 07:07:30 +00:00
Quentin McGaw
31374c3634 chore(deps): run go mod tidy 2023-06-14 07:01:55 +00:00
Quentin McGaw
c98f8d6052 feat(providers): support zoneedit.com (#482) 2023-06-14 09:01:16 +02:00
Quentin McGaw
07cdce1fa2 hotfix(sources/env): leave HTTPIPProviders as nil if not value is found
- Bug introduced in feaac82c11 due to retro-compatibility
2023-06-14 06:32:07 +00:00
Quentin McGaw
4a6020558f chore(all): rename settings package to provider
- internal/provider: rename `Settings` interface to `Provider`
- internal/params: rename *Settings* to *Providers*
- internal/records: rename `Settings` to `Provider`
2023-06-14 06:25:56 +00:00
Azorimor
09eb9e706d feat(provider): support netcup.de (#361) 2023-06-14 08:16:40 +02:00
Quentin McGaw
5455207852 docs(readme): undocument SHOUTRRR_PARAMS 2023-06-13 14:59:46 +00:00
Quentin McGaw
feaac82c11 docs(publicip): remove outdated ddnss http option 2023-06-13 14:41:40 +00:00
Quentin McGaw
baacd052ce fix(publicip/http): remove opendns option 2023-06-13 14:41:37 +00:00
Quentin McGaw
c12b7e50d7 feat(pkg/publicip/dns): add opendns option 2023-06-13 14:41:35 +00:00
Quentin McGaw
954dffd3a7 fix(ipv6): replace bad regex with custom IPv6 extract function
- Fix HTTP IPv6 fetching invalid extraction result
- Affects IPv6 comparison for allinkl, dnsomatic, google, he and noip
2023-06-13 07:16:55 +00:00
Quentin McGaw
204be2072e fix(ci): write creation date correctly 2023-06-13 05:43:43 +00:00
Quentin McGaw
bfa6883f37 hotfix(settings): fix settings string test 2023-06-12 21:19:38 +00:00
Quentin McGaw
6513e5869a fix(sources/env): take into account absent values for PUBLICIP_FETCHERS 2023-06-12 20:26:46 +00:00
Quentin McGaw
81b336fe07 hotfix(main): fix bad HTTP -> DNS public ip enable flag 2023-06-12 20:26:16 +00:00
Quentin McGaw
83488e2020 hotfix(settings): do not log backup settings if it's disabled 2023-06-12 20:21:08 +00:00
Quentin McGaw
4922b1db0b hotfix(resolver): do not validate address as a listening address 2023-06-12 20:16:51 +00:00
Quentin McGaw
0f127bd58d hotfix(settings): IPV6_MASK -> IPV6_PREFIX 2023-06-12 15:54:46 +00:00
Quentin McGaw
9b5e0e1187 docs(cloudflare): wildcard hosts are possible now 2023-06-12 15:50:19 +00:00
Quentin McGaw
9d7244fade fix(noip): do not check for received IP when using provider ip 2023-06-12 15:16:27 +00:00
Quentin McGaw
936bf4386e docs(readme): add missing easydns entry 2023-06-12 14:47:12 +00:00
Quentin McGaw
5676673e9e chore(lint): bump golangci-lint v1.52.2 -> v1.53.2 2023-06-12 14:34:43 +00:00
charlydaily
6a9ba7ecb4 feat: add support for easydns (#480) 2023-06-12 16:34:00 +02:00
Quentin McGaw
2949112ad1 hotfix(settings): fix settings string test 2023-06-12 14:18:46 +00:00
Quentin McGaw
6d9568c7e5 chore(shoutrrr): create internal/shoutrrr package 2023-06-12 14:13:34 +00:00
Dennis Gaida
0241e32e92 docs(readme): document SHOUTRRR_PARAMS environment variable (#411) 2023-06-12 14:54:16 +02:00
Quentin McGaw
a9cb4ec63b feat(settings): log out tree of settings 2023-06-12 12:22:05 +00:00
Quentin McGaw
3e4d359140 chore(settings): default root url to / (cannot be empty) 2023-06-12 12:19:30 +00:00
Quentin McGaw
ac67611b06 hotfix(settings/shoutrrr): fix defaulting 2023-06-12 11:17:16 +00:00
Quentin McGaw
aebe5988f1 feat(shoutrrr): add debug logs 2023-06-12 11:10:44 +00:00
Quentin McGaw
b1eea74db1 chore(github): update issue labels 2023-06-12 11:10:26 +00:00
Quentin McGaw
6d018d920d chore(.github): more explicit title requirement 2023-06-12 10:00:02 +00:00
Quentin McGaw
88f98aebe4 chore(github): remove automated qdm12 assignee 2023-06-12 09:59:09 +00:00
Quentin McGaw
9a4a268926 chore(all): migrate from net.IP* to net/netip 2023-06-12 09:48:43 +00:00
Quentin McGaw
3e6fb5ead4 hotfix(sources/env): fix server address read from HEALTH_SERVER_ADDRESS 2023-06-12 09:46:39 +00:00
Quentin McGaw
330d65dc8f hotfix(settings/ipv6): set mask default only when unset 2023-06-12 09:16:59 +00:00
Quentin McGaw
baabc80b07 chore(providers): replace ip nil check with useProviderIP check
- Affects duckdns, infomaniak and namecheap
2023-06-12 08:50:23 +00:00
Quentin McGaw
d3d6725021 fix(opendns): do not compare ip received when using "provider_ip": true 2023-06-12 08:49:09 +00:00
Quentin McGaw
e4fd4592fd fix(njalla): do not compare ip received when using "provider_ip": true 2023-06-12 08:48:58 +00:00
Quentin McGaw
dfcd97f6c1 fix(update): getNewIPs error handling
- If get ip for v4 or v6 fails, do not use resulting nil ip
- If get ip for v6 fails, do not mask resulting nil ip
2023-06-12 08:31:25 +00:00
Quentin McGaw
619f626ae8 fix(opendns): new IP comparison check 2023-06-12 08:12:33 +00:00
Quentin McGaw
4b51c8b35b chore(config): rework entire global settings reading
- Use qdm12/gosettings
- No functional changes (at least intended)
- Remove qdm12/golibs dependency

chore(params): remove dependency on golibs
2023-06-12 06:17:24 +00:00
Quentin McGaw
05b2535fee chore(pkg/publicip/info): remove dependency on qdm12/golibs 2023-06-08 18:15:41 +00:00
Quentin McGaw
ec072d2862 chore(settings): no dependency on qdm12/golibs/verification 2023-06-08 18:11:48 +00:00
Quentin McGaw
57a928df92 chore(main): replace golibs/connectivity with health.CheckHTTP 2023-06-08 12:57:08 +00:00
Quentin McGaw
62808c6d1c chore(deps): run go mod tidy 2023-06-08 12:32:56 +00:00
Stavros Kois
61ead94870 docs: add missing provider_ip field in examples (#466)
- he.net
- selfhost.de
- servercow
2023-06-07 20:24:24 +02:00
Stavros Kois
10b6b79c70 docs(inwx): fix bad provider name opendns -> inwx (#468)
Closes #410
2023-06-07 20:09:43 +02:00
Stavros Kois
625b687b77 docs(noip): add missing username field in example (#469) 2023-06-07 20:08:49 +02:00
Stavros Kois
4028e8ade2 docs(opendns): fix bad provider name dyn to opendns in example (#470) 2023-06-07 20:08:24 +02:00
Stavros Kois
9ee74a3e75 docs(infomaniak): add missing username field in example (#467) 2023-06-07 20:07:35 +02:00
Stavros Kois
35c8928bd4 docs(dnsomatic): fix settings fields (#463)
- replace invalid `token` with `username` + `password`
- add `provider_ip` field
2023-06-07 20:06:35 +02:00
Stavros Kois
d31318d1fe docs(gcp): fix typo for provider name (#465) 2023-06-07 20:03:15 +02:00
Quentin McGaw
e41a39008e chore(persistence/json): drop dependency on qdm12/golibs 2023-06-07 17:58:25 +00:00
Quentin McGaw
97ca03544a chore(deps): bump github.com/stretchr/testify from v1.8.1 to v1.8.4 2023-06-07 09:19:49 +00:00
Quentin McGaw
8631b4e8d5 chore(deps): bump github.com/miekg/dns from v1.1.42 to v1.1.54 2023-06-07 09:19:10 +00:00
Quentin McGaw
a6e5053800 chore(deps): bump chi from v1.5.4 to v4.1.2 2023-06-07 09:18:40 +00:00
Quentin McGaw
5f107ffc2c chore(deps): bump golang.org/x/net from v0.1.0 to v0.10.0 2023-06-07 09:18:02 +00:00
Quentin McGaw
c39df2796f chore(ci): add mocks check step 2023-06-07 09:17:38 +00:00
Quentin McGaw
790ff8b78b chore(ci): add build-skip workflow 2023-06-07 09:13:47 +00:00
Quentin McGaw
39faaa771f chore(ci): rework entire workflow 2023-06-07 09:13:04 +00:00
354 changed files with 17503 additions and 9443 deletions

View File

@@ -1 +1 @@
FROM qmcgaw/godevcontainer
FROM qmcgaw/godevcontainer:v0.21-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](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

@@ -4,9 +4,9 @@
.vscode
docs
readme
!readme/*.go
.gitignore
config.json
docker-compose.yml
LICENSE
README.md
ui/favicon.svg

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

@@ -1,9 +1,8 @@
---
name: Bug
about: Report a bug
title: 'Bug: ...'
labels: ":bug: bug"
assignees: qdm12
title: 'Bug: FILL THIS TEXT OR ISSUE WILL BE CLOSED'
labels:
---

View File

@@ -1,9 +1,8 @@
---
name: Feature request
about: Suggest a feature to add to this project
title: 'Feature request: ...'
labels: ":bulb: feature request"
assignees: qdm12
title: 'Feature request: FILL THIS TEXT OR ISSUE WILL BE CLOSED'
labels:
---

View File

@@ -1,9 +1,8 @@
---
name: Help
about: Ask for help
title: 'Help: ...'
labels: ":pray: help wanted"
assignees:
title: 'Help: FILL THIS TEXT OR ISSUE WILL BE CLOSED'
labels:
---

15
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
version: 2
updates:
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: docker
directory: /
schedule:
interval: "daily"
- package-ecosystem: gomod
directory: /
schedule:
interval: "daily"

113
.github/labels.yml vendored
View File

@@ -1,51 +1,62 @@
- name: ":robot: bot"
color: "69cde9"
description: ""
- name: ":bug: bug"
color: "b60205"
description: ""
- name: ":game_die: dependencies"
color: "0366d6"
description: ""
- name: ":memo: documentation"
color: "c5def5"
description: ""
- name: ":busts_in_silhouette: duplicate"
color: "cccccc"
description: ""
- name: ":sparkles: enhancement"
color: "0054ca"
description: ""
- name: ":bulb: feature request"
color: "0e8a16"
description: ""
- name: ":mega: feedback"
color: "03a9f4"
description: ""
- name: ":rocket: future maybe"
color: "fef2c0"
description: ""
- name: ":hatching_chick: good first issue"
color: "7057ff"
description: ""
- name: ":pray: help wanted"
color: "4caf50"
description: ""
- name: ":hand: hold"
color: "24292f"
description: ""
- name: ":no_entry_sign: invalid"
color: "e6e6e6"
description: ""
- name: ":interrobang: maybe bug"
color: "ff5722"
description: ""
- name: ":thinking: needs more info"
color: "795548"
description: ""
- name: ":question: question"
color: "3f51b5"
description: ""
- name: ":coffin: wontfix"
color: "ffffff"
description: ""
- name: "Status: 🗯️ Waiting for feedback"
color: "f7d692"
- name: "Status: 🔴 Blocked"
color: "f7d692"
description: "Blocked by another issue or pull request"
- name: "Status: 🔒 After next release"
color: "f7d692"
description: "Will be done after the next release"
- name: "Closed: ⚰️ Inactive"
color: "959a9c"
description: "No answer was received for weeks"
- name: "Closed: 👥 Duplicate"
color: "959a9c"
description: "Issue duplicates an existing issue"
- name: "Closed: 🗑️ Bad issue"
color: "959a9c"
- name: "Priority: 🚨 Urgent"
color: "03adfc"
- name: "Priority: 💤 Low priority"
color: "03adfc"
- name: "Complexity: ☣️ Hard to do"
color: "ff9efc"
- name: "Complexity: 🟩 Easy to do"
color: "ff9efc"
- name: "Category: Config problem 📝"
color: "ffc7ea"
- name: "Category: Healthcheck 🩺"
color: "ffc7ea"
- name: "Category: Documentation ✒️"
description: "A problem with the readme or in the docs/ directory"
color: "ffc7ea"
- name: "Category: Maintenance ⛓️"
description: "Anything related to code or other maintenance"
color: "ffc7ea"
- name: "Category: Good idea 🎯"
description: "This is a good idea, judged by the maintainers"
color: "ffc7ea"
- name: "Category: Motivated! 🙌"
description: "Your pumpness makes me pumped! The issue or PR shows great motivation!"
color: "ffc7ea"
- name: "Category: Foolproof settings 👼"
color: "ffc7ea"
- name: "Category: Label missing ❗"
color: "ffc7ea"
- name: "Category: Provider update ♻️"
color: "ffc7ea"
- name: "Category: Shoutrrr 📢"
color: "ffc7ea"
- name: "Category: IP fetching 📥"
color: "ffc7ea"
- name: "Category: Database 🗃️"
color: "ffc7ea"
- name: "Category: New provider 🆕"
color: "ffc7ea"
- name: "Category: Web UI 🖱️"
color: "ffc7ea"
- name: "Category: Wildcard 🃏"
color: "ffc7ea"

35
.github/workflows/build-skip.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: No trigger file paths
on:
push:
branches:
- master
paths-ignore:
- .github/workflows/build.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
pull_request:
paths-ignore:
- .github/workflows/build.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
jobs:
verify:
runs-on: ubuntu-latest
permissions:
actions: read
steps:
- name: No trigger path triggered for required verify workflow.
run: exit 0

View File

@@ -1,6 +1,11 @@
name: CI
on:
release:
types:
- published
push:
branches:
- master
paths:
- .github/workflows/build.yml
- cmd/**
@@ -26,14 +31,27 @@ on:
jobs:
verify:
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
env:
DOCKER_BUILDKIT: "1"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: reviewdog/action-misspell@v1
with:
locale: "US"
level: error
exclude: |
*.md
- name: Linting
run: docker build --target lint .
- name: Mocks check
run: docker build --target mocks .
- name: Build test image
run: docker build --target test -t test-container .
@@ -44,12 +62,27 @@ jobs:
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
test-container
# We run this here to use the caching of the previous steps
- name: Build final image
run: docker build .
run: docker build -t final-image .
codeql:
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- uses: github/codeql-action/init@v4
with:
languages: go
- uses: github/codeql-action/autobuild@v4
- uses: github/codeql-action/analyze@v4
publish:
needs: [verify]
if: |
github.repository == 'qdm12/ddns-updater' &&
(
@@ -57,52 +90,77 @@ jobs:
github.event_name == 'release' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')
)
needs: [verify, codeql]
permissions:
actions: read
contents: write
packages: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0 # for gorelease last step
- uses: docker/setup-qemu-action@v2
- uses: docker/setup-buildx-action@v2
# extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
flavor: |
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
images: |
ghcr.io/qdm12/ddns-updater
qmcgaw/ddns-updater
tags: |
type=ref,event=pr
type=semver,pattern={{major}}.{{minor}}.{{patch}}
type=semver,pattern=v{{major}}.{{minor}}.{{patch}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern=v{{major}}.{{minor}}
type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
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/login-action@v2
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
password: ${{ github.token }}
- name: Set variables
id: vars
run: |
BRANCH=${GITHUB_REF#refs/heads/}
TAG=${GITHUB_REF#refs/tags/}
echo ::set-output name=commit::$(git rev-parse --short HEAD)
echo ::set-output name=build_date::$(date -u +%Y-%m-%dT%H:%M:%SZ)
if [ "$TAG" != "$GITHUB_REF" ]; then
echo ::set-output name=version::$TAG
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/s390x,linux/ppc64le,linux/riscv64
elif [ "$BRANCH" = "master" ]; then
echo ::set-output name=version::latest
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/s390x,linux/ppc64le,linux/riscv64
else
echo ::set-output name=version::${BRANCH##*/}
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7
fi
- name: Short commit
id: shortcommit
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
- name: Build and push final image
uses: docker/build-push-action@v3
uses: docker/build-push-action@v6
with:
platforms: ${{ steps.vars.outputs.platforms }}
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/s390x,linux/ppc64le,linux/riscv64
labels: ${{ steps.meta.outputs.labels }}
build-args: |
BUILD_DATE=${{ steps.vars.outputs.build_date }}
COMMIT=${{ steps.vars.outputs.commit }}
VERSION=${{ steps.vars.outputs.version }}
tags: |
qmcgaw/ddns-updater:${{ steps.vars.outputs.version }}
ghcr.io/${{ github.repository_owner }}/ddns-updater:${{ steps.vars.outputs.version }}
CREATED=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
COMMIT=${{ steps.shortcommit.outputs.value }}
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
tags: ${{ steps.meta.outputs.tags }}
push: true
- if: github.event_name == 'release'
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- if: github.event_name == 'release'
uses: goreleaser/goreleaser-action@v6
with:
version: latest
args: release --clean --config .github/workflows/configs/.goreleaser.yaml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,30 @@
before:
hooks:
- go mod download
builds:
- main: ./cmd/ddns-updater/main.go
flags:
- -trimpath
env:
- CGO_ENABLED=0
targets:
# See https://goreleaser.com/customization/build/
- linux_amd64
- linux_386
- linux_arm64
- linux_arm_7
- linux_arm_6
- linux_arm_5
- darwin_amd64
- darwin_arm64
- windows_amd64
- windows_386
- windows_arm64
archives:
- format: binary
checksum:
name_template: "checksums.txt"
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc

View File

@@ -0,0 +1,47 @@
{
"ignorePatterns": [
{
"pattern": "^http://localhost"
},
{
"pattern": "^https://api6.ipify.org$"
},
{
"pattern": "^http://ip1.dynupdate6.no-ip.com$"
},
{
"pattern": "^https://ap.www.namecheap.com/Domains/DomainControlPanel/example.com/advancedns$"
},
{
"pattern": "^https://www.godaddy.com"
},
{
"pattern": "^https://www.namecheap.com"
},
{
"pattern": "https://www.linode.com/docs/products/tools/api/guides/manage-api-tokens/"
},
{
"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",
"retryOn429": false,
"fallbackRetryDelay": "30s",
"aliveStatusCodes": [
200,
206
]
}

View File

@@ -1,19 +0,0 @@
name: Docker Hub description
on:
push:
branches: [master]
paths:
- README.md
- .github/workflows/dockerhub-description.yml
jobs:
dockerHubDescription:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v3.1.0
env:
DOCKERHUB_USERNAME: qmcgaw
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
DOCKERHUB_REPOSITORY: qmcgaw/ddns-updater

View File

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

21
.github/workflows/markdown-skip.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Markdown
on:
push:
branches:
- master
paths-ignore:
- "**.md"
- .github/workflows/markdown.yml
pull_request:
paths-ignore:
- "**.md"
- .github/workflows/markdown.yml
jobs:
markdown:
runs-on: ubuntu-latest
permissions:
actions: read
steps:
- name: No trigger path triggered for required markdown workflow.
run: exit 0

48
.github/workflows/markdown.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Markdown
on:
push:
branches:
- master
paths:
- "**.md"
- .github/workflows/markdown.yml
pull_request:
paths:
- "**.md"
- .github/workflows/markdown.yml
jobs:
markdown:
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
steps:
- uses: actions/checkout@v4
- uses: DavidAnson/markdownlint-cli2-action@v18
with:
globs: "**.md"
config: .markdownlint.json
- uses: reviewdog/action-misspell@v1
with:
locale: "US"
level: error
pattern: |
*.md
- uses: gaurav-nelson/github-action-markdown-link-check@v1
with:
use-quiet-mode: yes
config-file: .github/workflows/configs/mlc-config.json
- uses: peter-evans/dockerhub-description@v5
if: github.repository == 'qdm12/ddns-updater' && github.event_name == 'push'
with:
username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }}
repository: qmcgaw/ddns-updater
short-description: Container to update DNS records periodically with WebUI for many DNS providers
readme-filepath: README.md
enable-url-completion: true

2
.gitignore vendored
View File

@@ -1 +1 @@
data
/data

View File

@@ -1,61 +1,96 @@
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
- godot
- goerr113
- goheader
- goimports
- gomnd
- gomoddirectives
- goprintffuncname
- gosec
- gosmopolitan
- grouper
- importas
- inamedparam
- interfacebloat
- intrange
- ireturn
- lll
- maintidx
- makezero
- mirror
- misspell
- mnd
- musttag
- nakedret
- nestif
@@ -65,14 +100,17 @@ linters:
- nolintlint
- nosprintfhostport
- paralleltest
- perfsprint
- prealloc
- predeclared
- promlinter
- protogetter
- reassign
- revive
- rowserrcheck
- sloglint
- sqlclosecheck
- tenv
- tagalign
- thelper
- tparallel
- unconvert
@@ -80,8 +118,4 @@ linters:
- usestdlibvars
- wastedassign
- whitespace
run:
skip-dirs:
- .devcontainer
- .github
- zerologlint

8
.markdownlint.json Normal file
View File

@@ -0,0 +1,8 @@
{
"MD013": false,
"MD033": {
"allowed_elements": [
"img"
]
}
}

View File

@@ -1,18 +1,22 @@
ARG BUILDPLATFORM=linux/amd64
ARG ALPINE_VERSION=3.18
ARG GO_VERSION=1.20
ARG XCPUTRANSLATE_VERSION=v0.6.0
ARG GOLANGCI_LINT_VERSION=v1.52.2
ARG ALPINE_VERSION=3.22
ARG GO_VERSION=1.25
ARG XCPUTRANSLATE_VERSION=v0.9.0
ARG GOLANGCI_LINT_VERSION=v2.4.0
ARG MOCKGEN_VERSION=v1.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} 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
ENV CGO_ENABLED=0
RUN apk --update add git g++
# Note: findutils needed to have xargs support `-d` flag for mocks stage.
RUN apk --update add git g++ findutils
COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate
COPY --from=golangci-lint /bin /go/bin/golangci-lint
COPY --from=mockgen /bin /go/bin/mockgen
# Copy repository code and install Go dependencies
COPY go.mod go.sum ./
RUN go mod download
@@ -25,32 +29,49 @@ FROM --platform=$BUILDPLATFORM base AS test
# - we set CGO_ENABLED=1 to have it enabled
# - we installed g++ to support the race detector
ENV CGO_ENABLED=1
COPY readme/ ./readme/
COPY README.md ./README.md
ENTRYPOINT go test -race -coverpkg=./... -coverprofile=coverage.txt -covermode=atomic ./...
FROM --platform=$BUILDPLATFORM base AS lint
COPY .golangci.yml ./
RUN golangci-lint run --timeout=10m
FROM --platform=${BUILDPLATFORM} base AS mocks
RUN git init && \
git config user.email ci@localhost && \
git config user.name ci && \
git config core.fileMode false && \
git add -A && \
git commit -m "snapshot" && \
grep -lr -E '^// Code generated by MockGen\. DO NOT EDIT\.$' . | xargs -r -d '\n' rm && \
go generate -run "mockgen" ./... && \
git diff --exit-code && \
rm -rf .git/
FROM --platform=$BUILDPLATFORM base AS build
RUN mkdir -p /tmp/data
ARG VERSION=unknown
ARG BUILD_DATE="an unknown date"
ARG CREATED="an unknown date"
ARG COMMIT=unknown
ARG TARGETPLATFORM
RUN GOARCH="$(xcputranslate translate -targetplatform ${TARGETPLATFORM} -field arch)" \
GOARM="$(xcputranslate translate -targetplatform ${TARGETPLATFORM} -field arm)" \
go build -trimpath -ldflags="-s -w \
-X 'main.version=$VERSION' \
-X 'main.buildDate=$BUILD_DATE' \
-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= \
@@ -64,10 +85,12 @@ ENV \
PUBLICIP_DNS_TIMEOUT=3s \
HTTP_TIMEOUT=10s \
DATADIR=/updater/data \
CONFIG_FILEPATH=/updater/data/config.json \
RESOLVER_ADDRESS= \
RESOLVER_TIMEOUT=5s \
# Web UI
LISTENING_PORT=8000 \
SERVER_ENABLED=yes \
LISTENING_ADDRESS=:8000 \
ROOT_URL=/ \
# Backup
BACKUP_PERIOD=0 \
@@ -76,18 +99,24 @@ ENV \
LOG_LEVEL=info \
LOG_CALLER=hidden \
SHOUTRRR_ADDRESSES= \
TZ=
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 BUILD_DATE="an unknown date"
ARG CREATED="an unknown date"
ARG COMMIT=unknown
LABEL \
org.opencontainers.image.authors="quentin.mcgaw@gmail.com" \
org.opencontainers.image.version=$VERSION \
org.opencontainers.image.created=$BUILD_DATE \
org.opencontainers.image.created=$CREATED \
org.opencontainers.image.revision=$COMMIT \
org.opencontainers.image.url="https://github.com/qdm12/ddns-updater" \
org.opencontainers.image.documentation="https://github.com/qdm12/ddns-updater" \
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

308
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,23 +20,40 @@ 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)
[![MIT](https://img.shields.io/github/license/qdm12/ddns-updater)](https://github.com/qdm12/ddns-updater/master/LICENSE)
[![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
@@ -46,66 +61,115 @@ Light container updating DNS A and/or AAAA records periodically for multiple DNS
- DuckDNS
- DynDNS
- Dynu
- DynV6
- EasyDNS
- FreeDNS
- Gandi
- GCP
- GoDaddy
- Google
- GoIP.de
- He.net
- Hetzner
- Infomaniak
- INWX
- Ionos
- Linode
- Loopia
- LuaDNS
- Myaddr
- Name.com
- Namecheap
- NameSilo
- Netcup
- NoIP
- Now-DNS
- Njalla
- OpenDNS
- OVH
- Porkbun
- Route53
- 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)
- 11MB Docker image based on a Go static binary in a Scratch Docker image
- Web user interface (Mobile)
![Mobile Web UI](readme/webui-mobile.png)
- Send notifications with [**Shoutrrr**](https://containrrr.dev/shoutrrr/v0.8/services/overview/) using `SHOUTRRR_ADDRESSES`
- 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
- Docker healthcheck verifying the DNS resolution of your domains
- Highly configurable
- Send notifications with [**Shoutrrr**](https://containrrr.dev/shoutrrr/services/overview/) using `SHOUTRRR_ADDRESSES`
- Compatible with `amd64`, `386`, `arm64`, `armv7`, `armv6`, `s390x`, `ppc64le`, `riscv64` CPU architectures.
## 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"
}
]
@@ -117,25 +181,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. ⚠️ If you use IPv6, you might need to set `-e IPV6_PREFIX=/64` (`/64` is your prefix, depending on your ISP)
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
You can also use [docker-compose.yml](https://github.com/qdm12/ddns-updater/blob/master/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).
### 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
@@ -157,52 +212,71 @@ Start by having the following content in *config.json*, or in your `CONFIG` envi
For each setting, you need to fill in parameters.
Check the documentation for your DNS provider:
- [Aliyun](https://github.com/qdm12/ddns-updater/blob/master/docs/aliyun.md)
- [Cloudflare](https://github.com/qdm12/ddns-updater/blob/master/docs/cloudflare.md)
- [DDNSS.de](https://github.com/qdm12/ddns-updater/blob/master/docs/ddnss.de.md)
- [DigitalOcean](https://github.com/qdm12/ddns-updater/blob/master/docs/digitalocean.md)
- [DD24](https://github.com/qdm12/ddns-updater/blob/master/docs/domaindiscount24.md)
- [DonDominio](https://github.com/qdm12/ddns-updater/blob/master/docs/dondominio.md)
- [DNSOMatic](https://github.com/qdm12/ddns-updater/blob/master/docs/dnsomatic.md)
- [DNSPod](https://github.com/qdm12/ddns-updater/blob/master/docs/dnspod.md)
- [Dreamhost](https://github.com/qdm12/ddns-updater/blob/master/docs/dreamhost.md)
- [DuckDNS](https://github.com/qdm12/ddns-updater/blob/master/docs/duckdns.md)
- [DynDNS](https://github.com/qdm12/ddns-updater/blob/master/docs/dyndns.md)
- [Dynu](https://github.com/qdm12/ddns-updater/blob/master/docs/dynu.md)
- [DynV6](https://github.com/qdm12/ddns-updater/blob/master/docs/dynv6.md)
- [FreeDNS](https://github.com/qdm12/ddns-updater/blob/master/docs/freedns.md)
- [Gandi](https://github.com/qdm12/ddns-updater/blob/master/docs/gandi.md)
- [GCP](https://github.com/qdm12/ddns-updater/blob/master/docs/gcp.md)
- [GoDaddy](https://github.com/qdm12/ddns-updater/blob/master/docs/godaddy.md)
- [Google](https://github.com/qdm12/ddns-updater/blob/master/docs/google.md)
- [He.net](https://github.com/qdm12/ddns-updater/blob/master/docs/he.net.md)
- [Infomaniak](https://github.com/qdm12/ddns-updater/blob/master/docs/infomaniak.md)
- [INWX](https://github.com/qdm12/ddns-updater/blob/master/docs/inwx.md)
- [Linode](https://github.com/qdm12/ddns-updater/blob/master/docs/linode.md)
- [LuaDNS](https://github.com/qdm12/ddns-updater/blob/master/docs/luadns.md)
- [Namecheap](https://github.com/qdm12/ddns-updater/blob/master/docs/namecheap.md)
- [NoIP](https://github.com/qdm12/ddns-updater/blob/master/docs/noip.md)
- [Njalla](https://github.com/qdm12/ddns-updater/blob/master/docs/njalla.md)
- [OpenDNS](https://github.com/qdm12/ddns-updater/blob/master/docs/opendns.md)
- [OVH](https://github.com/qdm12/ddns-updater/blob/master/docs/ovh.md)
- [Porkbun](https://github.com/qdm12/ddns-updater/blob/master/docs/porkbun.md)
- [Selfhost.de](https://github.com/qdm12/ddns-updater/blob/master/docs/selfhost.de.md)
- [Servercow.de](https://github.com/qdm12/ddns-updater/blob/master/docs/servercow.md)
- [Spdyn](https://github.com/qdm12/ddns-updater/blob/master/docs/spdyn.md)
- [Strato.de](https://github.com/qdm12/ddns-updater/blob/master/docs/strato.md)
- [Variomedia.de](https://github.com/qdm12/ddns-updater/blob/master/docs/variomedia.md)
- [Aliyun](docs/aliyun.md)
- [Allinkl](docs/allinkl.md)
- [ChangeIP](docs/changeip.md)
- [Cloudflare](docs/cloudflare.md)
- [DD24](docs/dd24.md)
- [DDNSS.de](docs/ddnss.de.md)
- [deSEC](docs/desec.md)
- [DigitalOcean](docs/digitalocean.md)
- [Domeneshop](docs/domeneshop.md)
- [DonDominio](docs/dondominio.md)
- [DNSOMatic](docs/dnsomatic.md)
- [DNSPod](docs/dnspod.md)
- [Dreamhost](docs/dreamhost.md)
- [DuckDNS](docs/duckdns.md)
- [DynDNS](docs/dyndns.md)
- [Dynu](docs/dynu.md)
- [DynV6](docs/dynv6.md)
- [EasyDNS](docs/easydns.md)
- [FreeDNS](docs/freedns.md)
- [Gandi](docs/gandi.md)
- [GCP](docs/gcp.md)
- [GoDaddy](docs/godaddy.md)
- [GoIP.de](docs/goip.md)
- [He.net](docs/he.net.md)
- [Hetzner](docs/hetzner.md)
- [Infomaniak](docs/infomaniak.md)
- [INWX](docs/inwx.md)
- [Ionos](docs/ionos.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)
- [Njalla](docs/njalla.md)
- [OpenDNS](docs/opendns.md)
- [OVH](docs/ovh.md)
- [Porkbun](docs/porkbun.md)
- [Route53](docs/route53.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 `--log-level`.
| Environment variable | Default | Description |
| --- | --- | --- |
| `CONFIG` | | One line JSON object containing the entire config (takes precendence over config.json file) if specified |
| `CONFIG` | | One line JSON object containing the entire config (takes precedence over config.json file) if specified |
| `PERIOD` | `5m` | Default period of IP address check, following [this format](https://golang.org/pkg/time/#ParseDuration) |
| `IPV6_PREFIX` | `/128` | IPv6 prefix used to mask your public IPv6 address and your record IPv6 address. Ranges from `/0` to `/128` depending on your ISP. |
| `PUBLICIP_FETCHERS` | `all` | Comma separated fetcher types to obtain the public IP address from `http` and `dns` |
| `PUBLICIP_HTTP_PROVIDERS` | `all` | Comma separated providers to obtain the public IP address (ipv4 or ipv6). See the [Public IP section](#public-ip) |
| `PUBLICIPV4_HTTP_PROVIDERS` | `all` | Comma separated providers to obtain the public IPv4 address only. See the [Public IP section](#public-ip) |
@@ -211,17 +285,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 |
| `LISTENING_PORT` | `8000` | Internal TCP listening port for the web UI |
| `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_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/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
@@ -234,23 +314,39 @@ This allows you not to be blocked for making too many requests.
You can otherwise customize it with the following:
- `PUBLICIP_HTTP_PROVIDERS` gets your public IPv4 or IPv6 address. It can be one or more of the following:
- `opendns` using [https://diagnostic.opendns.com/myip](https://diagnostic.opendns.com/myip)
- `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)
- `ddnss` using [https://ddnss.de/meineip.php](https://ddnss.de/meineip.php)
- `google` using [https://domains.google.com/checkip](https://domains.google.com/checkip)
- You can also specify an HTTPS URL such as `https://ipinfo.io/ip`
- `spdyn` using [https://checkip.spdyn.de](https://checkip.spdyn.de/)
- `ipleak` using [https://ipleak.net/json](https://ipleak.net/json)
- `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)
- `noip` using [http://ip1.dynupdate.no-ip.com](http://ip1.dynupdate.no-ip.com)
- 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)
- `noip` using [http://ip1.dynupdate6.no-ip.com](http://ip1.dynupdate6.no-ip.com)
- You can also specify an HTTPS URL such as `https://ipinfo.io/ip`
- `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). It can be one or more of the following:
- `google`
- `cloudflare`
- `opendns`
### Host firewall
@@ -296,7 +392,7 @@ We could do an API call to get the record IP address every period, but that woul
## Testing
- The automated healthcheck verifies all your records are up to date [using DNS lookups](https://github.com/qdm12/ddns-updater/blob/master/internal/healthcheck/healthcheck.go#L15)
- The automated healthcheck verifies all your records are up to date [using DNS lookups](internal/health/check.go#L42)
- You can also manually check, by:
1. Going to your DNS management webpage
1. Setting your record to `127.0.0.1`
@@ -308,7 +404,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:
@@ -318,19 +414,18 @@ You can use optional build arguments with `--build-arg KEY=VALUE` from the table
| `UID` | `1000` | User ID running the container |
| `GID` | `1000` | User group ID running the container |
| `VERSION` | `unknown` | Version of the program and Docker image |
| `BUILD_DATE` | `an unknown date` | Build date of the program and Docker image |
| `CREATED` | `an unknown date` | Build date of the program and Docker image |
| `COMMIT` | `unknown` | Commit hash of the program and Docker image |
## Development and contributing
- [Contribute with code](https://github.com/qdm12/ddns-updater/blob/master/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
This repository is under an [MIT license](https://github.com/qdm12/ddns-updater/master/license)
This repository is under an [MIT license](LICENSE)
## Used in external projects
@@ -340,7 +435,4 @@ This repository is under an [MIT license](https://github.com/qdm12/ddns-updater/
Sponsor me on [Github](https://github.com/sponsors/qdm12) or donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
[![https://github.com/sponsors/qdm12](https://raw.githubusercontent.com/qdm12/private-internet-access-docker/master/doc/sponsors.jpg)](https://github.com/sponsors/qdm12)
[![https://www.paypal.me/qmcgaw](https://raw.githubusercontent.com/qdm12/private-internet-access-docker/master/doc/paypal.jpg)](https://www.paypal.me/qmcgaw)
Many thanks to J. Famiglietti for supporting me financially 🥇👍

380
cmd/ddns-updater/main.go Normal file
View File

@@ -0,0 +1,380 @@
package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
_ "time/tzdata"
_ "github.com/breml/rootcerts"
"github.com/qdm12/ddns-updater/internal/backup"
"github.com/qdm12/ddns-updater/internal/config"
"github.com/qdm12/ddns-updater/internal/data"
"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/gosplash"
"github.com/qdm12/log"
)
//nolint:gochecknoglobals
var (
version = "unknown"
commit = "unknown"
date = "an unknown date"
)
func main() {
buildInfo := models.BuildInformation{
Version: version,
Commit: commit,
Created: date,
}
logger := log.New()
reader := reader.New(reader.Settings{
HandleDeprecatedKey: func(source, oldKey, newKey string) {
logger.Warnf("%q key %s is deprecated, please use %q instead",
source, oldKey, newKey)
},
})
ctx := context.Background()
ctx, stop := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
ctx, cancel := context.WithCancel(ctx)
errorCh := make(chan error)
go func() {
errorCh <- _main(ctx, reader, os.Args, logger, buildInfo, time.Now)
}()
select {
case <-ctx.Done():
stop()
logger.Warn("Caught OS signal, shutting down")
case err := <-errorCh:
stop()
close(errorCh)
if err == nil { // expected exit such as healthcheck
os.Exit(0)
}
logger.Error(err.Error())
cancel()
}
const shutdownGracePeriod = 5 * time.Second
timer := time.NewTimer(shutdownGracePeriod)
select {
case err := <-errorCh:
if !timer.Stop() {
<-timer.C
}
if err != nil {
logger.Error(err.Error())
}
logger.Info("Shutdown successful")
case <-timer.C:
logger.Warn("Shutdown timed out")
}
os.Exit(1)
}
func _main(ctx context.Context, reader *reader.Reader, args []string, logger log.LoggerInterface,
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)
}
client := health.NewClient()
return client.Query(ctx, *healthSettings.ServerAddress)
}
}
printSplash(buildInfo)
config, err := readConfig(reader, logger)
if err != nil {
return err
}
if *config.Paths.Umask > 0 {
system.SetUmask(*config.Paths.Umask)
}
shoutrrrSettings := shoutrrr.Settings{
Addresses: config.Shoutrrr.Addresses,
DefaultTitle: config.Shoutrrr.DefaultTitle,
Logger: logger.New(log.SetComponent("shoutrrr")),
}
shoutrrrClient, err := shoutrrr.New(shoutrrrSettings)
if err != nil {
return fmt.Errorf("setting up Shoutrrr: %w", err)
}
persistentDB, err := persistence.NewDatabase(*config.Paths.DataDir)
if err != nil {
shoutrrrClient.Notify(err.Error())
return err
}
jsonReader := jsonparams.NewReader(logger)
providers, warnings, err := jsonReader.JSONProviders(*config.Paths.Config)
for _, w := range warnings {
logger.Warn(w)
shoutrrrClient.Notify(w)
}
if err != nil {
shoutrrrClient.Notify(err.Error())
return err
}
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, err := readRecords(providers, persistentDB, logger, shoutrrrClient)
if err != nil {
return fmt.Errorf("reading records: %w", err)
}
db := data.NewDatabase(records, persistentDB)
httpSettings := publicip.HTTPSettings{
Enabled: *config.PubIP.HTTPEnabled,
Client: client,
Options: config.PubIP.ToHTTPOptions(),
}
dnsSettings := publicip.DNSSettings{
Enabled: *config.PubIP.DNSEnabled,
Options: config.PubIP.ToDNSPOptions(),
}
ipGetter, err := publicip.NewFetcher(dnsSettings, httpSettings)
if err != nil {
return err
}
resolverSettings := resolver.Settings{
Address: config.Resolver.Address,
Timeout: config.Resolver.Timeout,
}
resolver, err := resolver.New(resolverSettings)
if err != nil {
return fmt.Errorf("creating resolver: %w", err)
}
hioClient := healthchecksio.New(client, config.Health.HealthchecksioBaseURL,
*config.Health.HealthchecksioUUID)
updater := update.NewUpdater(db, client, shoutrrrClient, logger, timeNow)
updaterService := update.NewService(db, updater, ipGetter, config.Update.Period,
config.Update.Cooldown, logger, resolver, timeNow, hioClient)
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 updaterService.ForceUpdate(ctx)
shoutrrrClient.Notify("Launched with " + strconv.Itoa(len(records)) + " records to watch")
select {
case <-ctx.Done():
case err = <-runError:
exitHealthchecksio(hioClient, logger, healthchecksio.Exit1)
shoutrrrClient.Notify(err.Error())
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
}
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 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)
}
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

@@ -1,311 +0,0 @@
package main
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
_ "time/tzdata"
_ "github.com/breml/rootcerts"
"github.com/containrrr/shoutrrr"
"github.com/qdm12/ddns-updater/internal/backup"
"github.com/qdm12/ddns-updater/internal/config"
"github.com/qdm12/ddns-updater/internal/data"
"github.com/qdm12/ddns-updater/internal/health"
"github.com/qdm12/ddns-updater/internal/models"
jsonparams "github.com/qdm12/ddns-updater/internal/params"
persistence "github.com/qdm12/ddns-updater/internal/persistence/json"
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/update"
"github.com/qdm12/ddns-updater/pkg/publicip"
"github.com/qdm12/golibs/connectivity"
"github.com/qdm12/golibs/params"
"github.com/qdm12/goshutdown"
"github.com/qdm12/gosplash"
"github.com/qdm12/log"
)
//nolint:gochecknoglobals
var (
version = "unknown"
commit = "unknown"
buildDate = "an unknown date"
)
func main() {
buildInfo := models.BuildInformation{
Version: version,
Commit: commit,
BuildDate: buildDate,
}
env := params.New()
logger := log.New()
ctx := context.Background()
ctx, stop := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
ctx, cancel := context.WithCancel(ctx)
errorCh := make(chan error)
go func() {
errorCh <- _main(ctx, env, os.Args, logger, buildInfo, time.Now)
}()
select {
case <-ctx.Done():
stop()
logger.Warn("Caught OS signal, shutting down")
case err := <-errorCh:
stop()
close(errorCh)
if err == nil { // expected exit such as healthcheck
os.Exit(0)
}
logger.Error(err.Error())
cancel()
}
const shutdownGracePeriod = 5 * time.Second
timer := time.NewTimer(shutdownGracePeriod)
select {
case err := <-errorCh:
if !timer.Stop() {
<-timer.C
}
if err != nil {
logger.Error(err.Error())
}
logger.Info("Shutdown successful")
case <-timer.C:
logger.Warn("Shutdown timed out")
}
os.Exit(1)
}
var (
errShoutrrrSetup = errors.New("failed setting up Shoutrrr")
)
func _main(ctx context.Context, env params.Interface, 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
client := health.NewClient()
var healthConfig config.Health
_, err := healthConfig.Get(env)
if err != nil {
return err
}
return client.Query(ctx, healthConfig.Port)
}
announcementExp, err := time.Parse(time.RFC3339, "2021-07-22T00:00:00Z")
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.BuildDate,
Announcement: "",
AnnounceExp: announcementExp,
// Sponsor information
PaypalUser: "qmcgaw",
GithubSponsor: "qdm12",
}
for _, line := range gosplash.MakeLines(splashSettings) {
fmt.Println(line)
}
var config config.Config
warnings, err := config.Get(env)
for _, warning := range warnings {
logger.Warn(warning)
}
if err != nil {
return err
}
// Setup logger
options := []log.Option{log.SetLevel(config.Logger.Level)}
if config.Logger.Caller {
options = append(options, log.SetCallerFile(true), log.SetCallerLine(true))
}
logger.Patch(options...)
sender, err := shoutrrr.CreateSender(config.Shoutrrr.Addresses...)
if err != nil {
return fmt.Errorf("%w: %w", errShoutrrrSetup, err)
}
notify := func(message string) {
errs := sender.Send(message, &config.Shoutrrr.Params)
for i, err := range errs {
if err != nil {
destination := strings.Split(config.Shoutrrr.Addresses[i], ":")[0]
logger.Error(destination + ": " + err.Error())
}
}
}
persistentDB, err := persistence.NewDatabase(config.Paths.DataDir)
if err != nil {
notify(err.Error())
return err
}
jsonReader := jsonparams.NewReader(logger)
settings, warnings, err := jsonReader.JSONSettings(config.Paths.JSON)
for _, w := range warnings {
logger.Warn(w)
notify(w)
}
if err != nil {
notify(err.Error())
return err
}
L := len(settings)
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(settings)) + " settings to update records")
}
client := &http.Client{Timeout: config.Client.Timeout}
connectivity := connectivity.NewHTTPSGetChecker(client, http.StatusOK)
err = connectivity.Check(ctx, "https://github.com")
if err != nil {
logger.Warn(err.Error())
}
records := make([]recordslib.Record, len(settings))
for i, s := range settings {
logger.Info("Reading history from database: domain " +
s.Domain() + " host " + s.Host())
events, err := persistentDB.GetEvents(s.Domain(), s.Host())
if err != nil {
notify(err.Error())
return err
}
records[i] = recordslib.New(s, events)
}
defer client.CloseIdleConnections()
db := data.NewDatabase(records, persistentDB)
defer func() {
err := db.Close()
if err != nil {
logger.Error(err.Error())
}
}()
config.PubIP.HTTPSettings.Client = client
ipGetter, err := publicip.NewFetcher(config.PubIP.DNSSettings, config.PubIP.HTTPSettings)
if err != nil {
return err
}
resolver, err := resolver.New(config.Resolver)
if err != nil {
return fmt.Errorf("creating resolver: %w", err)
}
updater := update.NewUpdater(db, client, notify, logger)
runner := update.NewRunner(db, updater, ipGetter, config.Update.Period,
config.IPv6.Mask, config.Update.Cooldown, logger, resolver, timeNow)
runnerHandler, runnerCtx, runnerDone := goshutdown.NewGoRoutineHandler("runner")
go runner.Run(runnerCtx, runnerDone)
// note: errors are logged within the goroutine,
// no need to collect the resulting errors.
go runner.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)
address := ":" + strconv.Itoa(int(config.Server.Port))
serverLogger := logger.New(log.SetComponent("http server"))
server := server.New(ctx, address, config.Server.RootURL, db, serverLogger, runner)
serverHandler, serverCtx, serverDone := goshutdown.NewGoRoutineHandler("server")
go server.Run(serverCtx, serverDone)
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 {
notify(err.Error())
return err
}
return nil
}
type InfoErroer interface {
Info(s string)
Error(s string)
}
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
}
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
}
}
}

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:
@@ -21,7 +21,7 @@ services:
- HTTP_TIMEOUT=10s
# Web UI
- LISTENING_PORT=8000
- LISTENING_ADDRESS=:8000
- ROOT_URL=/
# Backup

View File

@@ -10,10 +10,10 @@
{
"provider": "aliyun",
"domain": "domain.com",
"host": "@",
"access_key_id": "your access_key_id",
"access_secret": "your access_secret",
"ip_version": "ipv4"
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
@@ -21,13 +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), defaults to `ipv4 or ipv6`
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup

View File

@@ -9,10 +9,11 @@
"settings": [
{
"provider": "allinkl",
"domain": "domain.com",
"host": "host",
"domain": "sub.domain.com",
"username": "dynXXXXXXX",
"password": "password"
"password": "password",
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
@@ -20,9 +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 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 public IPv6 address obtained is used in the record updating.
## 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 public IPv6 address obtained is used in the record updating.
## Domain setup

View File

@@ -11,10 +11,10 @@
"provider": "cloudflare",
"zone_identifier": "some id",
"domain": "domain.com",
"host": "@",
"ttl": 600,
"token": "yourtoken",
"ip_version": "ipv4"
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
@@ -23,18 +23,18 @@
### 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. It should be left to `"@"`, since subdomain and wildcards (`"*"`) are not really supported by Cloudflare it seems.
- `"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://support.cloudflare.com/hc/en-us/articles/200167836-Where-do-I-find-my-Cloudflare-API-key-)):
- Email `"email"` and Global API Key `"key"`
- User service key `"user_service_key"`
- API Token `"token"`, configured with DNS edit permissions for your DNS name's zone
- One of the following ([how to find API keys](https://developers.cloudflare.com/fundamentals/api/get-started/)):
- Email `"email"` and Global API Key `"key"`
- User service key `"user_service_key"`
- API Token `"token"`, configured with DNS edit permissions for your DNS name's zone
### Optional parameters
- `"proxied"` can be set to `true` to use the proxy services of Cloudflare
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), and defaults to `ipv4 or ipv6`
- `"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 public IPv6 address obtained is used in the record updating.
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). Working settings are already in [.vscode/settings.json](../.vscode/settings.json).
## 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.

40
docs/custom.md Normal file
View File

@@ -0,0 +1,40 @@
# Custom provider
The custom provider allows to configure a URL with a few additional parameters to update your records.
For now it sends an HTTP GET request to the URL given with some additional parameters.
Feel free to open issues to extend its configuration options.
## Configuration
### Example
```json
{
"settings": [
{
"provider": "custom",
"domain": "example.com",
"url": "https://example.com/update?domain=example.com&host=@&username=username&client_key=client_key",
"ipv4key": "ipv4",
"ipv6key": "ipv6",
"success_regex": "good",
"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.
- `"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`. 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 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 public IPv6 address obtained is used in the record updating.

View File

@@ -10,9 +10,9 @@
{
"provider": "dd24",
"domain": "domain.com",
"host": "@",
"password": "password",
"ip_version": "ipv4"
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
@@ -20,10 +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), defaults to `ipv4 or ipv6`
- `"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 public IPv6 address obtained is used in the record updating.

View File

@@ -9,13 +9,12 @@
"settings": [
{
"provider": "ddnss",
"provider_ip": true,
"domain": "domain.com",
"host": "@",
"username": "user",
"password": "password",
"dual_stack": false,
"ip_version": "ipv4"
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
@@ -23,16 +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), defaults to `ipv4 or ipv6`
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup

33
docs/desec.md Normal file
View File

@@ -0,0 +1,33 @@
# deSEC
## Configuration
### Example
```json
{
"settings": [
{
"provider": "desec",
"domain": "sub.dedyn.io",
"token": "token",
"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 token that you can create [here](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 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 public IPv6 address obtained is used in the record updating.
## Domain setup
[desec.io/domains](https://desec.io/domains)

View File

@@ -10,9 +10,9 @@
{
"provider": "digitalocean",
"domain": "domain.com",
"host": "@",
"token": "yourtoken",
"ip_version": "ipv4"
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
@@ -20,12 +20,12 @@
### 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"` is your token that you can create [here](https://cloud.digitalocean.com/settings/applications)
### Optional parameters
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6`
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup

View File

@@ -10,9 +10,10 @@
{
"provider": "dnsomatic",
"domain": "domain.com",
"host": "@",
"token": "yourtoken",
"ip_version": "ipv4"
"username": "username",
"password": "password",
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
@@ -20,13 +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"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6`
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup

View File

@@ -10,9 +10,9 @@
{
"provider": "dnspod",
"domain": "domain.com",
"host": "@",
"token": "yourtoken",
"ip_version": "ipv4"
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
@@ -20,12 +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), defaults to `ipv4 or ipv6`
- `"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 public IPv6 address obtained is used in the record updating.
## 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 public IPv6 address obtained is used in the record updating.

View File

@@ -10,10 +10,10 @@
{
"provider": "dondominio",
"domain": "domain.com",
"name": "something",
"username": "username",
"password": "password",
"ip_version": "ipv4"
"key": "key",
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
@@ -21,13 +21,15 @@
### Compulsory parameters
- `"domain"`
- `"name"` is the name server associated with the domain
- `"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), defaults to `ipv4 or ipv6`
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup
See [dondominio.dev/en/dondns/docs/api/#before-start](https://dondominio.dev/en/dondns/docs/api/#before-start)

View File

@@ -10,9 +10,9 @@
{
"provider": "dreamhost",
"domain": "domain.com",
"host": "@",
"key": "key",
"ip_version": "ipv4"
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
@@ -25,7 +25,8 @@
### Optional parameters
- `"host"` is your host and can be a subdomain or `"@"`. It defaults to `"@"`.
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6`
- `"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 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 public IPv6 address obtained is used in the record updating.
## Domain setup

View File

@@ -9,10 +9,10 @@
"settings": [
{
"provider": "duckdns",
"host": "host",
"domain": "sub.duckdns.org",
"token": "token",
"ip_version": "ipv4",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -20,16 +20,19 @@
### 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), defaults to `ipv4 or ipv6`
- `"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.
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup
[![DuckDNS Website](../readme/duckdns.png)](https://duckdns.org)
[![DuckDNS Website](../readme/duckdns.png)](https://www.duckdns.org/)
*See the [duckdns website](https://duckdns.org)*
*See the [duckdns website](https://www.duckdns.org/)*

View File

@@ -10,11 +10,10 @@
{
"provider": "dyn",
"domain": "domain.com",
"host": "@",
"username": "username",
"client_key": "client_key",
"ip_version": "ipv4",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -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), `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), defaults to `ipv4 or ipv6`
- `"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.
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup

View File

@@ -10,12 +10,11 @@
{
"provider": "dynu",
"domain": "domain.com",
"host": "@",
"group": "group",
"username": "username",
"password": "password",
"ip_version": "ipv4",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -23,15 +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), defaults to `ipv4 or ipv6`
- `"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.
- `"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 public IPv6 address obtained is used in the record updating.
- `"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,10 +10,9 @@
{
"provider": "dynv6",
"domain": "domain.com",
"host": "@",
"token": "token",
"ip_version": "ipv4",
"provider_ip": true
"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"` that you can obtain [here](https://dynv6.com/keys#token)
### Optional parameters
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6`
- `"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.
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup

33
docs/easydns.md Normal file
View File

@@ -0,0 +1,33 @@
# EasyDNS
## Configuration
### Example
```json
{
"settings": [
{
"provider": "easydns",
"domain": "domain.com",
"username": "username",
"token": "token",
"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"`
- `"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 public IPv6 address obtained is used in the record updating.
## 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 public IPv6 address obtained is used in the record updating.
<!-- UPDATE THIS IF NEEDED -->
## Domain setup
<!-- FILL THIS UP WITH A FEW NUMBERED STEPS -->

View File

@@ -9,10 +9,10 @@
"settings": [
{
"provider": "freedns",
"domain": "domain.com",
"host": "host",
"domain": "sub.domain.com",
"token": "token",
"ip_version": "ipv4"
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
@@ -20,14 +20,15 @@
### 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), defaults to `ipv4 or ipv6`
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup
This integration uses FreeDNS's v2 dynamic dns interface, which is not shown by default when you select `Dynamic DNS` from the side menu. Instead you must go to https://freedns.afraid.org/dynamic/v2/ and enable dynamic DNS for the subdomains you wish and you will then see a url like `https://sync.afraid.org/u/token/` for each enabled subdomain.
This integration uses FreeDNS's v2 dynamic dns interface, which is not shown by default when you select `Dynamic DNS` from the side menu.
Instead you must go to [freedns.afraid.org/dynamic/v2/](https://freedns.afraid.org/dynamic/v2/) and enable dynamic DNS for the subdomains you wish and you will then see a url like `https://sync.afraid.org/u/token/` for each enabled subdomain.

View File

@@ -12,10 +12,10 @@ This provider uses Gandi v5 API
{
"provider": "gandi",
"domain": "domain.com",
"host": "@",
"key": "key",
"personal_access_token": "token",
"ttl": 3600,
"ip_version": "ipv4"
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
@@ -23,15 +23,15 @@ This provider uses Gandi v5 API
### Compulsory parameters
- `"domain"`
- `"host"` which can be a subdomain, `@` or a wildcard `*`
- `"key"`
- `"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), defaults to `ipv4 or ipv6`
- `"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 public IPv6 address obtained is used in the record updating.
- `"ttl"` default is `3600`
## Domain setup
[Gandi Documentation Website](https://docs.gandi.net/en/domain_names/advanced_users/api.html#gandi-s-api)
[Gandi Documentation Website](https://docs.gandi.net/en/rest_api/index.html)

View File

@@ -8,7 +8,7 @@
{
"settings": [
{
"provider": "gpc",
"provider": "gcp",
"project": "my-project-id",
"zone": "zone",
"credentials": {
@@ -17,8 +17,8 @@
// ...
},
"domain": "domain.com",
"host": "@",
"ip_version": "ipv4"
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
@@ -29,9 +29,9 @@
- `"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 `"*"`
- `"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), defaults to `ipv4`
- `"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 public IPv6 address obtained is used in the record updating.

View File

@@ -10,10 +10,10 @@
{
"provider": "godaddy",
"domain": "domain.com",
"host": "@",
"key": "key",
"secret": "secret",
"ip_version": "ipv4"
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
@@ -21,18 +21,18 @@
### 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), defaults to `ipv4 or ipv6`
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup
[![GoDaddy Website](../readme/godaddy.png)](https://godaddy.com)
[![GoDaddy Website](../readme/godaddy.png)](https://www.godaddy.com/en-ie)
1. Login to [https://developer.godaddy.com/keys](https://developer.godaddy.com/keys/) with your account credentials.

36
docs/goip.md Normal file
View File

@@ -0,0 +1,36 @@
# GoIP.de
## Configuration
### Example
```json
{
"settings": [
{
"provider": "goip",
"domain": "sub.mydomain.goip.de",
"username": "username",
"password": "password",
"ip_version": "",
"ipv6_suffix": ""
}
]
}
```
### Compulsory parameters
- `"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
- `"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 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 public IPv6 address obtained is used in the record updating.

View File

@@ -1,42 +0,0 @@
# Google
## Configuration
### Example
```json
{
"settings": [
{
"provider": "google",
"domain": "domain.com",
"host": "@",
"username": "username",
"password": "password",
"ip_version": "ipv4"
}
]
}
```
### Compulsory parameters
- `"domain"`
- `"host"` is your host and can be a subdomain or `"@"` or `"*"`
- `"username"`
- `"password"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6`
## Domain setup
Thanks to [@gauravspatel](https://github.com/gauravspatel) for #124
1. Enable dynamic DNS in the *synthetic records* section of DNS management.
1. The username and password is generated once you create the dynamic DNS entry.
### Wildcard entries
If you want to create a **wildcard entry**, you have to create a custom **CNAME** record with key `"*"` and value `"@"`.

View File

@@ -10,9 +10,9 @@
{
"provider": "he",
"domain": "domain.com",
"host": "@",
"password": "password",
"ip_version": "ipv4"
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
@@ -20,12 +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), defaults to `ipv4 or ipv6`
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup

34
docs/hetzner.md Normal file
View File

@@ -0,0 +1,34 @@
# Hetzner
## Configuration
### Example
```json
{
"settings": [
{
"provider": "hetzner",
"zone_identifier": "some id",
"domain": "domain.com",
"ttl": 600,
"token": "yourtoken",
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
```
### Compulsory parameters
- `"zone_identifier"` is the Zone ID of your site, from the domain overview page written as *Zone ID*
- `"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
### 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 public IPv6 address obtained is used in the record updating.

View File

@@ -10,10 +10,10 @@
{
"provider": "infomaniak",
"domain": "domain.com",
"host": "@",
"username": "username",
"password": "password",
"ip_version": "ipv4",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -21,14 +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), defaults to `ipv4 or ipv6`
- `"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.
- `"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 public IPv6 address obtained is used in the record updating.
## 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

@@ -1,32 +1,35 @@
# OpenDNS
# INWX
## 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
{
"settings": [
{
"provider": "dyn",
"provider": "inwx",
"domain": "domain.com",
"host": "@",
"username": "username",
"password": "password",
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
```
### 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), defaults to `ipv4 or ipv6`
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup

29
docs/ionos.md Normal file
View File

@@ -0,0 +1,29 @@
# Ionos
## Configuration
### Example
```json
{
"settings": [
{
"provider": "ionos",
"domain": "domain.com",
"api_key": "api_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.
- `"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 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 public IPv6 address obtained is used in the record updating.

View File

@@ -10,9 +10,9 @@
{
"provider": "linode",
"domain": "domain.com",
"host": "@",
"token": "token",
"ip_version": "ipv4"
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
@@ -20,15 +20,15 @@
### 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), defaults to `ipv4 or ipv6`
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup
1. Create a personal access token with `domains` set, with read and write privileges, ideally that never expires. You can refer to [@AnujRNair's comment](https://github.com/qdm12/ddns-updater/pull/144#discussion_r559292678) and to [Linode's guide](https://www.linode.com/docs/products/tools/cloud-manager/guides/cloud-api-keys).
1. Create a personal access token with `domains` set, with read and write privileges, ideally that never expires. You can refer to [@AnujRNair's comment](https://github.com/qdm12/ddns-updater/pull/144#discussion_r559292678) and to [Linode's guide](https://www.linode.com/docs/products/tools/api/guides/manage-api-tokens/).
1. The program will create the A or AAAA record for you if it doesn't exist already.

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 public IPv6 address obtained is used in the record updating.

View File

@@ -10,10 +10,10 @@
{
"provider": "luadns",
"domain": "domain.com",
"host": "@",
"email": "email",
"token": "token",
"ip_version": "ipv4"
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
@@ -21,14 +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), defaults to `ipv4 or ipv6`
- `"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 public IPv6 address obtained is used in the record updating.
## 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 public IPv6 address obtained is used in the record updating.
## Domain setup
Claim a subdomain at [myaddr.tools](https://myaddr.tools/)

35
docs/name.com.md Normal file
View File

@@ -0,0 +1,35 @@
# Name.com
<img src="../readme/name.svg" alt="drawing" width="25%"/>
## Configuration
### Example
```json
{
"settings": [
{
"provider": "name.com",
"domain": "domain.com",
"username": "username",
"token": "token",
"ttl": 300,
"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"` is your account username
- `"token"` which you can obtain from [www.name.com/account/settings/api](https://www.name.com/account/settings/api)
### Optional parameters
- `"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 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 public IPv6 address obtained is used in the record updating.

View File

@@ -10,9 +10,7 @@
{
"provider": "namecheap",
"domain": "domain.com",
"host": "@",
"password": "password",
"provider_ip": true
"password": "password"
}
]
}
@@ -20,38 +18,35 @@
### 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
[![Namecheap Website](../readme/namecheap.png)](https://www.namecheap.com)
[![Namecheap Website](../readme/namecheap.png)](https://www.namecheap.com/)
1. Create a Namecheap account and buy a domain name - *example.com* as an example
1. Login to Namecheap at [https://www.namecheap.com/myaccount/login.aspx](https://www.namecheap.com/myaccount/login.aspx)
1. Login to Namecheap at [https://www.namecheap.com/myaccount/login/](https://www.namecheap.com/myaccount/login/)
For **each domain name** you want to add, replace *example.com* in the following link with your domain name and go to [https://ap.www.namecheap.com/Domains/DomainControlPanel/**example.com**/advancedns](https://ap.www.namecheap.com/Domains/DomainControlPanel/example.com/advancedns)
1. For each host you want to add (if you don't know, create one record with the host set to `*`):
1. In the *HOST RECORDS* section, click on *ADD NEW RECORD*
![https://ap.www.namecheap.com/Domains/DomainControlPanel/mealracle.com/advancedns](../readme/namecheap1.png)
![https://ap.www.namecheap.com/Domains/DomainControlPanel/example.com/advancedns](../readme/namecheap1.png)
1. Select the following settings and create the *A + Dynamic DNS Record*:
![https://ap.www.namecheap.com/Domains/DomainControlPanel/mealracle.com/advancedns](../readme/namecheap2.png)
![https://ap.www.namecheap.com/Domains/DomainControlPanel/example.com/advancedns](../readme/namecheap2.png)
1. Scroll down and turn on the switch for *DYNAMIC DNS*
![https://ap.www.namecheap.com/Domains/DomainControlPanel/mealracle.com/advancedns](../readme/namecheap3.png)
![https://ap.www.namecheap.com/Domains/DomainControlPanel/example.com/advancedns](../readme/namecheap3.png)
1. The Dynamic DNS Password will appear, which is `0e4512a9c45a4fe88313bcc2234bf547` in this example.
![https://ap.www.namecheap.com/Domains/DomainControlPanel/mealracle.com/advancedns](../readme/namecheap4.png)
![https://ap.www.namecheap.com/Domains/DomainControlPanel/example.com/advancedns](../readme/namecheap4.png)

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 public IPv6 address obtained is used in the record updating.
- `"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.

37
docs/netcup.md Normal file
View File

@@ -0,0 +1,37 @@
# Netcup
## Configuration
Note: This implementation does not require a domain reseller account. The warning in the dashboard can be ignored.
Also keep in mind, that TTL, Expire, Retry and Refresh values of the given Domain are not updated. They can be manually set in the dashboard. For DDNS purposes low numbers should be used.
### Example
```json
{
"settings": [
{
"provider": "netcup",
"domain": "domain.com",
"api_key": "xxxxx",
"password": "yyyyy",
"customer_number": "111111",
"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`) 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.
### 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 public IPv6 address obtained is used in the record updating.

View File

@@ -10,10 +10,9 @@
{
"provider": "njalla",
"domain": "domain.com",
"host": "@",
"key": "key",
"ip_version": "ipv4",
"provider_ip": true
"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.
- `"key"` is the key for your record
### Optional parameters
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6`
- `"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.
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup

View File

@@ -10,10 +10,10 @@
{
"provider": "noip",
"domain": "domain.com",
"host": "@",
"username": "username",
"password": "password",
"ip_version": "ipv4",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -21,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`).
- `"username"`
- `"password"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6`
- `"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.
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup

31
docs/nowdns.md Normal file
View File

@@ -0,0 +1,31 @@
# Now-DNS
## Configuration
### Example
```json
{
"settings": [
{
"provider": "nowdns",
"domain": "domain.com",
"username": "username",
"password": "password",
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
```
### Compulsory parameters
- `"domain"` your full domain name (FQDN)
- `"username"` your email address
- `"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 public IPv6 address obtained is used in the record updating.

View File

@@ -8,13 +8,12 @@
{
"settings": [
{
"provider": "dyn",
"provider": "opendns",
"domain": "domain.com",
"host": "@",
"username": "username",
"password": "password",
"ip_version": "ipv4",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -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`).
- `"username"`
- `"password"`
### Optional parameters
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6`
- `"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.
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup

View File

@@ -10,11 +10,10 @@
{
"provider": "ovh",
"domain": "domain.com",
"host": "@",
"username": "username",
"password": "password",
"ip_version": "ipv4",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -22,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
@@ -35,14 +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), defaults to `ipv4 or ipv6`
- `"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.
- `"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 public IPv6 address obtained is used in the record updating.
- `"mode"` select between two modes, OVH's dynamic hosting service (`"dynamic"`) or OVH's API (`"api"`). Default is `"dynamic"`
## Domain setup

View File

@@ -10,26 +10,43 @@
{
"provider": "porkbun",
"domain": "domain.com",
"host": "@",
"api_key": "sk1_7d119e3f656b00ae042980302e1425a04163c476efec1833q3cb0w54fc6f5022",
"secret_api_key": "pk1_5299b57125c8f3cdf347d2fe0e713311ee3a1e11f11a14942b26472593e35368",
"ip_version": "ipv4"
"api_key": "pk1_5299b57125c8f3cdf347d2fe0e713311ee3a1e11f11a14942b26472593e35368",
"secret_api_key": "sk1_7d119e3f656b00ae042980302e1425a04163c476efec1833q3cb0w54fc6f5022",
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
```
### Parameters
### 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 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 public IPv6 address obtained is used in the record updating.
## Domain setup
- Create an API key at [porkbun.com/account/api](https://porkbun.com/account/api)
- From the [Domain Management page](https://porkbun.com/account/domainsSpeedy), toggle on **API ACCESS** for your domain.
💁 [Official setup documentation](https://kb.porkbun.com/article/190-getting-started-with-the-porkbun-dns-api)
## 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.
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"
}
]
}
```

View File

@@ -10,10 +10,10 @@
{
"provider": "selfhost.de",
"domain": "domain.com",
"host": "@",
"username": "username",
"password": "password",
"ip_version": "ipv4"
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
@@ -21,13 +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), defaults to `ipv4 or ipv6`
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup

View File

@@ -10,28 +10,28 @@
{
"provider": "servercow",
"domain": "domain.com",
"host": "",
"username": "servercow_username",
"password": "servercow_password",
"ttl": 600,
"ip_version": "ipv4"
"ip_version": "ipv4",
"ipv6_suffix": ""
}
]
}
```
### Compulsury parameters
### 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
### Optional parameters
- `"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), and defaults to `ipv4 or ipv6`
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup
See [their article](https://cp.servercow.de/en/plugin/support_manager/knowledgebase/view/34/dns-api-v1/7/)
See [their article](https://cp.servercow.de/en/plugin/support_manager/knowledgebase/view/34/dns-api-v1/7/)

View File

@@ -10,12 +10,11 @@
{
"provider": "spdyn",
"domain": "domain.com",
"host": "@",
"user": "user",
"password": "password",
"token": "token",
"ip_version": "ipv4",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -23,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
@@ -37,5 +35,5 @@
### Optional parameters
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6`
- `"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.
- `"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 public IPv6 address obtained is used in the record updating.

View File

@@ -10,10 +10,9 @@
{
"provider": "strato",
"domain": "domain.com",
"host": "@",
"password": "password",
"ip_version": "ipv4",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -21,14 +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), defaults to `ipv4 or ipv6`
- `"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.
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup

View File

@@ -10,11 +10,10 @@
{
"provider": "variomedia",
"domain": "domain.com",
"host": "@",
"email": "email@domain.com",
"password": "password",
"ip_version": "ipv4",
"provider_ip": true
"ipv6_suffix": ""
}
]
}
@@ -22,15 +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), defaults to `ipv4 or ipv6`
- `"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.
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup

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 public IPv6 address obtained is used in the record updating.
- `"ttl"` is the record TTL which defaults to 900 seconds.

41
docs/zoneedit.md Normal file
View File

@@ -0,0 +1,41 @@
# Zoneedit
## Configuration
⚠️ zoneedit.com for some reason requires at least a 10 minutes period between update request sent.
DDNS-Updater only sends update requests when it detects your domain name IP address mismatches your current public IP address,
so it should be fine in most cases since this happens rarely (in hours/days). But in case it happens and you want to avoid this,
set the environment variable as `PERIOD=11m` to check your public IP address and update every 11 minutes only.
### Example
```json
{
"settings": [
{
"provider": "zoneedit",
"domain": "domain.com",
"username": "username",
"token": "token",
"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"`
- `"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 public IPv6 address obtained is used in the record updating.
## Domain setup
[support.zoneedit.com/en/knowledgebase/article/dynamic-dns](https://support.zoneedit.com/en/knowledgebase/article/dynamic-dns)

54
go.mod
View File

@@ -1,43 +1,39 @@
module github.com/qdm12/ddns-updater
go 1.20
go 1.25.0
require (
github.com/breml/rootcerts v0.2.11
github.com/containrrr/shoutrrr v0.7.0
github.com/go-chi/chi v1.5.4
github.com/breml/rootcerts v0.2.19
github.com/containrrr/shoutrrr v0.8.0
github.com/go-chi/chi/v5 v5.2.1
github.com/golang/mock v1.6.0
github.com/miekg/dns v1.1.42
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
github.com/qdm12/goshutdown v0.3.0
github.com/qdm12/gosplash v0.1.0
github.com/miekg/dns v1.1.70
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.1
google.golang.org/api v0.102.0
github.com/stretchr/testify v1.10.0
golang.org/x/mod v0.31.0
golang.org/x/net v0.49.0
golang.org/x/oauth2 v0.28.0
)
require (
cloud.google.com/go/compute v1.12.1 // indirect
cloud.google.com/go/compute/metadata v0.2.1 // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
github.com/googleapis/gax-go/v2 v2.6.0 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/net v0.1.0 // indirect
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
golang.org/x/sys v0.1.0 // indirect
golang.org/x/text v0.4.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect
google.golang.org/grpc v1.50.1 // indirect
google.golang.org/protobuf v1.28.1 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/tools v0.40.0 // indirect
google.golang.org/protobuf v1.34.1 // 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
)

1200
go.sum

File diff suppressed because it is too large Load Diff

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

@@ -1,27 +1,47 @@
package config
import (
"fmt"
"time"
"github.com/qdm12/golibs/params"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree"
)
type Backup struct {
Period time.Duration
Directory string
Period *time.Duration
Directory *string
}
func (b *Backup) get(env params.Interface) (err error) {
b.Period, err = env.Duration("BACKUP_PERIOD", params.Default("0"))
if err != nil {
return fmt.Errorf("%w: for environment variable BACKUP_PERIOD", err)
}
b.Directory, err = env.Path("BACKUP_DIRECTORY", params.Default("./data"))
if err != nil {
return fmt.Errorf("%w: for environment variable BACKUP_DIRECTORY", err)
}
func (b *Backup) setDefaults() {
b.Period = gosettings.DefaultPointer(b.Period, 0)
b.Directory = gosettings.DefaultPointer(b.Directory, "./data")
}
func (b Backup) Validate() (err error) {
return nil
}
func (b Backup) String() string {
return b.toLinesNode().String()
}
func (b Backup) toLinesNode() *gotree.Node {
if *b.Period == 0 {
return gotree.New("Backup: disabled")
}
node := gotree.New("Backup")
node.Appendf("Period: %s", b.Period)
node.Appendf("Directory: %s", *b.Directory)
return node
}
func (b *Backup) read(reader *reader.Reader) (err error) {
b.Period, err = reader.DurationPtr("BACKUP_PERIOD")
if err != nil {
return err
}
b.Directory = reader.Get("BACKUP_DIRECTORY")
return nil
}

View File

@@ -1,20 +1,40 @@
package config
import (
"fmt"
"time"
"github.com/qdm12/golibs/params"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree"
)
type Client struct {
Timeout time.Duration
}
func (c *Client) get(env params.Interface) (err error) {
c.Timeout, err = env.Duration("HTTP_TIMEOUT", params.Default("10s"))
func (c *Client) setDefaults() {
const defaultTimeout = 20 * time.Second
c.Timeout = gosettings.DefaultComparable(c.Timeout, defaultTimeout)
}
func (c Client) Validate() (err error) {
return nil
}
func (c Client) String() string {
return c.toLinesNode().String()
}
func (c Client) toLinesNode() *gotree.Node {
node := gotree.New("HTTP client")
node.Appendf("Timeout: %s", c.Timeout)
return node
}
func (c *Client) read(reader *reader.Reader) (err error) {
c.Timeout, err = reader.Duration("HTTP_TIMEOUT")
if err != nil {
return fmt.Errorf("%w: for environment variable HTTP_TIMEOUT", err)
return err
}
return nil

View File

@@ -1,86 +0,0 @@
package config
import (
"fmt"
"github.com/qdm12/ddns-updater/internal/resolver"
"github.com/qdm12/golibs/params"
)
type Config struct {
Client Client
Update Update
PubIP PubIP
Resolver resolver.Settings
IPv6 IPv6
Server Server
Health Health
Paths Paths
Backup Backup
Logger Logger
Shoutrrr Shoutrrr
}
func (c *Config) Get(env params.Interface) (warnings []string, err error) {
err = c.Client.get(env)
if err != nil {
return warnings, err
}
warning, err := c.Update.get(env)
warnings = appendIfNotEmpty(warnings, warning)
if err != nil {
return warnings, err
}
newWarnings, err := c.PubIP.get(env)
warnings = append(warnings, newWarnings...)
if err != nil {
return warnings, err
}
c.Resolver, err = readResolver()
if err != nil {
return warnings, fmt.Errorf("reading resolver settings: %w", err)
}
err = c.IPv6.get(env)
if err != nil {
return warnings, err
}
warning, err = c.Server.get(env)
warnings = appendIfNotEmpty(warnings, warning)
if err != nil {
return warnings, err
}
warning, err = c.Health.Get(env)
warnings = appendIfNotEmpty(warnings, warning)
if err != nil {
return warnings, err
}
err = c.Paths.get(env)
if err != nil {
return warnings, err
}
err = c.Backup.get(env)
if err != nil {
return warnings, err
}
c.Logger, err = readLog()
if err != nil {
return warnings, err
}
newWarnings, err = c.Shoutrrr.get(env)
warnings = append(warnings, newWarnings...)
if err != nil {
return warnings, err
}
return warnings, nil
}

View File

@@ -2,31 +2,66 @@ package config
import (
"fmt"
"net"
"strconv"
"net/url"
"os"
"github.com/qdm12/golibs/params"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree"
)
type Health struct {
ServerAddress string
Port uint16 // obtained from ServerAddress
// 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) Get(env params.Interface) (warning string, err error) {
h.ServerAddress, warning, err = env.ListeningAddress(
"HEALTH_SERVER_ADDRESS", params.Default("127.0.0.1:9999"))
if err != nil {
return warning, fmt.Errorf("%w: for environment variable HEALTH_SERVER_ADDRESS", err)
}
_, portStr, err := net.SplitHostPort(h.ServerAddress)
if err != nil {
return warning, fmt.Errorf("%w: for environment variable HEALTH_SERVER_ADDRESS", err)
}
port, err := strconv.Atoi(portStr)
if err != nil {
return warning, fmt.Errorf("%w: for environment variable HEALTH_SERVER_ADDRESS", err)
}
h.Port = uint16(port)
return warning, nil
func (h *Health) SetDefaults() {
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) {
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("healthchecks.io base URL: %w", err)
}
return nil
}
func (h Health) String() string {
return h.toLinesNode().String()
}
func (h Health) toLinesNode() *gotree.Node {
node := gotree.New("Health")
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
}
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

@@ -0,0 +1,3 @@
package config
const all = "all"

View File

@@ -1,60 +0,0 @@
package config
import (
"errors"
"fmt"
"net"
"strings"
"github.com/qdm12/golibs/params"
)
type IPv6 struct {
Mask net.IPMask
}
func (i *IPv6) get(env params.Interface) (err error) {
maskStr, err := env.Get("IPV6_PREFIX", params.Default("/128"))
if err != nil {
return fmt.Errorf("%w: for environment variable IPV6_PREFIX", err)
}
i.Mask, err = ipv6DecimalPrefixToMask(maskStr)
if err != nil {
return fmt.Errorf("%w: for environment variable IPV6_PREFIX", err)
}
return nil
}
var ErrParsePrefix = errors.New("cannot parse IP prefix")
func ipv6DecimalPrefixToMask(prefixDecimal string) (ipMask net.IPMask, err error) {
if prefixDecimal == "" {
return nil, fmt.Errorf("%w: empty prefix", ErrParsePrefix)
}
prefixDecimal = strings.TrimPrefix(prefixDecimal, "/")
const bits = 8 * net.IPv6len
ones, consumed, ok := decimalToInteger(prefixDecimal)
if !ok || consumed != len(prefixDecimal) || ones < 0 || ones > bits {
return nil, fmt.Errorf("%w: %s", ErrParsePrefix, prefixDecimal)
}
return net.CIDRMask(ones, bits), nil
}
func decimalToInteger(s string) (ones int, i int, ok bool) {
const big = 0xFFFFFF // Bigger than we need, not too big to worry about overflow
const ten = 10
for i = 0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
ones = ones*ten + int(s[i]-'0')
if ones >= big {
return big, i, false
}
}
return ones, i, true
}

View File

@@ -1,62 +0,0 @@
package config
import (
"fmt"
"net"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_ipv6DecimalPrefixToMask(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
prefixDecimal string
ipMask net.IPMask
err error
}{
"empty": {
err: fmt.Errorf("cannot parse IP prefix: empty prefix"),
},
"malformed": {
prefixDecimal: "malformed",
err: fmt.Errorf("cannot parse IP prefix: malformed"),
},
"with leading slash": {
prefixDecimal: "/78",
ipMask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 255, 252, 0, 0, 0, 0, 0, 0},
},
"without leading slash": {
prefixDecimal: "78",
ipMask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 255, 252, 0, 0, 0, 0, 0, 0},
},
"full IPv6 mask": {
prefixDecimal: "/128",
ipMask: net.IPMask{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
},
"zero IPv6 mask": {
prefixDecimal: "/0",
ipMask: net.IPMask{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ipMask, err := ipv6DecimalPrefixToMask(testCase.prefixDecimal)
if testCase.err != nil {
require.Error(t, err)
assert.Equal(t, testCase.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, testCase.ipMask, ipMask)
})
}
}

View File

@@ -1,73 +1,65 @@
package config
import (
"errors"
"fmt"
"os"
"strings"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree"
"github.com/qdm12/log"
)
type Logger struct {
Caller bool
Level log.Level
Level string
Caller string
}
var (
ErrLogCallerNotValid = errors.New("LOG_CALLER value is not valid")
)
func (l *Logger) setDefaults() {
l.Level = gosettings.DefaultComparable(l.Level, log.LevelInfo.String())
l.Caller = gosettings.DefaultComparable(l.Caller, "hidden")
}
func readLog() (settings Logger, err error) {
callerString := os.Getenv("LOG_CALLER")
switch callerString {
case "":
case "hidden":
case "short":
settings.Caller = true
default:
return settings, fmt.Errorf("%w: "+
`%q must be one of "", "hidden" or "short"`,
ErrLogCallerNotValid, callerString)
}
settings.Level, err = readLogLevel()
func (l Logger) Validate() (err error) {
_, err = log.ParseLevel(l.Level)
if err != nil {
return settings, err
return fmt.Errorf("log level: %w", err)
}
return settings, nil
}
func readLogLevel() (level log.Level, err error) {
s := os.Getenv("LOG_LEVEL")
if s == "" {
return log.LevelInfo, nil
}
level, err = parseLogLevel(s)
err = validate.IsOneOf(l.Caller, "hidden", "short")
if err != nil {
return level, fmt.Errorf("environment variable LOG_LEVEL: %w", err)
return fmt.Errorf("log caller: %w", err)
}
return level, nil
return nil
}
var ErrLogLevelUnknown = errors.New("log level is unknown")
func parseLogLevel(s string) (level log.Level, err error) {
switch strings.ToLower(s) {
case "debug":
return log.LevelDebug, nil
case "info":
return log.LevelInfo, nil
case "warning":
return log.LevelWarn, nil
case "error":
return log.LevelError, nil
default:
return level, fmt.Errorf(
"%w: %q is not valid and can be one of debug, info, warning or error",
ErrLogLevelUnknown, s)
func (l Logger) ToOptions() (options []log.Option) {
level, _ := log.ParseLevel(l.Level)
options = append(options, log.SetLevel(level))
if l.Caller == "short" {
options = append(options, log.SetCallerFile(true), log.SetCallerLine(true))
}
return options
}
func (l Logger) String() string {
return l.toLinesNode().String()
}
func (l Logger) toLinesNode() *gotree.Node {
node := gotree.New("Logger")
node.Appendf("Level: %s", l.Level)
node.Appendf("Caller: %s", l.Caller)
return node
}
func (l *Logger) read(reader *reader.Reader) {
l.Level = reader.String("LOG_LEVEL")
// Retro compatibility
if strings.ToLower(l.Level) == "warning" {
l.Level = "warn"
}
l.Caller = reader.String("LOG_CALLER")
}

View File

@@ -2,22 +2,72 @@ package config
import (
"fmt"
"io/fs"
"path/filepath"
"strconv"
"github.com/qdm12/golibs/params"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree"
)
type Paths struct {
DataDir string
JSON string // obtained from DataDir
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) get(env params.Interface) (err error) {
p.DataDir, err = env.Path("DATADIR", params.Default("./data"))
if err != nil {
return fmt.Errorf("%w: for environment variable DATADIR", err)
}
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))
}
p.JSON = filepath.Join(p.DataDir, "config.json")
func (p Paths) Validate() (err error) {
return nil
}
func (p Paths) String() string {
return p.toLinesNode().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) (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

@@ -5,193 +5,335 @@ import (
"fmt"
"net/url"
"strings"
"time"
"github.com/qdm12/ddns-updater/pkg/publicip"
"github.com/qdm12/ddns-updater/pkg/publicip/dns"
"github.com/qdm12/ddns-updater/pkg/publicip/http"
"github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
"github.com/qdm12/golibs/params"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree"
)
const all = "all"
type PubIP struct {
HTTPSettings publicip.HTTPSettings
DNSSettings publicip.DNSSettings
HTTPEnabled *bool
HTTPIPProviders []string
HTTPIPv4Providers []string
HTTPIPv6Providers []string
DNSEnabled *bool
DNSProviders []string
DNSTimeout time.Duration
}
func (p *PubIP) get(env params.Interface) (warnings []string, err error) {
err = p.getFetchers(env)
func (p *PubIP) setDefaults() {
p.HTTPEnabled = gosettings.DefaultPointer(p.HTTPEnabled, true)
p.HTTPIPProviders = gosettings.DefaultSlice(p.HTTPIPProviders, []string{all})
p.HTTPIPv4Providers = gosettings.DefaultSlice(p.HTTPIPv4Providers, []string{all})
p.HTTPIPv6Providers = gosettings.DefaultSlice(p.HTTPIPv6Providers, []string{all})
p.DNSEnabled = gosettings.DefaultPointer(p.DNSEnabled, true)
p.DNSProviders = gosettings.DefaultSlice(p.DNSProviders, []string{all})
const defaultDNSTimeout = 3 * time.Second
p.DNSTimeout = gosettings.DefaultComparable(p.DNSTimeout, defaultDNSTimeout)
}
func (p PubIP) Validate() (err error) {
err = p.validateHTTPIPProviders()
if err != nil {
return nil, err
return fmt.Errorf("HTTP IP providers: %w", err)
}
httpIPProviders, warning, err := p.getIPHTTPProviders(env)
warnings = appendIfNotEmpty(warnings, warning)
err = p.validateHTTPIPv4Providers()
if err != nil {
return warnings, err
return fmt.Errorf("HTTP IPv4 providers: %w", err)
}
httpIP4Providers, warning, err := p.getIPv4HTTPProviders(env)
warnings = appendIfNotEmpty(warnings, warning)
err = p.validateHTTPIPv6Providers()
if err != nil {
return warnings, err
return fmt.Errorf("HTTP IPv6 providers: %w", err)
}
httpIP6Providers, warning, err := p.getIPv6HTTPProviders(env)
warnings = appendIfNotEmpty(warnings, warning)
err = p.validateDNSProviders()
if err != nil {
return warnings, err
return fmt.Errorf("DNS providers: %w", err)
}
p.HTTPSettings.Options = []http.Option{
return nil
}
func (p *PubIP) String() string {
return p.toLinesNode().String()
}
func (p *PubIP) toLinesNode() (node *gotree.Node) {
node = gotree.New("Public IP fetching")
node.Appendf("HTTP enabled: %s", gosettings.BoolToYesNo(p.HTTPEnabled))
if *p.HTTPEnabled {
childNode := node.Append("HTTP IP providers")
for _, provider := range p.HTTPIPProviders {
childNode.Append(provider)
}
childNode = node.Append("HTTP IPv4 providers")
for _, provider := range p.HTTPIPv4Providers {
childNode.Append(provider)
}
childNode = node.Append("HTTP IPv6 providers")
for _, provider := range p.HTTPIPv6Providers {
childNode.Append(provider)
}
}
node.Appendf("DNS enabled: %s", gosettings.BoolToYesNo(p.DNSEnabled))
if *p.DNSEnabled {
node.Appendf("DNS timeout: %s", p.DNSTimeout)
childNode := node.Append("DNS over TLS providers")
for _, provider := range p.DNSProviders {
childNode.Append(provider)
}
}
return node
}
// ToHTTPOptions assumes the settings have been validated.
func (p *PubIP) ToHTTPOptions() (options []http.Option) {
httpIPProviders := stringsToHTTPProviders(p.HTTPIPProviders, ipversion.IP4or6)
httpIPv4Providers := stringsToHTTPProviders(p.HTTPIPv4Providers, ipversion.IP4)
httpIPv6Providers := stringsToHTTPProviders(p.HTTPIPv6Providers, ipversion.IP6)
return []http.Option{
http.SetProvidersIP(httpIPProviders[0], httpIPProviders[1:]...),
http.SetProvidersIP4(httpIP4Providers[0], httpIP4Providers[1:]...),
http.SetProvidersIP6(httpIP6Providers[0], httpIP6Providers[1:]...),
http.SetProvidersIP4(httpIPv4Providers[0], httpIPv4Providers[1:]...),
http.SetProvidersIP6(httpIPv6Providers[0], httpIPv6Providers[1:]...),
}
dnsIPProviders, err := p.getDNSProviders(env)
if err != nil {
return warnings, err
}
dnsTimeout, err := env.Duration("PUBLICIP_DNS_TIMEOUT", params.Default("3s"))
if err != nil {
return warnings, err
}
p.DNSSettings.Options = []dns.Option{
dns.SetTimeout(dnsTimeout),
dns.SetProviders(dnsIPProviders[0], dnsIPProviders[1:]...),
}
return warnings, nil
}
var ErrInvalidFetcher = errors.New("invalid fetcher specified")
func (p *PubIP) getFetchers(env params.Interface) (err error) {
s, err := env.Get("PUBLICIP_FETCHERS", params.Default(all))
if err != nil {
return fmt.Errorf("%w: for environment variable PUBLICIP_FETCHERS", err)
}
fields := strings.Split(s, ",")
for i, field := range fields {
switch strings.ToLower(field) {
case all:
p.HTTPSettings.Enabled = true
p.DNSSettings.Enabled = true
case "http":
p.HTTPSettings.Enabled = true
case "dns":
p.DNSSettings.Enabled = true
default:
err = fmt.Errorf(
"%w: %q at position %d of %d",
ErrInvalidFetcher, field, i+1, len(fields))
}
}
return err
}
// getDNSProviders obtains the DNS providers to obtain your public IPv4 and/or IPv6 address.
func (p *PubIP) getDNSProviders(env params.Interface) (providers []dns.Provider, err error) {
s, err := env.Get("PUBLICIP_DNS_PROVIDERS", params.Default(all))
if err != nil {
return nil, fmt.Errorf("%w: for environment variable PUBLICIP_DNS_PROVIDERS", err)
}
availableProviders := dns.ListProviders()
fields := strings.Split(s, ",")
providers = make([]dns.Provider, len(fields))
for i, field := range fields {
if field == all {
return availableProviders, nil
}
providers[i] = dns.Provider(field)
err = dns.ValidateProvider(providers[i])
if err != nil {
return nil, err
}
}
return providers, nil
}
// getHTTPProviders obtains the HTTP providers to obtain your public IPv4 or IPv6 address.
func (p *PubIP) getIPHTTPProviders(env params.Interface) (
providers []http.Provider, warning string, err error) {
return httpIPMethod(env, "PUBLICIP_HTTP_PROVIDERS", "IP_METHOD", ipversion.IP4or6)
}
// getIPv4HTTPProviders obtains the HTTP providers to obtain your public IPv4 address.
func (p *PubIP) getIPv4HTTPProviders(env params.Interface) (
providers []http.Provider, warning string, err error) {
return httpIPMethod(env, "PUBLICIPV4_HTTP_PROVIDERS", "IPV4_METHOD", ipversion.IP4)
}
// getIPv6HTTPProviders obtains the HTTP providers to obtain your public IPv6 address.
func (p *PubIP) getIPv6HTTPProviders(env params.Interface) (
providers []http.Provider, warning string, err error) {
return httpIPMethod(env, "PUBLICIPV6_HTTP_PROVIDERS", "IPV6_METHOD", ipversion.IP6)
}
var (
ErrInvalidPublicIPHTTPProvider = errors.New("invalid public IP HTTP provider")
)
func httpIPMethod(env params.Interface, envKey, retroKey string, version ipversion.IPVersion) (
providers []http.Provider, warning string, err error) {
retroKeyOption := params.RetroKeys([]string{retroKey}, func(oldKey, newKey string) {
warning = "You are using an old environment variable " + oldKey +
" please change it to " + newKey
})
s, err := env.Get(envKey, params.Default("cycle"), retroKeyOption)
if err != nil {
return nil, warning, fmt.Errorf("%w: for environment variable %s", err, envKey)
}
availableProviders := http.ListProvidersForVersion(version)
choices := make(map[http.Provider]struct{}, len(availableProviders))
for _, provider := range availableProviders {
choices[provider] = struct{}{}
}
fields := strings.Split(s, ",")
for _, field := range fields {
// Retro-compatibility.
switch field {
case "ipify6":
field = "ipify"
case "noip4", "noip6", "noip8245_4", "noip8245_6":
field = "noip"
case "cycle":
field = all
}
if field == all {
return availableProviders, warning, nil
}
// Custom URL check
url, err := url.Parse(field)
if err == nil && url != nil && url.Scheme == "https" {
providers = append(providers, http.CustomProvider(url))
func stringsToHTTPProviders(providers []string, ipVersion ipversion.IPVersion) (
updatedProviders []http.Provider,
) {
updatedProvidersSet := make(map[string]struct{}, len(providers))
for _, provider := range providers {
if provider != all {
updatedProvidersSet[provider] = struct{}{}
continue
}
provider := http.Provider(field)
if _, ok := choices[provider]; !ok {
return nil, warning, fmt.Errorf("%w: %s", ErrInvalidPublicIPHTTPProvider, provider)
allProviders := http.ListProvidersForVersion(ipVersion)
for _, provider := range allProviders {
updatedProvidersSet[string(provider)] = struct{}{}
}
providers = append(providers, provider)
}
if len(providers) == 0 {
return nil, warning, fmt.Errorf("%w: for IP version %s", ErrInvalidPublicIPHTTPProvider, version)
updatedProviders = make([]http.Provider, 0, len(updatedProvidersSet))
for provider := range updatedProvidersSet {
updatedProviders = append(updatedProviders, http.Provider(provider))
}
return providers, warning, nil
return updatedProviders
}
// ToDNSPOptions assumes the settings have been validated.
func (p *PubIP) ToDNSPOptions() (options []dns.Option) {
uniqueProviders := make(map[string]struct{}, len(p.DNSProviders))
for _, provider := range p.DNSProviders {
if provider != all {
uniqueProviders[provider] = struct{}{}
}
allProviders := dns.ListProviders()
for _, provider := range allProviders {
uniqueProviders[string(provider)] = struct{}{}
}
}
providers := make([]dns.Provider, 0, len(uniqueProviders))
for providerString := range uniqueProviders {
providers = append(providers, dns.Provider(providerString))
}
return []dns.Option{
dns.SetTimeout(p.DNSTimeout),
dns.SetProviders(providers[0], providers[1:]...),
}
}
var ErrNoPublicIPDNSProvider = errors.New("no public IP DNS provider specified")
func (p PubIP) validateDNSProviders() (err error) {
if len(p.DNSProviders) == 0 {
return fmt.Errorf("%w", ErrNoPublicIPDNSProvider)
}
availableProviders := dns.ListProviders()
validChoices := make([]string, len(availableProviders)+1)
for i, provider := range availableProviders {
validChoices[i] = string(provider)
}
validChoices[len(validChoices)-1] = all
return validate.AreAllOneOf(p.DNSProviders, validChoices)
}
func (p PubIP) validateHTTPIPProviders() (err error) {
return validateHTTPIPProviders(p.HTTPIPProviders, ipversion.IP4or6)
}
func (p PubIP) validateHTTPIPv4Providers() (err error) {
return validateHTTPIPProviders(p.HTTPIPv4Providers, ipversion.IP4)
}
func (p PubIP) validateHTTPIPv6Providers() (err error) {
return validateHTTPIPProviders(p.HTTPIPv6Providers, ipversion.IP6)
}
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) {
if len(providerStrings) == 0 {
return fmt.Errorf("%w", ErrNoPublicIPHTTPProvider)
}
availableProviders := http.ListProvidersForVersion(version)
choices := make(map[string]struct{}, len(availableProviders)+1)
choices[all] = struct{}{}
for i := range availableProviders {
choices[string(availableProviders[i])] = struct{}{}
}
for _, providerString := range providerStrings {
if providerString == "noip" {
// NoIP is no longer supported because the echo service
// only works over plaintext HTTP and could be tempered with.
// Silently discard it and it will default to another HTTP IP
// echo service.
continue
}
// Custom URL check
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
}
_, ok := choices[providerString]
if !ok {
return fmt.Errorf("%w: %s", validate.ErrValueNotOneOf, providerString)
}
}
return nil
}
func (p *PubIP) read(r *reader.Reader, warner Warner) (err error) {
p.HTTPEnabled, p.DNSEnabled, err = getFetchers(r)
if err != nil {
return err
}
p.HTTPIPProviders = r.CSV("PUBLICIP_HTTP_PROVIDERS",
reader.RetroKeys("IP_METHOD"))
p.HTTPIPv4Providers = r.CSV("PUBLICIPV4_HTTP_PROVIDERS",
reader.RetroKeys("IPV4_METHOD"))
p.HTTPIPv6Providers = r.CSV("PUBLICIPV6_HTTP_PROVIDERS",
reader.RetroKeys("IPV6_METHOD"))
// Retro-compatibility
for i := range p.HTTPIPProviders {
p.HTTPIPProviders[i] = handleRetroProvider(p.HTTPIPProviders[i])
}
for i := range p.HTTPIPv4Providers {
p.HTTPIPv4Providers[i] = handleRetroProvider(p.HTTPIPv4Providers[i])
}
for i := range p.HTTPIPv6Providers {
p.HTTPIPv6Providers[i] = handleRetroProvider(p.HTTPIPv6Providers[i])
}
// Retro-compatibility for now defunct opendns http provider for ipv4 or ipv6
if len(p.HTTPIPProviders) > 0 { // check to avoid transforming `nil` to `[]`
httpIPProvidersTemp := make([]string, len(p.HTTPIPProviders))
copy(httpIPProvidersTemp, p.HTTPIPProviders)
p.HTTPIPProviders = make([]string, 0, len(p.HTTPIPProviders))
for _, provider := range httpIPProvidersTemp {
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)
}
}
}
p.DNSProviders = r.CSV("PUBLICIP_DNS_PROVIDERS")
// Retro-compatibility
for i, provider := range p.DNSProviders {
if provider == "google" {
warner.Warnf("dns provider google will be ignored " +
"since it is no longer supported, " +
"see https://github.com/qdm12/ddns-updater/issues/492")
p.DNSProviders[i] = p.DNSProviders[len(p.DNSProviders)-1]
p.DNSProviders = p.DNSProviders[:len(p.DNSProviders)-1]
}
}
p.DNSTimeout, err = r.Duration("PUBLICIP_DNS_TIMEOUT")
if err != nil {
return err
}
return nil
}
var ErrFetcherNotValid = errors.New("fetcher is not valid")
func getFetchers(reader *reader.Reader) (http, dns *bool, err error) {
// TODO change to use reader.BoolPtr with retro-compatibility
s := reader.String("PUBLICIP_FETCHERS")
if s == "" {
return nil, nil, nil
}
http, dns = new(bool), new(bool)
fields := strings.Split(s, ",")
for i, field := range fields {
switch strings.ToLower(field) {
case "all":
*http = true
*dns = true
case "http":
*http = true
case "dns":
*dns = true
default:
return nil, nil, fmt.Errorf(
"%w: %q at position %d of %d",
ErrFetcherNotValid, field, i+1, len(fields))
}
}
return http, dns, nil
}
func handleRetroProvider(provider string) (updatedProvider string) {
switch provider {
case "ipify6":
return "ipify"
case "noip4", "noip6", "noip8245_4", "noip8245_6":
return "noip"
case "cycle":
return "all"
default:
return provider
}
}

View File

@@ -1,27 +1,80 @@
package config
import (
"errors"
"fmt"
"os"
"net"
"time"
"github.com/qdm12/ddns-updater/internal/resolver"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree"
)
func readResolver() (settings resolver.Settings, err error) {
address := os.Getenv("RESOLVER_ADDRESS")
if address != "" {
settings.Address = &address
}
timeoutString := os.Getenv("RESOLVER_TIMEOUT")
if timeoutString != "" {
timeout, err := time.ParseDuration(timeoutString)
if err != nil {
return settings, fmt.Errorf("environment variable RESOLVER_TIMEOUT: %w", err)
}
settings.Timeout = timeout
}
return settings, nil
type Resolver struct {
Address *string
Timeout time.Duration
}
func (r *Resolver) setDefaults() {
r.Address = gosettings.DefaultPointer(r.Address, "")
const defaultTimeout = 5 * time.Second
r.Timeout = gosettings.DefaultComparable(r.Timeout, defaultTimeout)
}
var (
ErrAddressHostEmpty = errors.New("address host is empty")
ErrAddressPortEmpty = errors.New("address port is empty")
ErrTimeoutTooLow = errors.New("timeout is too low")
)
func (r Resolver) Validate() (err error) {
if *r.Address != "" {
host, port, err := net.SplitHostPort(*r.Address)
if err != nil {
return fmt.Errorf("splitting host and port from address: %w", err)
}
switch {
case host == "":
return fmt.Errorf("%w: in %s", ErrAddressHostEmpty, *r.Address)
case port == "":
return fmt.Errorf("%w: in %s", ErrAddressPortEmpty, *r.Address)
}
}
const minTimeout = 10 * time.Millisecond
if r.Timeout < minTimeout {
return fmt.Errorf("%w: %s is below the minimum %s",
ErrTimeoutTooLow, r.Timeout, minTimeout)
}
return nil
}
func (r Resolver) String() string {
return r.ToLinesNode().String()
}
func (r Resolver) ToLinesNode() *gotree.Node {
if *r.Address == "" {
return gotree.New("Resolver: use Go default resolver")
}
node := gotree.New("Resolver")
node.Appendf("Address: %s", *r.Address)
node.Appendf("Timeout: %s", r.Timeout)
return node
}
func (r *Resolver) read(reader *reader.Reader) (err error) {
r.Address = reader.Get("RESOLVER_ADDRESS")
if r.Address != nil { // conveniently add port 53 if not specified
_, port, err := net.SplitHostPort(*r.Address)
if err == nil && port == "" {
*r.Address += ":53"
}
}
r.Timeout, err = reader.Duration("RESOLVER_TIMEOUT")
return err
}

View File

@@ -0,0 +1,10 @@
package config
type Warner interface {
Warnf(format string, a ...interface{})
}
func handleDeprecated(warner Warner, oldKey, newKey string) {
warner.Warnf("You are using an old environment variable %s, please change it to %s",
oldKey, newKey)
}

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