mirror of
https://github.com/qdm12/ddns-updater.git
synced 2026-04-25 02:22:11 -04:00
Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e95816ab46 | ||
|
|
75191c2876 | ||
|
|
4b65908a88 | ||
|
|
f910ac9cfc | ||
|
|
5a56464b38 | ||
|
|
8b9ca1204e | ||
|
|
0b747f8323 | ||
|
|
7f6ccdb4fb | ||
|
|
7b8505cff4 | ||
|
|
40e4da4e93 | ||
|
|
981bed1e13 | ||
|
|
d73be0a9e5 | ||
|
|
4c7f17e816 | ||
|
|
5ea1537d59 | ||
|
|
9220585a98 | ||
|
|
c16287e48a | ||
|
|
0674864e54 | ||
|
|
039396b68c | ||
|
|
e46f2f2965 | ||
|
|
4ccd53da4d | ||
|
|
4653a484ef | ||
|
|
71139938f0 | ||
|
|
b3b4dad0a2 | ||
|
|
d5515cfb2c | ||
|
|
ab22578c9e | ||
|
|
623cb536e1 | ||
|
|
13035511bb | ||
|
|
e019e371ea | ||
|
|
c14324153c | ||
|
|
0c77340887 | ||
|
|
9787957b94 | ||
|
|
3a6262ef2c | ||
|
|
6b9ed56b18 | ||
|
|
8c1b3e556c | ||
|
|
1627254667 | ||
|
|
a8edd5ccc3 | ||
|
|
4cdc052577 | ||
|
|
bc272e079e | ||
|
|
c7dbbcbaa0 | ||
|
|
8f456977be | ||
|
|
425da967a2 | ||
|
|
1cd57d655e | ||
|
|
d04c67c7ab | ||
|
|
918df24488 | ||
|
|
6d70ca078c | ||
|
|
9f0ca6ceaa | ||
|
|
7ad0d8dc57 | ||
|
|
5b800cf278 | ||
|
|
1d6053e528 | ||
|
|
c345a788e3 | ||
|
|
158fed7c51 | ||
|
|
07d7645d78 | ||
|
|
55d8c0d703 | ||
|
|
db07ed3759 | ||
|
|
dbd2f79760 | ||
|
|
da4791e2db | ||
|
|
32fafeca95 | ||
|
|
711b1acfc7 | ||
|
|
339f5001e1 |
4
.github/CONTRIBUTING.md
vendored
4
.github/CONTRIBUTING.md
vendored
@@ -50,7 +50,7 @@ You might want to use an editor such as [Visual Studio Code](https://code.visual
|
||||
|
||||
- Test the code: `go test ./...`
|
||||
- Lint the code `golangci-lint run`
|
||||
- Build the program: `go build -o app cmd/updater/main.go`
|
||||
- Build the program: `go build -o app cmd/ddns-updater/main.go`
|
||||
- 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`
|
||||
|
||||
@@ -77,7 +77,7 @@ In more detailed steps:
|
||||
|
||||
```go
|
||||
case constants.Example:
|
||||
return example.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
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.
|
||||
|
||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -141,7 +141,7 @@ jobs:
|
||||
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
||||
|
||||
- name: Build and push final image
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/s390x,linux/ppc64le,linux/riscv64
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
2
.github/workflows/configs/.goreleaser.yaml
vendored
2
.github/workflows/configs/.goreleaser.yaml
vendored
@@ -2,7 +2,7 @@ before:
|
||||
hooks:
|
||||
- go mod download
|
||||
builds:
|
||||
- main: ./cmd/updater/main.go
|
||||
- main: ./cmd/ddns-updater/main.go
|
||||
flags:
|
||||
- -trimpath
|
||||
env:
|
||||
|
||||
3
.github/workflows/configs/mlc-config.json
vendored
3
.github/workflows/configs/mlc-config.json
vendored
@@ -26,6 +26,9 @@
|
||||
},
|
||||
{
|
||||
"pattern": "https://github.com/qdm12/ddns-updater/pkgs/container/ddns-updater"
|
||||
},
|
||||
{
|
||||
"pattern": "^https://www.duckdns.org/$"
|
||||
}
|
||||
],
|
||||
"timeout": "20s",
|
||||
|
||||
2
.github/workflows/markdown.yml
vendored
2
.github/workflows/markdown.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: DavidAnson/markdownlint-cli2-action@v16
|
||||
- uses: DavidAnson/markdownlint-cli2-action@v17
|
||||
with:
|
||||
globs: "**.md"
|
||||
config: .markdownlint.json
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ARG BUILDPLATFORM=linux/amd64
|
||||
ARG ALPINE_VERSION=3.19
|
||||
ARG ALPINE_VERSION=3.20
|
||||
ARG GO_VERSION=1.22
|
||||
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
||||
ARG GOLANGCI_LINT_VERSION=v1.56.2
|
||||
@@ -62,7 +62,7 @@ RUN GOARCH="$(xcputranslate translate -targetplatform ${TARGETPLATFORM} -field a
|
||||
-X 'main.version=$VERSION' \
|
||||
-X 'main.date=$CREATED' \
|
||||
-X 'main.commit=$COMMIT' \
|
||||
" -o app cmd/updater/main.go
|
||||
" -o app cmd/ddns-updater/main.go
|
||||
|
||||
FROM scratch
|
||||
EXPOSE 8000
|
||||
@@ -103,6 +103,8 @@ ENV \
|
||||
SHOUTRRR_ADDRESSES= \
|
||||
SHOUTRRR_DEFAULT_TITLE="DDNS Updater" \
|
||||
TZ= \
|
||||
# UMASK left empty so it dynamically defaults to the OS current umask
|
||||
UMASK= \
|
||||
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
|
||||
HEALTH_HEALTHCHECKSIO_BASE_URL=https://hc-ping.com \
|
||||
HEALTH_HEALTHCHECKSIO_UUID=
|
||||
|
||||
28
README.md
28
README.md
@@ -30,10 +30,23 @@ Program to keep DNS A and/or AAAA records updated for multiple DNS providers
|
||||
[](LICENSE)
|
||||

|
||||
|
||||
## 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 [`qmcgaw/ddns-updater`](https://hub.docker.com/r/qmcgaw/ddns-updater) and [`ghcr.io/qdm12/ddns-updater`]((https://github.com/qdm12/ddns-updater/pkgs/container/ddns-updater))
|
||||
- 🆕 Available as [zero-dependency binaries for Linux, Windows and MacOS](https://github.com/qdm12/ddns-updater/releases)
|
||||
- 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
|
||||
@@ -99,7 +112,7 @@ Program to keep DNS A and/or AAAA records updated for multiple DNS providers
|
||||
|
||||
### Binary programs
|
||||
|
||||
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). Note this is only available from [release v2.6.0](https://github.com/qdm12/ddns-updater/releases/tag/v2.6.0).
|
||||
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:
|
||||
@@ -109,8 +122,7 @@ Program to keep DNS A and/or AAAA records updated for multiple DNS providers
|
||||
"settings": [
|
||||
{
|
||||
"provider": "namecheap",
|
||||
"domain": "example.com",
|
||||
"host": "@",
|
||||
"domain": "sub.example.com",
|
||||
"password": "e5322165c1d74692bfa6d807100c0310"
|
||||
}
|
||||
]
|
||||
@@ -154,8 +166,7 @@ Program to keep DNS A and/or AAAA records updated for multiple DNS providers
|
||||
"settings": [
|
||||
{
|
||||
"provider": "namecheap",
|
||||
"domain": "example.com",
|
||||
"host": "@",
|
||||
"domain": "sub.example.com",
|
||||
"password": "e5322165c1d74692bfa6d807100c0310"
|
||||
}
|
||||
]
|
||||
@@ -245,7 +256,7 @@ Check the documentation for your DNS provider:
|
||||
|
||||
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",`.
|
||||
|
||||
### Environment variables
|
||||
|
||||
@@ -279,6 +290,7 @@ Note that:
|
||||
| `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
|
||||
|
||||
@@ -294,7 +306,6 @@ You can otherwise customize it with the following:
|
||||
- `ipify` using [https://api64.ipify.org](https://api64.ipify.org)
|
||||
- `ifconfig` using [https://ifconfig.io/ip](https://ifconfig.io/ip)
|
||||
- `ipinfo` using [https://ipinfo.io/ip](https://ipinfo.io/ip)
|
||||
- `google` using [https://domains.google.com/checkip](https://domains.google.com/checkip)
|
||||
- `spdyn` using [https://checkip.spdyn.de](https://checkip.spdyn.de/)
|
||||
- `ipleak` using [https://ipleak.net/json](https://ipleak.net/json)
|
||||
- `icanhazip` using [https://icanhazip.com](https://icanhazip.com)
|
||||
@@ -400,7 +411,6 @@ You can use optional build arguments with `--build-arg KEY=VALUE` from the table
|
||||
- [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
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"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"
|
||||
@@ -45,7 +46,7 @@ func main() {
|
||||
buildInfo := models.BuildInformation{
|
||||
Version: version,
|
||||
Commit: commit,
|
||||
Date: date,
|
||||
Created: date,
|
||||
}
|
||||
logger := log.New()
|
||||
|
||||
@@ -129,6 +130,10 @@ func _main(ctx context.Context, reader *reader.Reader, args []string, logger log
|
||||
return err
|
||||
}
|
||||
|
||||
if *config.Paths.Umask > 0 {
|
||||
system.SetUmask(*config.Paths.Umask)
|
||||
}
|
||||
|
||||
shoutrrrSettings := shoutrrr.Settings{
|
||||
Addresses: config.Shoutrrr.Addresses,
|
||||
DefaultTitle: config.Shoutrrr.DefaultTitle,
|
||||
@@ -262,7 +267,7 @@ func _main(ctx context.Context, reader *reader.Reader, args []string, logger log
|
||||
}
|
||||
|
||||
func printSplash(buildInfo models.BuildInformation) {
|
||||
announcementExp, err := time.Parse(time.RFC3339, "2023-07-15T00:00:00Z")
|
||||
announcementExp, err := time.Parse(time.RFC3339, "2024-10-15T00:00:00Z")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -272,8 +277,8 @@ func printSplash(buildInfo models.BuildInformation) {
|
||||
Emails: []string{"quentin.mcgaw@gmail.com"},
|
||||
Version: buildInfo.Version,
|
||||
Commit: buildInfo.Commit,
|
||||
BuildDate: buildInfo.Date,
|
||||
Announcement: "Public IP dns provider GOOGLE, see https://github.com/qdm12/ddns-updater/issues/492",
|
||||
Created: buildInfo.Created,
|
||||
Announcement: "Public IP http provider GOOGLE is no longer working",
|
||||
AnnounceExp: announcementExp,
|
||||
// Sponsor information
|
||||
PaypalUser: "qmcgaw",
|
||||
@@ -319,10 +324,10 @@ func readRecords(providers []provider.Provider, persistentDB *persistence.Databa
|
||||
records = make([]recordslib.Record, len(providers))
|
||||
for i, provider := range providers {
|
||||
logger.Info("Reading history from database: domain " +
|
||||
provider.Domain() + " host " + provider.Host() +
|
||||
provider.Domain() + " owner " + provider.Owner() +
|
||||
" " + provider.IPVersion().String())
|
||||
events, err := persistentDB.GetEvents(provider.Domain(),
|
||||
provider.Host(), provider.IPVersion())
|
||||
provider.Owner(), provider.IPVersion())
|
||||
if err != nil {
|
||||
shoutrrrClient.Notify(err.Error())
|
||||
return nil, err
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
{
|
||||
"provider": "aliyun",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"access_key_id": "your access_key_id",
|
||||
"access_secret": "your access_secret",
|
||||
"ip_version": "ipv4",
|
||||
@@ -22,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`).
|
||||
- `"access_key_id"`
|
||||
- `"access_secret"`
|
||||
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
"settings": [
|
||||
{
|
||||
"provider": "allinkl",
|
||||
"domain": "domain.com",
|
||||
"host": "host",
|
||||
"domain": "sub.domain.com",
|
||||
"username": "dynXXXXXXX",
|
||||
"password": "password",
|
||||
"ip_version": "ipv4",
|
||||
@@ -22,8 +21,7 @@
|
||||
|
||||
### 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
|
||||
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
"settings": [
|
||||
{
|
||||
"provider": "changeip",
|
||||
"domain": "domain.com",
|
||||
"host": "host",
|
||||
"domain": "sub.domain.com",
|
||||
"username": "dynXXXXXXX",
|
||||
"password": "password",
|
||||
"ip_version": "ipv4",
|
||||
@@ -22,8 +21,7 @@
|
||||
|
||||
### 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"`
|
||||
- `"password"`
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"provider": "cloudflare",
|
||||
"zone_identifier": "some id",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"ttl": 600,
|
||||
"token": "yourtoken",
|
||||
"ip_version": "ipv4",
|
||||
@@ -24,8 +23,7 @@
|
||||
### Compulsory parameters
|
||||
|
||||
- `"zone_identifier"` is the Zone ID of your site, from the domain overview page written as *Zone ID*
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be `"@"`, a subdomain or the wildcard `"*"`.
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
|
||||
See [this issue comment for context](https://github.com/qdm12/ddns-updater/issues/243#issuecomment-928313949). This is left as is for compatibility.
|
||||
- `"ttl"` integer value for record TTL in seconds (specify 1 for automatic)
|
||||
- One of the following ([how to find API keys](https://developers.cloudflare.com/fundamentals/api/get-started/)):
|
||||
|
||||
@@ -15,7 +15,6 @@ Feel free to open issues to extend its configuration options.
|
||||
{
|
||||
"provider": "custom",
|
||||
"domain": "example.com",
|
||||
"host": "@",
|
||||
"url": "https://example.com/update?domain=example.com&host=@&username=username&client_key=client_key",
|
||||
"ipv4key": "ipv4",
|
||||
"ipv6key": "ipv6",
|
||||
@@ -29,8 +28,7 @@ Feel free to open issues to extend its configuration options.
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"` is the domain name to update
|
||||
- `"host"` is the host to update, which can be `"@"` (root), `"*"` or a subdomain
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
|
||||
- `"url"` is the URL to update your records and should contain all the information EXCEPT the IP address to update
|
||||
- `"ipv4key"` is the URL query parameter name for the IPv4 address, for example `ipv4` will be added to the URL with `&ipv4=1.2.3.4`.
|
||||
- `"ipv6key"` is the URL query parameter name for the IPv6 address, for example `ipv6` will be added to the URL with `&ipv6=::aaff`. Even if you don't use IPv6, this must be set to something.
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
{
|
||||
"provider": "dd24",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"password": "password",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": ""
|
||||
@@ -21,8 +20,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`).
|
||||
- `"password"` is your password
|
||||
|
||||
### Optional parameters
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
"settings": [
|
||||
{
|
||||
"provider": "ddnss",
|
||||
"provider_ip": true,
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"username": "user",
|
||||
"password": "password",
|
||||
"dual_stack": false,
|
||||
@@ -24,14 +22,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
|
||||
|
||||
- `"dual_stack"` can be set to `true` **if you have turn on dual stack for your record** to update both IPv4 and IPv6 addresses. Note it is ignored if `"provider_ip": true`. More precisely:
|
||||
- `"dual_stack"` can be set to `true` **if you have turn on dual stack for your record** to update both IPv4 and IPv6 addresses. More precisely:
|
||||
- if it is `false`, the updates are done using the `ip` parameter and only one IP address can be set (ipv4 or ipv6, whichever is last sent).
|
||||
- if it is `true`, the updates are done using the `ip` and `ip6` parameters, for IPv4 and IPv6 respectively, and both can be set on the same record
|
||||
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
|
||||
|
||||
@@ -9,12 +9,10 @@
|
||||
"settings": [
|
||||
{
|
||||
"provider": "desec",
|
||||
"domain": "dedyn.io",
|
||||
"host": "host",
|
||||
"domain": "sub.dedyn.io",
|
||||
"token": "token",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": "",
|
||||
"provider_ip": false
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -22,15 +20,13 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` can be `@` for the root domain or a subdomain or a wildcard subdomain (`*`), defaults to `@`
|
||||
- `"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.
|
||||
- `"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.
|
||||
|
||||
## Domain setup
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
{
|
||||
"provider": "digitalocean",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"token": "yourtoken",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": ""
|
||||
@@ -21,8 +20,7 @@
|
||||
|
||||
### 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
|
||||
|
||||
@@ -10,10 +10,8 @@
|
||||
{
|
||||
"provider": "dnsomatic",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"provider_ip": true,
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
@@ -23,11 +21,9 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be a subdomain or `"@"` or `"*"`
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
|
||||
- `"username"`
|
||||
- `"password"`
|
||||
- `"provider_ip"`
|
||||
|
||||
### Optional parameters
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
{
|
||||
"provider": "dnspod",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"token": "yourtoken",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": ""
|
||||
@@ -21,8 +20,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`).
|
||||
- `"token"`
|
||||
|
||||
### Optional parameters
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
{
|
||||
"provider": "dondominio",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"name": "something",
|
||||
"username": "username",
|
||||
"key": "key",
|
||||
"ip_version": "ipv4",
|
||||
@@ -23,9 +21,7 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` is the subdomain to update which can be `@`, `*` or a subdomain
|
||||
- `"name"` is the name of the service/hosting
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
|
||||
- `"username"`
|
||||
- `"password"`
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
{
|
||||
"provider": "dreamhost",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"key": "key",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": ""
|
||||
@@ -26,7 +25,7 @@
|
||||
|
||||
### Optional parameters
|
||||
|
||||
- `"host"` is your host and can be a subdomain or `"@"`. It defaults to `"@"`.
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
|
||||
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
|
||||
- `"ipv6_suffix"` is the IPv6 interface 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.
|
||||
|
||||
|
||||
@@ -9,11 +9,10 @@
|
||||
"settings": [
|
||||
{
|
||||
"provider": "duckdns",
|
||||
"host": "host",
|
||||
"domain": "sub.duckdns.org",
|
||||
"token": "token",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": "",
|
||||
"provider_ip": true
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -21,14 +20,13 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"host"` is your host, for example `subdomain` for `subdomain.duckdns.org`
|
||||
- `"domain"` is the domain to update. For example, for the owner/host `sub`, it would be `sub.duckdns.org`. The [eTLD](https://developer.mozilla.org/en-US/docs/Glossary/eTLD) must be `duckdns.org`.
|
||||
- `"token"`
|
||||
|
||||
### Optional parameters
|
||||
|
||||
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
|
||||
- `"ipv6_suffix"` is the IPv6 interface 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.
|
||||
- `"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.
|
||||
|
||||
## Domain setup
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
{
|
||||
"provider": "dyn",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"username": "username",
|
||||
"client_key": "client_key",
|
||||
"ip_version": "ipv4",
|
||||
@@ -22,8 +21,7 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be a subdomain or `"@"` or `"*"`
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
|
||||
- `"username"`
|
||||
- `"client_key"`
|
||||
|
||||
|
||||
@@ -10,13 +10,11 @@
|
||||
{
|
||||
"provider": "dynu",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"group": "group",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": "",
|
||||
"provider_ip": true
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -24,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`).
|
||||
- `"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)
|
||||
|
||||
@@ -33,7 +30,6 @@
|
||||
|
||||
- `"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.
|
||||
- `"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.
|
||||
- `"group"` specify the Group for which you want to set the IP (will update any domains and subdomains in the same group)
|
||||
|
||||
## Domain setup
|
||||
|
||||
@@ -10,11 +10,9 @@
|
||||
{
|
||||
"provider": "dynv6",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"token": "token",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": "",
|
||||
"provider_ip": true
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -22,14 +20,12 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be a subdomain or `"@"`
|
||||
- `"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) 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.
|
||||
- `"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.
|
||||
|
||||
## Domain setup
|
||||
|
||||
@@ -10,12 +10,10 @@
|
||||
{
|
||||
"provider": "easydns",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"username": "username",
|
||||
"token": "token",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": "",
|
||||
"provider_ip": true
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -23,8 +21,7 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be a subdomain or `"@"` or `"*"`
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
|
||||
- `"username"`
|
||||
- `"token"`
|
||||
|
||||
@@ -32,6 +29,5 @@
|
||||
|
||||
- `"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.
|
||||
- `"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.
|
||||
|
||||
## Domain setup
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
{
|
||||
"provider": "example",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"ip_version": "ipv4",
|
||||
@@ -24,8 +23,7 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be a subdomain or `"@"` or the wildcard `"*"`
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
|
||||
- `"username"`
|
||||
- `"password"`
|
||||
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
"settings": [
|
||||
{
|
||||
"provider": "freedns",
|
||||
"domain": "domain.com",
|
||||
"host": "host",
|
||||
"domain": "sub.domain.com",
|
||||
"token": "token",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": ""
|
||||
@@ -21,8 +20,7 @@
|
||||
|
||||
### 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
|
||||
|
||||
@@ -12,7 +12,6 @@ This provider uses Gandi v5 API
|
||||
{
|
||||
"provider": "gandi",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"personal_access_token": "token",
|
||||
"ttl": 3600,
|
||||
"ip_version": "ipv4",
|
||||
@@ -24,8 +23,7 @@ This provider uses Gandi v5 API
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` which can be a subdomain, `@` or a wildcard `*`
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
|
||||
- `"personal_access_token"`
|
||||
|
||||
### Optional parameters
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
// ...
|
||||
},
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
@@ -30,8 +29,7 @@
|
||||
- `"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
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
{
|
||||
"provider": "godaddy",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"key": "key",
|
||||
"secret": "secret",
|
||||
"ip_version": "ipv4",
|
||||
@@ -22,8 +21,7 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be a subdomain, `"@"` or `"*"` generally
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
|
||||
- `"key"`
|
||||
- `"secret"`
|
||||
|
||||
|
||||
@@ -9,11 +9,9 @@
|
||||
"settings": [
|
||||
{
|
||||
"provider": "goip",
|
||||
"domain": "goip.de",
|
||||
"host": "mysubdomain",
|
||||
"domain": "mydomain.goip.de",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"provider_ip": true,
|
||||
"ip_version": "",
|
||||
"ipv6_suffix": ""
|
||||
|
||||
@@ -24,13 +22,11 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"host"` is the full FQDN of your ddns address. sample.goip.de or something.goip.it
|
||||
- `"domain"` is the domain to update. For example, for the owner/host `sub`, it would be `sub.goip.de`. The [eTLD](https://developer.mozilla.org/en-US/docs/Glossary/eTLD) must be `goip.de` or `goip.it`.
|
||||
- `"username"` is your goip.de username listed under "Routers"
|
||||
- `"password"` is your router account password
|
||||
|
||||
### Optional parameters
|
||||
|
||||
- `"domain"` is the domain name which can be `goip.de` or `goip.it`, and defaults to `goip.de` if left unset.
|
||||
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request. This is automatically disabled for an IPv6 public address since it is not supported.
|
||||
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4`.
|
||||
- `"ipv6_suffix"` is the IPv6 interface 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.
|
||||
|
||||
@@ -10,9 +10,7 @@
|
||||
{
|
||||
"provider": "he",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"password": "password",
|
||||
"provider_ip": true,
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
@@ -22,14 +20,12 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be a subdomain or `"@"` or `"*"` (untested)
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard. (untested)
|
||||
- `"password"`
|
||||
|
||||
### Optional parameters
|
||||
|
||||
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
|
||||
- `"ipv6_suffix"` is the IPv6 interface 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.
|
||||
- `"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.
|
||||
|
||||
## Domain setup
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"provider": "hetzner",
|
||||
"zone_identifier": "some id",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"ttl": 600,
|
||||
"token": "yourtoken",
|
||||
"ip_version": "ipv4",
|
||||
@@ -24,8 +23,7 @@
|
||||
### Compulsory parameters
|
||||
|
||||
- `"zone_identifier"` is the Zone ID of your site, from the domain overview page written as *Zone ID*
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be `"@"`, a subdomain or the wildcard `"*"`.
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
|
||||
- `"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
|
||||
|
||||
@@ -10,12 +10,10 @@
|
||||
{
|
||||
"provider": "infomaniak",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": "",
|
||||
"provider_ip": true
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -23,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`).
|
||||
- `"username"` for dyndns (**not** your infomaniak admin username!)
|
||||
- `"password"` for dyndns (**not** your infomaniak admin password!)
|
||||
|
||||
@@ -32,7 +29,6 @@
|
||||
|
||||
- `"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.
|
||||
- `"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.
|
||||
|
||||
## Domain setup
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
{
|
||||
"provider": "inwx",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"ip_version": "ipv4",
|
||||
@@ -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`).
|
||||
- `"username"`
|
||||
- `"password"`
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
{
|
||||
"provider": "ionos",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"api_key": "api_key",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": ""
|
||||
@@ -21,8 +20,7 @@
|
||||
|
||||
### 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.
|
||||
- `"api_key"` is your API key, obtained from [creating an API key](https://www.ionos.com/help/domains/configuring-your-ip-address/set-up-dynamic-dns-with-company-name/#c181598)
|
||||
|
||||
### Optional parameters
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
{
|
||||
"provider": "linode",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"token": "token",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": ""
|
||||
@@ -21,8 +20,7 @@
|
||||
|
||||
### 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
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
{
|
||||
"provider": "luadns",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"email": "email",
|
||||
"token": "token",
|
||||
"ip_version": "ipv4",
|
||||
@@ -22,8 +21,7 @@
|
||||
|
||||
### 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"`
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
{
|
||||
"provider": "name.com",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"username": "username",
|
||||
"token": "token",
|
||||
"ttl": 300,
|
||||
@@ -25,8 +24,7 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be a subdomain, `"@"` or `"*"` generally
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
|
||||
- `"username"` is your account username
|
||||
- `"token"` which you can obtain from [www.name.com/account/settings/api](https://www.name.com/account/settings/api)
|
||||
|
||||
|
||||
@@ -10,9 +10,7 @@
|
||||
{
|
||||
"provider": "namecheap",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"password": "password",
|
||||
"provider_ip": true
|
||||
"password": "password"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -20,14 +18,11 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be a subdomain, `"@"` or `"*"` generally
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
|
||||
- `"password"`
|
||||
|
||||
### Optional parameters
|
||||
|
||||
- `"provider_ip"` can be set to `true` to let your DNS provider determine your IPv4 address (and/or IPv6 address) automatically when you send an update request, without sending the new IP address detected by the program in the request.
|
||||
|
||||
Note that Namecheap only supports ipv4 addresses for now.
|
||||
|
||||
## Domain setup
|
||||
|
||||
@@ -14,7 +14,6 @@ Also keep in mind, that TTL, Expire, Retry and Refresh values of the given Domai
|
||||
{
|
||||
"provider": "netcup",
|
||||
"domain": "domain.com",
|
||||
"host": "host",
|
||||
"api_key": "xxxxx",
|
||||
"password": "yyyyy",
|
||||
"customer_number": "111111",
|
||||
@@ -27,8 +26,7 @@ Also keep in mind, that TTL, Expire, Retry and Refresh values of the given Domai
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"` is your domain
|
||||
- `"host"` is your host (subdomain) or `"@"` for the root of the domain. It cannot be the wildcard.
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`) or the wildcard `*.example.com`.
|
||||
- `"api_key"` is your api key (generated in the [customercontrolpanel](https://www.customercontrolpanel.de))
|
||||
- `"password"` is your api password (generated in the [customercontrolpanel](https://www.customercontrolpanel.de)). Netcup only allows one ApiPassword. This is not the account password. This password is used for all api keys.
|
||||
- `"customer_number"` is your customer number (viewable in the [customercontrolpanel](https://www.customercontrolpanel.de) next to your name). As seen in the example above, provide the number as string value.
|
||||
|
||||
@@ -10,11 +10,9 @@
|
||||
{
|
||||
"provider": "njalla",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"key": "key",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": "",
|
||||
"provider_ip": true
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -22,15 +20,13 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be a subdomain or `"@"` or `"*"`
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
|
||||
- `"key"` is the key for your record
|
||||
|
||||
### Optional parameters
|
||||
|
||||
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
|
||||
- `"ipv6_suffix"` is the IPv6 interface 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.
|
||||
- `"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.
|
||||
|
||||
## Domain setup
|
||||
|
||||
|
||||
@@ -10,12 +10,10 @@
|
||||
{
|
||||
"provider": "noip",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": "",
|
||||
"provider_ip": true
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -23,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`).
|
||||
- `"username"`
|
||||
- `"password"`
|
||||
|
||||
@@ -32,6 +29,5 @@
|
||||
|
||||
- `"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.
|
||||
- `"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.
|
||||
|
||||
## Domain setup
|
||||
|
||||
@@ -10,12 +10,10 @@
|
||||
{
|
||||
"provider": "opendns",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": "",
|
||||
"provider_ip": true
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -23,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`).
|
||||
- `"username"`
|
||||
- `"password"`
|
||||
|
||||
@@ -32,6 +29,5 @@
|
||||
|
||||
- `"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.
|
||||
- `"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.
|
||||
|
||||
## Domain setup
|
||||
|
||||
10
docs/ovh.md
10
docs/ovh.md
@@ -10,12 +10,10 @@
|
||||
{
|
||||
"provider": "ovh",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": "",
|
||||
"provider_ip": true
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -23,8 +21,7 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be a subdomain or `"@"`
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
|
||||
|
||||
#### Using DynHost
|
||||
|
||||
@@ -36,7 +33,7 @@
|
||||
- `"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
|
||||
|
||||
@@ -44,7 +41,6 @@ The ZoneDNS implementation allows you to update any record name including *.your
|
||||
|
||||
- `"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.
|
||||
- `"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.
|
||||
- `"mode"` select between two modes, OVH's dynamic hosting service (`"dynamic"`) or OVH's API (`"api"`). Default is `"dynamic"`
|
||||
|
||||
## Domain setup
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
{
|
||||
"provider": "porkbun",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"api_key": "sk1_7d119e3f656b00ae042980302e1425a04163c476efec1833q3cb0w54fc6f5022",
|
||||
"secret_api_key": "pk1_5299b57125c8f3cdf347d2fe0e713311ee3a1e11f11a14942b26472593e35368",
|
||||
"ip_version": "ipv4",
|
||||
@@ -22,10 +21,9 @@
|
||||
|
||||
### 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
|
||||
@@ -43,5 +41,12 @@
|
||||
## Record creation
|
||||
|
||||
In case you don't have an A or AAAA record for your host and domain combination, it will be created by DDNS-Updater.
|
||||
However, to do so, the corresponding ALIAS record, that is automatically created by Porkbun, is automatically deleted to allow this.
|
||||
|
||||
Porkbun creates default DNS entries for new domains, which can conflict with creating a root or wildcard A/AAAA record. Therefore, ddns-updater automatically removes any conflicting default record before creating records, as described in the table below:
|
||||
|
||||
| Record type | Owner | Record value | Situation requiring a removal |
|
||||
| --- | --- | --- | --- |
|
||||
| `ALIAS` | `@` | pixie.porkbun.com | Creating A or AAAA record for the root domain **or** wildcard domain |
|
||||
| `CNAME` | `*` | pixie.porkbun.com | Creating A or AAAA record for the wildcard domain |
|
||||
|
||||
More details is in [this comment by @everydaycombat](https://github.com/qdm12/ddns-updater/issues/546#issuecomment-1773960193).
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
{
|
||||
"provider": "route53",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": "",
|
||||
"access_key": "ffffffffffffffffffff",
|
||||
@@ -24,8 +23,7 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be a subdomain or `"@"` or the wildcard `"*"`
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
|
||||
- `"access_key"` is the `AWS_ACCESS_KEY`
|
||||
- `"secret_key"` is the `AWS_SECRET_ACCESS_KEY`
|
||||
- `"zone_id"` is identification of your hosted zone
|
||||
|
||||
@@ -10,10 +10,8 @@
|
||||
{
|
||||
"provider": "selfhost.de",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"provider_ip": true,
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
@@ -23,8 +21,7 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be a subdomain or `"@"`
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
|
||||
- `"username"` is your DynDNS username
|
||||
- `"password"` is your DynDNS password
|
||||
|
||||
@@ -32,6 +29,5 @@
|
||||
|
||||
- `"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.
|
||||
- `"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.
|
||||
|
||||
## Domain setup
|
||||
|
||||
@@ -10,11 +10,9 @@
|
||||
{
|
||||
"provider": "servercow",
|
||||
"domain": "domain.com",
|
||||
"host": "",
|
||||
"username": "servercow_username",
|
||||
"password": "servercow_password",
|
||||
"ttl": 600,
|
||||
"provider_ip": true,
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
@@ -24,8 +22,7 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be `""`, a subdomain or `"*"` generally
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
|
||||
- `"username"` is the username for your DNS API User
|
||||
- `"password"` is the password for your DNS API User
|
||||
|
||||
@@ -34,7 +31,6 @@
|
||||
- `"ttl"` can be set to an integer value for record TTL in seconds (if not set the default is 120)
|
||||
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
|
||||
- `"ipv6_suffix"` is the IPv6 interface 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.
|
||||
- `"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.
|
||||
|
||||
## Domain setup
|
||||
|
||||
|
||||
@@ -10,13 +10,11 @@
|
||||
{
|
||||
"provider": "spdyn",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"user": "user",
|
||||
"password": "password",
|
||||
"token": "token",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": "",
|
||||
"provider_ip": true
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -24,8 +22,7 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be a subdomain or `"@"`
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
|
||||
|
||||
#### Using user and password
|
||||
|
||||
@@ -40,4 +37,3 @@
|
||||
|
||||
- `"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.
|
||||
- `"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.
|
||||
|
||||
@@ -10,11 +10,9 @@
|
||||
{
|
||||
"provider": "strato",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"password": "password",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": "",
|
||||
"provider_ip": true
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -22,15 +20,13 @@
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be a subdomain or `"@"`
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`).
|
||||
- `"password"` is your dyndns password
|
||||
|
||||
### Optional parameters
|
||||
|
||||
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
|
||||
- `"ipv6_suffix"` is the IPv6 interface 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.
|
||||
- `"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.
|
||||
|
||||
## Domain setup
|
||||
|
||||
|
||||
@@ -10,12 +10,10 @@
|
||||
{
|
||||
"provider": "variomedia",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"email": "email@domain.com",
|
||||
"password": "password",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": "",
|
||||
"provider_ip": true
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -23,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`).
|
||||
- `"email"`
|
||||
- `"password"` is your DNS settings password, not your account password ⚠️
|
||||
|
||||
@@ -32,7 +29,6 @@
|
||||
|
||||
- `"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.
|
||||
- `"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.
|
||||
|
||||
## Domain setup
|
||||
|
||||
|
||||
@@ -16,12 +16,10 @@ set the environment variable as `PERIOD=11m` to check your public IP address and
|
||||
{
|
||||
"provider": "zoneedit",
|
||||
"domain": "domain.com",
|
||||
"host": "@",
|
||||
"username": "username",
|
||||
"token": "token",
|
||||
"ip_version": "ipv4",
|
||||
"ipv6_suffix": "",
|
||||
"provider_ip": true
|
||||
"ipv6_suffix": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -29,8 +27,7 @@ set the environment variable as `PERIOD=11m` to check your public IP address and
|
||||
|
||||
### Compulsory parameters
|
||||
|
||||
- `"domain"`
|
||||
- `"host"` is your host and can be a subdomain or `"@"` or `"*"`
|
||||
- `"domain"` is the domain to update. It can be `example.com` (root domain), `sub.example.com` (subdomain of `example.com`) or `*.example.com` for the wildcard.
|
||||
- `"username"`
|
||||
- `"token"`
|
||||
|
||||
@@ -38,7 +35,6 @@ set the environment variable as `PERIOD=11m` to check your public IP address and
|
||||
|
||||
- `"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.
|
||||
- `"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.
|
||||
|
||||
## Domain setup
|
||||
|
||||
|
||||
23
go.mod
23
go.mod
@@ -1,22 +1,24 @@
|
||||
module github.com/qdm12/ddns-updater
|
||||
|
||||
go 1.22
|
||||
go 1.22.0
|
||||
|
||||
toolchain go1.22.5
|
||||
|
||||
require (
|
||||
github.com/breml/rootcerts v0.2.17
|
||||
github.com/chmike/domain v1.0.1
|
||||
github.com/breml/rootcerts v0.2.18
|
||||
github.com/containrrr/shoutrrr v0.8.0
|
||||
github.com/go-chi/chi/v5 v5.0.12
|
||||
github.com/go-chi/chi/v5 v5.1.0
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/miekg/dns v1.1.61
|
||||
github.com/miekg/dns v1.1.62
|
||||
github.com/qdm12/goservices v0.1.0
|
||||
github.com/qdm12/gosettings v0.4.1
|
||||
github.com/qdm12/gosplash v0.1.0
|
||||
github.com/qdm12/gosettings v0.4.4-rc1
|
||||
github.com/qdm12/gosplash v0.2.0
|
||||
github.com/qdm12/gotree v0.2.0
|
||||
github.com/qdm12/log v0.1.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/mod v0.18.0
|
||||
golang.org/x/oauth2 v0.21.0
|
||||
golang.org/x/mod v0.21.0
|
||||
golang.org/x/net v0.29.0
|
||||
golang.org/x/oauth2 v0.23.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -29,9 +31,8 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
46
go.sum
46
go.sum
@@ -1,18 +1,15 @@
|
||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
github.com/breml/rootcerts v0.2.17 h1:0/M2BE2Apw0qEJCXDOkaiu7d5Sx5ObNfe1BkImJ4u1I=
|
||||
github.com/breml/rootcerts v0.2.17/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
||||
github.com/chmike/domain v1.0.1 h1:ug6h3a7LLAfAecBAysbCXWxP1Jo8iBKWNVDxcs1BNzA=
|
||||
github.com/chmike/domain v1.0.1/go.mod h1:h558M2qGKpYRUxHHNyey6puvXkZBjvjmseOla/d1VGQ=
|
||||
github.com/breml/rootcerts v0.2.18 h1:KjZaNT7AX/akUjzpStuwTMQs42YHlPyc6NmdwShVba0=
|
||||
github.com/breml/rootcerts v0.2.18/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
||||
github.com/containrrr/shoutrrr v0.8.0 h1:mfG2ATzIS7NR2Ec6XL+xyoHzN97H8WPjir8aYzJUSec=
|
||||
github.com/containrrr/shoutrrr v0.8.0/go.mod h1:ioyQAyu1LJY6sILuNyKaQaw+9Ttik5QePU8atnAdO2o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
@@ -32,8 +29,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
|
||||
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
|
||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
@@ -42,16 +39,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/qdm12/goservices v0.1.0 h1:9sODefm/yuIGS7ynCkEnNlMTAYn9GzPhtcK4F69JWvc=
|
||||
github.com/qdm12/goservices v0.1.0/go.mod h1:/JOFsAnHFiSjyoXxa5FlfX903h20K5u/3rLzCjYVMck=
|
||||
github.com/qdm12/gosettings v0.4.1 h1:c7+14jO1Y2kFXBCUfS2+QE2NgwTKfzcdJzGEFRItCI8=
|
||||
github.com/qdm12/gosettings v0.4.1/go.mod h1:uItKwGXibJp2pQ0am6MBKilpjfvYTGiH+zXHd10jFj8=
|
||||
github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g=
|
||||
github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw=
|
||||
github.com/qdm12/gosettings v0.4.4-rc1 h1:VT+6O6ww3Cn5v5/LgY2zlXoiCkZzbaLDWaA8ufQoOLY=
|
||||
github.com/qdm12/gosettings v0.4.4-rc1/go.mod h1:CPrt2YC4UsURTrslmhxocVhMCW03lIrqdH2hzIf5prg=
|
||||
github.com/qdm12/gosplash v0.2.0 h1:DOxCEizbW6ZG+FgpH2oK1atT6bM8MHL9GZ2ywSS4zZY=
|
||||
github.com/qdm12/gosplash v0.2.0/go.mod h1:k+1PzhO0th9cpX4q2Nneu4xTsndXqrM/x7NTIYmJ4jo=
|
||||
github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c=
|
||||
github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
|
||||
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
|
||||
github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
@@ -60,15 +55,15 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
@@ -79,13 +74,13 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
@@ -98,7 +93,6 @@ google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFW
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
kernel.org/pub/linux/libs/security/libcap/cap v1.2.69 h1:N0m3tKYbkRMmDobh/47ngz+AWeV7PcfXMDi8xu3Vrag=
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
@@ -11,12 +14,17 @@ import (
|
||||
type Paths struct {
|
||||
DataDir *string
|
||||
Config *string
|
||||
// Umask is the custom umask to use for the system, if different than zero.
|
||||
// If it is set to zero, the system umask is unchanged.
|
||||
// It cannot be nil in the internal state.
|
||||
Umask *fs.FileMode
|
||||
}
|
||||
|
||||
func (p *Paths) setDefaults() {
|
||||
p.DataDir = gosettings.DefaultPointer(p.DataDir, "./data")
|
||||
defaultConfig := filepath.Join(*p.DataDir, "config.json")
|
||||
p.Config = gosettings.DefaultPointer(p.Config, defaultConfig)
|
||||
p.Umask = gosettings.DefaultPointer(p.Umask, fs.FileMode(0))
|
||||
}
|
||||
|
||||
func (p Paths) Validate() (err error) {
|
||||
@@ -31,10 +39,35 @@ func (p Paths) toLinesNode() *gotree.Node {
|
||||
node := gotree.New("Paths")
|
||||
node.Appendf("Data directory: %s", *p.DataDir)
|
||||
node.Appendf("Config file: %s", *p.Config)
|
||||
umaskString := "system default"
|
||||
if *p.Umask != 0 {
|
||||
umaskString = p.Umask.String()
|
||||
}
|
||||
node.Appendf("Umask: %s", umaskString)
|
||||
return node
|
||||
}
|
||||
|
||||
func (p *Paths) read(reader *reader.Reader) {
|
||||
func (p *Paths) read(reader *reader.Reader) (err error) {
|
||||
p.DataDir = reader.Get("DATADIR")
|
||||
p.Config = reader.Get("CONFIG_FILEPATH")
|
||||
|
||||
umaskString := reader.String("UMASK")
|
||||
if umaskString != "" {
|
||||
umask, err := parseUmask(umaskString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse umask: %w", err)
|
||||
}
|
||||
p.Umask = &umask
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseUmask(s string) (umask fs.FileMode, err error) {
|
||||
const base, bitSize = 8, 32
|
||||
umaskUint64, err := strconv.ParseUint(s, base, bitSize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return fs.FileMode(umaskUint64), nil
|
||||
}
|
||||
|
||||
47
internal/config/paths_test.go
Normal file
47
internal/config/paths_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
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 {
|
||||
testCase := testCase
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -263,7 +263,12 @@ func (p *PubIP) read(r *reader.Reader, warner Warner) (err error) {
|
||||
copy(httpIPProvidersTemp, p.HTTPIPProviders)
|
||||
p.HTTPIPProviders = make([]string, 0, len(p.HTTPIPProviders))
|
||||
for _, provider := range httpIPProvidersTemp {
|
||||
if provider != "opendns" {
|
||||
switch provider {
|
||||
case "opendns": // no longer available, for a long time
|
||||
case "google": // found no longer working on 2024.09.17
|
||||
warner.Warnf("http provider google will be ignored " +
|
||||
"since it is no longer supported by Google")
|
||||
default:
|
||||
p.HTTPIPProviders = append(p.HTTPIPProviders, provider)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,10 @@ func (c *Config) Read(reader *reader.Reader,
|
||||
}
|
||||
|
||||
c.Health.Read(reader)
|
||||
c.Paths.read(reader)
|
||||
err = c.Paths.read(reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading paths settings: %w", err)
|
||||
}
|
||||
|
||||
err = c.Backup.read(reader)
|
||||
if err != nil {
|
||||
|
||||
@@ -40,7 +40,8 @@ func Test_Settings_String(t *testing.T) {
|
||||
| └── Server listening address: 127.0.0.1:9999
|
||||
├── Paths
|
||||
| ├── Data directory: ./data
|
||||
| └── Config file: data/config.json
|
||||
| ├── Config file: data/config.json
|
||||
| └── Umask: system default
|
||||
├── Backup: disabled
|
||||
└── Logger
|
||||
├── Level: INFO
|
||||
|
||||
@@ -7,5 +7,5 @@ import (
|
||||
|
||||
type PersistentDatabase interface {
|
||||
Close() error
|
||||
StoreNewIP(domain, host string, ip netip.Addr, t time.Time) (err error)
|
||||
StoreNewIP(domain, owner string, ip netip.Addr, t time.Time) (err error)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ func (db *Database) Update(id uint, record records.Record) (err error) {
|
||||
if newCount > currentCount {
|
||||
if err := db.persistentDB.StoreNewIP(
|
||||
record.Provider.Domain(),
|
||||
record.Provider.Host(),
|
||||
record.Provider.Owner(),
|
||||
record.History.GetCurrentIP(),
|
||||
record.History.GetSuccessTime(),
|
||||
); err != nil {
|
||||
|
||||
@@ -3,7 +3,7 @@ package models
|
||||
type BuildInformation struct {
|
||||
Version string `json:"version"`
|
||||
Commit string `json:"commit"`
|
||||
Date string `json:"buildDate"`
|
||||
Created string `json:"buildDate"`
|
||||
}
|
||||
|
||||
func (b BuildInformation) VersionString() string {
|
||||
|
||||
@@ -10,7 +10,7 @@ type HTMLData struct {
|
||||
// It is exported so that the HTML template engine can render it.
|
||||
type HTMLRow struct {
|
||||
Domain string
|
||||
Host string
|
||||
Owner string
|
||||
Provider string
|
||||
IPVersion string
|
||||
Status string
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
package models
|
||||
|
||||
type DomainHost struct {
|
||||
Domain string
|
||||
Host string
|
||||
}
|
||||
@@ -10,22 +10,25 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/chmike/domain"
|
||||
"github.com/qdm12/ddns-updater/internal/models"
|
||||
"github.com/qdm12/ddns-updater/internal/provider"
|
||||
"github.com/qdm12/ddns-updater/internal/provider/constants"
|
||||
"github.com/qdm12/ddns-updater/internal/provider/utils"
|
||||
"github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
type commonSettings struct {
|
||||
Provider string `json:"provider"`
|
||||
Domain string `json:"domain"`
|
||||
Host string `json:"host"`
|
||||
Provider string `json:"provider"`
|
||||
Domain string `json:"domain"`
|
||||
// Host is kept for retro-compatibility and is replaced by Owner.
|
||||
Host string `json:"host,omitempty"`
|
||||
// Owner is kept for retro-compatibility and is determined from the
|
||||
// Domain field.
|
||||
Owner string `json:"owner,omitempty"`
|
||||
IPVersion string `json:"ip_version"`
|
||||
IPv6Suffix netip.Prefix `json:"ipv6_suffix,omitempty"`
|
||||
// Retro values for warnings
|
||||
IPMethod *string `json:"ip_method,omitempty"`
|
||||
Delay *uint64 `json:"delay,omitempty"`
|
||||
ProviderIP *bool `json:"provider_ip,omitempty"`
|
||||
}
|
||||
|
||||
// JSONProviders obtain the update settings from the JSON content,
|
||||
@@ -54,9 +57,8 @@ func (r *Reader) getProvidersFromFile(filePath string) (
|
||||
|
||||
r.logger.Info("file not found, creating an empty settings file")
|
||||
|
||||
const mode = fs.FileMode(0600)
|
||||
|
||||
err = r.writeFile(filePath, []byte(`{}`), mode)
|
||||
const filePerm = fs.FileMode(0o666)
|
||||
err = r.writeFile(filePath, []byte(`{}`), filePerm)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%w: %w", errWriteConfigToFile, err)
|
||||
}
|
||||
@@ -90,8 +92,8 @@ func (r *Reader) getProvidersFromEnv(filePath string) (
|
||||
if err != nil {
|
||||
return providers, warnings, fmt.Errorf("%w: %w", errWriteConfigToFile, err)
|
||||
}
|
||||
const mode = fs.FileMode(0600)
|
||||
err = r.writeFile(filePath, buffer.Bytes(), mode)
|
||||
const filePerm = fs.FileMode(0o666)
|
||||
err = r.writeFile(filePath, buffer.Bytes(), filePerm)
|
||||
if err != nil {
|
||||
return providers, warnings, fmt.Errorf("%w: %w", errWriteConfigToFile, err)
|
||||
}
|
||||
@@ -141,7 +143,6 @@ func extractAllSettings(jsonBytes []byte) (
|
||||
|
||||
var (
|
||||
ErrProviderNoLongerSupported = errors.New("provider no longer supported")
|
||||
ErrDomainBlank = errors.New("domain cannot be blank for provider")
|
||||
)
|
||||
|
||||
func makeSettingsFromObject(common commonSettings, rawSettings json.RawMessage,
|
||||
@@ -151,34 +152,29 @@ func makeSettingsFromObject(common commonSettings, rawSettings json.RawMessage,
|
||||
return nil, nil, fmt.Errorf("%w: %s", ErrProviderNoLongerSupported, common.Provider)
|
||||
}
|
||||
|
||||
if common.Domain == "" && (common.Provider != "duckdns" && common.Provider != "goip") {
|
||||
return nil, nil, fmt.Errorf("%w: for provider %s", ErrDomainBlank, common.Provider)
|
||||
if common.Owner == "" { // retro compatibility
|
||||
common.Owner = common.Host
|
||||
}
|
||||
|
||||
if common.Domain != "" {
|
||||
err = domain.Check(common.Domain)
|
||||
var domain string
|
||||
var owners []string
|
||||
if common.Owner != "" { // retro compatibility
|
||||
owners = strings.Split(common.Owner, ",")
|
||||
domain = common.Domain // single domain only
|
||||
domains := make([]string, len(owners))
|
||||
for i, owner := range owners {
|
||||
domains[i] = utils.BuildURLQueryHostname(owner, common.Domain)
|
||||
}
|
||||
warnings = append(warnings,
|
||||
fmt.Sprintf("you can specify the owner %q directly in the domain field as %q",
|
||||
common.Owner, strings.Join(domains, ",")))
|
||||
} else { // extract owner(s) from domain(s)
|
||||
domain, owners, err = extractFromDomainField(common.Domain)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("validating domain: %w", err)
|
||||
return nil, nil, fmt.Errorf("extracting owners from domains: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
providerName := models.Provider(common.Provider)
|
||||
if providerName == constants.DuckDNS { // only hosts, no domain
|
||||
if common.Domain != "" { // retro compatibility
|
||||
if common.Host == "" {
|
||||
common.Host = strings.TrimSuffix(common.Domain, ".duckdns.org")
|
||||
warnings = append(warnings,
|
||||
fmt.Sprintf("DuckDNS record should have %q specified as host instead of %q as domain",
|
||||
common.Host, common.Domain))
|
||||
} else {
|
||||
warnings = append(warnings,
|
||||
fmt.Sprintf("ignoring domain %q because host %q is specified for DuckDNS record",
|
||||
common.Domain, common.Host))
|
||||
}
|
||||
}
|
||||
}
|
||||
hosts := strings.Split(common.Host, ",")
|
||||
|
||||
if common.IPVersion == "" {
|
||||
common.IPVersion = ipversion.IP4or6.String()
|
||||
}
|
||||
@@ -198,14 +194,50 @@ func makeSettingsFromObject(common commonSettings, rawSettings json.RawMessage,
|
||||
ipv6Suffix, ipVersion))
|
||||
}
|
||||
|
||||
providers = make([]provider.Provider, len(hosts))
|
||||
for i, host := range hosts {
|
||||
host = strings.TrimSpace(host)
|
||||
providers[i], err = provider.New(providerName, rawSettings, common.Domain,
|
||||
host, ipVersion, ipv6Suffix)
|
||||
if common.ProviderIP != nil {
|
||||
warning := fmt.Sprintf("for domain %s and ip version %s: "+
|
||||
`the field "provider_ip" is deprecated and no longer used`,
|
||||
domain, ipVersion)
|
||||
warnings = append(warnings, warning)
|
||||
}
|
||||
|
||||
providerName := models.Provider(common.Provider)
|
||||
providers = make([]provider.Provider, len(owners))
|
||||
for i, owner := range owners {
|
||||
owner = strings.TrimSpace(owner)
|
||||
providers[i], err = provider.New(providerName, rawSettings, domain,
|
||||
owner, ipVersion, ipv6Suffix)
|
||||
if err != nil {
|
||||
return nil, warnings, err
|
||||
}
|
||||
}
|
||||
return providers, warnings, nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrMultipleDomainsSpecified = errors.New("multiple domains specified")
|
||||
)
|
||||
|
||||
func extractFromDomainField(domainField string) (domainRegistered string,
|
||||
owners []string, err error) {
|
||||
domains := strings.Split(domainField, ",")
|
||||
owners = make([]string, len(domains))
|
||||
for i, domain := range domains {
|
||||
newDomainRegistered, err := publicsuffix.EffectiveTLDPlusOne(domain)
|
||||
switch {
|
||||
case err != nil:
|
||||
return "", nil, fmt.Errorf("extracting effective TLD+1: %w", err)
|
||||
case domainRegistered == "":
|
||||
domainRegistered = newDomainRegistered
|
||||
case domainRegistered != newDomainRegistered:
|
||||
return "", nil, fmt.Errorf("%w: %q and %q",
|
||||
ErrMultipleDomainsSpecified, domainRegistered, newDomainRegistered)
|
||||
}
|
||||
if domain == domainRegistered {
|
||||
owners[i] = "@"
|
||||
continue
|
||||
}
|
||||
owners[i] = strings.TrimSuffix(domain, "."+domainRegistered)
|
||||
}
|
||||
return domainRegistered, owners, nil
|
||||
}
|
||||
|
||||
76
internal/params/json_test.go
Normal file
76
internal/params/json_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_extractFromDomainField(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
domainField string
|
||||
domainRegistered string
|
||||
owners []string
|
||||
errWrapped error
|
||||
errMessage string
|
||||
}{
|
||||
"root_domain": {
|
||||
domainField: "example.com",
|
||||
domainRegistered: "example.com",
|
||||
owners: []string{"@"},
|
||||
},
|
||||
"subdomain": {
|
||||
domainField: "abc.example.com",
|
||||
domainRegistered: "example.com",
|
||||
owners: []string{"abc"},
|
||||
},
|
||||
"two_dots_tld": {
|
||||
domainField: "abc.example.co.uk",
|
||||
domainRegistered: "example.co.uk",
|
||||
owners: []string{"abc"},
|
||||
},
|
||||
"wildcard": {
|
||||
domainField: "*.example.com",
|
||||
domainRegistered: "example.com",
|
||||
owners: []string{"*"},
|
||||
},
|
||||
"multiple": {
|
||||
domainField: "*.example.com,example.com",
|
||||
domainRegistered: "example.com",
|
||||
owners: []string{"*", "@"},
|
||||
},
|
||||
"different_domains": {
|
||||
domainField: "*.example.com,abc.something.com",
|
||||
errWrapped: ErrMultipleDomainsSpecified,
|
||||
errMessage: "multiple domains specified: \"example.com\" and \"something.com\"",
|
||||
},
|
||||
"goip.de": {
|
||||
domainField: "my.domain.goip.de",
|
||||
domainRegistered: "domain.goip.de",
|
||||
owners: []string{"my"},
|
||||
},
|
||||
"duckdns.org": {
|
||||
domainField: "my.domain.duckdns.org",
|
||||
domainRegistered: "domain.duckdns.org",
|
||||
owners: []string{"my"},
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
domainRegistered, owners, err := extractFromDomainField(testCase.domainField)
|
||||
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
if testCase.errWrapped != nil {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
}
|
||||
assert.Equal(t, testCase.domainRegistered, domainRegistered)
|
||||
assert.Equal(t, testCase.owners, owners)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
@@ -16,12 +15,12 @@ import (
|
||||
type Database struct {
|
||||
data dataModel
|
||||
filepath string
|
||||
sync.RWMutex
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (db *Database) Close() error {
|
||||
db.Lock() // ensure a write operation finishes
|
||||
defer db.Unlock()
|
||||
db.mutex.Lock() // ensure a write operation finishes
|
||||
defer db.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -34,8 +33,8 @@ func NewDatabase(dataDir string) (*Database, error) {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, fmt.Errorf("reading file: %w", err)
|
||||
}
|
||||
const perm fs.FileMode = 0700
|
||||
err = os.MkdirAll(filepath.Dir(filePath), perm)
|
||||
const dirPerm = os.FileMode(0o777)
|
||||
err = os.MkdirAll(filepath.Dir(filePath), dirPerm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating data directory: %w", err)
|
||||
}
|
||||
@@ -64,6 +63,14 @@ func NewDatabase(dataDir string) (*Database, error) {
|
||||
return nil, fmt.Errorf("closing database file: %w", err)
|
||||
}
|
||||
|
||||
// Migration from older database using "host" instead of "owner".
|
||||
for i := range data.Records {
|
||||
if data.Records[i].Owner == "" {
|
||||
data.Records[i].Owner = data.Records[i].Host
|
||||
data.Records[i].Host = ""
|
||||
}
|
||||
}
|
||||
|
||||
err = checkData(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s validation error: %w", filePath, err)
|
||||
@@ -77,7 +84,7 @@ func NewDatabase(dataDir string) (*Database, error) {
|
||||
|
||||
var (
|
||||
ErrDomainEmpty = errors.New("domain is empty")
|
||||
ErrHostIsEmpty = errors.New("host is empty")
|
||||
ErrOwnerNotSet = errors.New("owner is not set")
|
||||
ErrIPRecordsMisordered = errors.New("IP records are not ordered correctly by time")
|
||||
ErrIPEmpty = errors.New("IP is empty")
|
||||
ErrIPTimeEmpty = errors.New("time of IP is empty")
|
||||
@@ -89,16 +96,16 @@ func checkData(data dataModel) (err error) {
|
||||
case record.Domain == "":
|
||||
return fmt.Errorf("%w: for record %d of %d", ErrDomainEmpty,
|
||||
i+1, len(data.Records))
|
||||
case record.Host == "":
|
||||
case record.Owner == "":
|
||||
return fmt.Errorf("%w: for record %d of %d with domain %s",
|
||||
ErrHostIsEmpty, i+1, len(data.Records), record.Domain)
|
||||
ErrOwnerNotSet, i+1, len(data.Records), record.Domain)
|
||||
}
|
||||
|
||||
err = checkHistoryEvents(record.Events)
|
||||
if err != nil {
|
||||
return fmt.Errorf("for record %d of %d with domain %s and host %s: "+
|
||||
return fmt.Errorf("for record %d of %d with domain %s and owner %s: "+
|
||||
"history events: %w", i+1, len(data.Records),
|
||||
record.Domain, record.Host, err)
|
||||
record.Domain, record.Owner, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -126,8 +133,8 @@ func checkHistoryEvents(events []models.HistoryEvent) (err error) {
|
||||
}
|
||||
|
||||
func (db *Database) write() error {
|
||||
const createPerms fs.FileMode = 0600
|
||||
file, err := os.OpenFile(db.filepath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, createPerms)
|
||||
const filePerm = os.FileMode(0o666)
|
||||
file, err := os.OpenFile(db.filepath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, filePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening file: %w", err)
|
||||
}
|
||||
|
||||
@@ -11,8 +11,10 @@ type dataModel struct {
|
||||
}
|
||||
|
||||
type record struct {
|
||||
Domain string `json:"domain"`
|
||||
Host string `json:"host"`
|
||||
Domain string `json:"domain"`
|
||||
// Host is kept for retro-compatibility and is replaced by Owner.
|
||||
Host string `json:"host,omitempty"`
|
||||
Owner string `json:"owner"`
|
||||
Events []models.HistoryEvent `json:"ips"`
|
||||
}
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@ import (
|
||||
"github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
|
||||
)
|
||||
|
||||
// StoreNewIP stores a new IP address for a certain domain and host.
|
||||
func (db *Database) StoreNewIP(domain, host string, ip netip.Addr, t time.Time) (err error) {
|
||||
db.Lock()
|
||||
defer db.Unlock()
|
||||
// StoreNewIP stores a new IP address for a certain domain and owner.
|
||||
func (db *Database) StoreNewIP(domain, owner string, ip netip.Addr, t time.Time) (err error) {
|
||||
db.mutex.Lock()
|
||||
defer db.mutex.Unlock()
|
||||
|
||||
targetIndex := -1
|
||||
for i, record := range db.data.Records {
|
||||
if record.Domain == domain && record.Host == host {
|
||||
if record.Domain == domain && record.Owner == owner {
|
||||
targetIndex = i
|
||||
break
|
||||
}
|
||||
@@ -26,7 +26,7 @@ func (db *Database) StoreNewIP(domain, host string, ip netip.Addr, t time.Time)
|
||||
if recordNotFound {
|
||||
db.data.Records = append(db.data.Records, record{
|
||||
Domain: domain,
|
||||
Host: host,
|
||||
Owner: owner,
|
||||
})
|
||||
targetIndex = len(db.data.Records) - 1
|
||||
}
|
||||
@@ -39,14 +39,14 @@ func (db *Database) StoreNewIP(domain, host string, ip netip.Addr, t time.Time)
|
||||
return db.write()
|
||||
}
|
||||
|
||||
// GetEvents gets all the IP addresses history for a certain domain, host and
|
||||
// GetEvents gets all the IP addresses history for a certain domain, owner and
|
||||
// IP version, in the order from oldest to newest.
|
||||
func (db *Database) GetEvents(domain, host string,
|
||||
func (db *Database) GetEvents(domain, owner string,
|
||||
ipVersion ipversion.IPVersion) (events []models.HistoryEvent, err error) {
|
||||
db.RLock()
|
||||
defer db.RUnlock()
|
||||
db.mutex.RLock()
|
||||
defer db.mutex.RUnlock()
|
||||
for _, record := range db.data.Records {
|
||||
if record.Domain == domain && record.Host == host {
|
||||
if record.Domain == domain && record.Owner == owner {
|
||||
return filterEvents(record.Events, ipVersion), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
package constants
|
||||
|
||||
const (
|
||||
A = "A"
|
||||
AAAA = "AAAA"
|
||||
)
|
||||
8
internal/provider/constants/recordtypes.go
Normal file
8
internal/provider/constants/recordtypes.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package constants
|
||||
|
||||
const (
|
||||
A = "A"
|
||||
AAAA = "AAAA"
|
||||
CNAME = "CNAME"
|
||||
ALIAS = "ALIAS"
|
||||
)
|
||||
@@ -7,7 +7,7 @@ var (
|
||||
ErrAuth = errors.New("bad authentication")
|
||||
ErrBadRequest = errors.New("bad request sent")
|
||||
ErrBannedAbuse = errors.New("banned due to abuse")
|
||||
ErrBannedUserAgent = errors.New("user agend is banned")
|
||||
ErrBannedUserAgent = errors.New("user agent is banned")
|
||||
ErrConflictingRecord = errors.New("conflicting record")
|
||||
ErrDNSServerSide = errors.New("server side DNS error")
|
||||
ErrDomainDisabled = errors.New("record disabled")
|
||||
|
||||
@@ -18,14 +18,12 @@ var (
|
||||
ErrEmailNotValid = errors.New("email address is not valid")
|
||||
ErrGCPProjectNotSet = errors.New("GCP project is not set")
|
||||
ErrDomainNotValid = errors.New("domain is not valid")
|
||||
ErrHostNotSet = errors.New("host is not set")
|
||||
ErrHostOnlySubdomain = errors.New("host can only be a subdomain")
|
||||
ErrHostWildcard = errors.New(`host cannot be a "*"`)
|
||||
ErrOwnerNotSet = errors.New("owner is not set")
|
||||
ErrOwnerWildcard = errors.New(`owner cannot be "*"`)
|
||||
ErrIPv4KeyNotSet = errors.New("IPv4 key is not set")
|
||||
ErrIPv6KeyNotSet = errors.New("IPv6 key is not set")
|
||||
ErrKeyNotSet = errors.New("key is not set")
|
||||
ErrKeyNotValid = errors.New("key is not valid")
|
||||
ErrNameNotSet = errors.New("name is not set")
|
||||
ErrPasswordNotSet = errors.New("password is not set")
|
||||
ErrPasswordNotValid = errors.New("password is not valid")
|
||||
ErrSecretKeyNotSet = errors.New("secret key is not set")
|
||||
|
||||
@@ -63,7 +63,7 @@ import (
|
||||
type Provider interface {
|
||||
String() string
|
||||
Domain() string
|
||||
Host() string
|
||||
Owner() string
|
||||
BuildDomainName() string
|
||||
HTML() models.HTMLRow
|
||||
Proxied() bool
|
||||
@@ -75,103 +75,103 @@ type Provider interface {
|
||||
var ErrProviderUnknown = errors.New("unknown provider")
|
||||
|
||||
//nolint:gocyclo
|
||||
func New(providerName models.Provider, data json.RawMessage, domain, host string, //nolint:ireturn
|
||||
func New(providerName models.Provider, data json.RawMessage, domain, owner string, //nolint:ireturn
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (provider Provider, err error) {
|
||||
switch providerName {
|
||||
case constants.Aliyun:
|
||||
return aliyun.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return aliyun.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.AllInkl:
|
||||
return allinkl.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return allinkl.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Changeip:
|
||||
return changeip.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return changeip.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Cloudflare:
|
||||
return cloudflare.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return cloudflare.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Custom:
|
||||
return custom.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return custom.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Dd24:
|
||||
return dd24.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return dd24.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.DdnssDe:
|
||||
return ddnss.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return ddnss.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.DeSEC:
|
||||
return desec.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return desec.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.DigitalOcean:
|
||||
return digitalocean.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return digitalocean.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.DNSOMatic:
|
||||
return dnsomatic.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return dnsomatic.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.DNSPod:
|
||||
return dnspod.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return dnspod.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.DonDominio:
|
||||
return dondominio.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return dondominio.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Dreamhost:
|
||||
return dreamhost.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return dreamhost.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.DuckDNS:
|
||||
return duckdns.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return duckdns.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Dyn:
|
||||
return dyn.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return dyn.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Dynu:
|
||||
return dynu.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return dynu.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.DynV6:
|
||||
return dynv6.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return dynv6.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.EasyDNS:
|
||||
return easydns.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return easydns.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Example:
|
||||
return example.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return example.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.FreeDNS:
|
||||
return freedns.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return freedns.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Gandi:
|
||||
return gandi.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return gandi.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.GCP:
|
||||
return gcp.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return gcp.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.GoDaddy:
|
||||
return godaddy.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return godaddy.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.GoIP:
|
||||
return goip.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return goip.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.HE:
|
||||
return he.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return he.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Hetzner:
|
||||
return hetzner.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return hetzner.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Infomaniak:
|
||||
return infomaniak.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return infomaniak.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.INWX:
|
||||
return inwx.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return inwx.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Ionos:
|
||||
return ionos.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return ionos.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Linode:
|
||||
return linode.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return linode.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.LuaDNS:
|
||||
return luadns.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return luadns.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Namecheap:
|
||||
return namecheap.New(data, domain, host)
|
||||
return namecheap.New(data, domain, owner)
|
||||
case constants.NameCom:
|
||||
return namecom.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return namecom.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Netcup:
|
||||
return netcup.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return netcup.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Njalla:
|
||||
return njalla.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return njalla.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.NoIP:
|
||||
return noip.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return noip.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.NowDNS:
|
||||
return nowdns.New(data, domain, ipVersion, ipv6Suffix)
|
||||
case constants.OpenDNS:
|
||||
return opendns.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return opendns.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.OVH:
|
||||
return ovh.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return ovh.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Porkbun:
|
||||
return porkbun.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return porkbun.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Route53:
|
||||
return route53.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return route53.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.SelfhostDe:
|
||||
return selfhostde.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return selfhostde.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Servercow:
|
||||
return servercow.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return servercow.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Spdyn:
|
||||
return spdyn.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return spdyn.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Strato:
|
||||
return strato.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return strato.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Variomedia:
|
||||
return variomedia.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return variomedia.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Zoneedit:
|
||||
return zoneedit.New(data, domain, host, ipVersion, ipv6Suffix)
|
||||
return zoneedit.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", ErrProviderUnknown, providerName)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func (p *Provider) createRecord(ctx context.Context,
|
||||
values := newURLValues(p.accessKeyID)
|
||||
values.Set("Action", "AddDomainRecord")
|
||||
values.Set("DomainName", p.domain)
|
||||
values.Set("RR", p.host)
|
||||
values.Set("RR", p.owner)
|
||||
values.Set("Type", recordType)
|
||||
values.Set("Value", ip.String())
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ func (p *Provider) getRecordID(ctx context.Context, client *http.Client,
|
||||
values := newURLValues(p.accessKeyID)
|
||||
values.Set("Action", "DescribeDomainRecords")
|
||||
values.Set("DomainName", p.domain)
|
||||
values.Set("RRKeyWord", p.host)
|
||||
values.Set("RRKeyWord", p.owner)
|
||||
values.Set("Type", recordType)
|
||||
|
||||
sign(http.MethodGet, values, p.accessSecret)
|
||||
@@ -57,7 +57,7 @@ func (p *Provider) getRecordID(ctx context.Context, client *http.Client,
|
||||
if err != nil || data.Code != "InvalidDomainName.NoExist" {
|
||||
return "", fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrHTTPStatusNotValid, response.StatusCode,
|
||||
utils.BodyToSingleLine(response.Body))
|
||||
utils.ToSingleLine(string(bodyBytes)))
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("%w", errors.ErrRecordNotFound)
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
type Provider struct {
|
||||
domain string
|
||||
host string
|
||||
owner string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
accessKeyID string
|
||||
@@ -25,7 +25,7 @@ type Provider struct {
|
||||
region string
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, domain, host string,
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
p *Provider, err error) {
|
||||
extraSettings := struct {
|
||||
@@ -37,45 +37,52 @@ func New(data json.RawMessage, domain, host string,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p = &Provider{
|
||||
region := "cn-hangzhou"
|
||||
if extraSettings.Region != "" {
|
||||
region = extraSettings.Region
|
||||
}
|
||||
|
||||
err = validateSettings(domain, extraSettings.AccessKeyID, extraSettings.AccessSecret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
host: host,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
accessKeyID: extraSettings.AccessKeyID,
|
||||
accessSecret: extraSettings.AccessSecret,
|
||||
region: "cn-hangzhou",
|
||||
}
|
||||
if extraSettings.Region != "" {
|
||||
p.region = extraSettings.Region
|
||||
}
|
||||
err = p.isValid()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
region: region,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) isValid() error {
|
||||
func validateSettings(domain, accessKeyID, accessSecret string) (err error) {
|
||||
err = utils.CheckDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.accessKeyID == "":
|
||||
case accessKeyID == "":
|
||||
return fmt.Errorf("%w", errors.ErrAccessKeyIDNotSet)
|
||||
case p.accessSecret == "":
|
||||
case accessSecret == "":
|
||||
return fmt.Errorf("%w", errors.ErrAccessKeySecretNotSet)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) String() string {
|
||||
return utils.ToString(p.domain, p.host, constants.Aliyun, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.Aliyun, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -91,13 +98,13 @@ func (p *Provider) Proxied() bool {
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, p.domain)
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
Provider: "<a href=\"https://www.aliyun.com/\">Aliyun</a>",
|
||||
IPVersion: p.ipVersion.String(),
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func (p *Provider) updateRecord(ctx context.Context, client *http.Client,
|
||||
values := newURLValues(p.accessKeyID)
|
||||
values.Set("Action", "UpdateDomainRecord")
|
||||
values.Set("RecordId", recordID)
|
||||
values.Set("RR", p.host)
|
||||
values.Set("RR", p.owner)
|
||||
values.Set("Type", recordType)
|
||||
values.Set("Value", ip.String())
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
@@ -20,65 +19,68 @@ import (
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
domain string
|
||||
host string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
username string
|
||||
password string
|
||||
useProviderIP bool
|
||||
domain string
|
||||
owner string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, domain, host string,
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
p *Provider, err error) {
|
||||
extraSettings := struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
UseProviderIP bool `json:"provider_ip"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}{}
|
||||
err = json.Unmarshal(data, &extraSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p = &Provider{
|
||||
domain: domain,
|
||||
host: host,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
username: extraSettings.Username,
|
||||
password: extraSettings.Password,
|
||||
useProviderIP: extraSettings.UseProviderIP,
|
||||
}
|
||||
err = p.isValid()
|
||||
|
||||
err = validateSettings(domain, owner, extraSettings.Username, extraSettings.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
return p, nil
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
username: extraSettings.Username,
|
||||
password: extraSettings.Password,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) isValid() error {
|
||||
func validateSettings(domain, owner, username, password string) (err error) {
|
||||
err = utils.CheckDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.username == "":
|
||||
case owner == "*":
|
||||
return fmt.Errorf("%w", errors.ErrOwnerWildcard)
|
||||
case username == "":
|
||||
return fmt.Errorf("%w", errors.ErrUsernameNotSet)
|
||||
case p.password == "":
|
||||
case password == "":
|
||||
return fmt.Errorf("%w", errors.ErrPasswordNotSet)
|
||||
case p.host == "*":
|
||||
return fmt.Errorf("%w", errors.ErrHostWildcard)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) String() string {
|
||||
return utils.ToString(p.domain, p.host, constants.AllInkl, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.AllInkl, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -94,13 +96,13 @@ func (p *Provider) Proxied() bool {
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, p.domain)
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
Provider: "<a href=\"https://all-inkl.com/\">ALL-INKL.com</a>",
|
||||
IPVersion: p.ipVersion.String(),
|
||||
}
|
||||
@@ -114,14 +116,11 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
User: url.UserPassword(p.username, p.password),
|
||||
}
|
||||
values := url.Values{}
|
||||
values.Set("host", utils.BuildURLQueryHostname(p.host, p.domain))
|
||||
useProviderIP := p.useProviderIP && (ip.Is4() || !p.ipv6Suffix.IsValid())
|
||||
if !useProviderIP {
|
||||
if ip.Is6() {
|
||||
values.Set("myip6", ip.String())
|
||||
} else {
|
||||
values.Set("myip", ip.String())
|
||||
}
|
||||
values.Set("host", utils.BuildURLQueryHostname(p.owner, p.domain))
|
||||
if ip.Is6() {
|
||||
values.Set("myip6", ip.String())
|
||||
} else {
|
||||
values.Set("myip", ip.String())
|
||||
}
|
||||
u.RawQuery = values.Encode()
|
||||
|
||||
@@ -137,11 +136,10 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
s, err := utils.ReadAndCleanBody(response.Body)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("reading response body: %w", err)
|
||||
return netip.Addr{}, fmt.Errorf("reading response: %w", err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s", errors.ErrHTTPStatusNotValid,
|
||||
@@ -179,7 +177,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
}
|
||||
|
||||
newIP = ips[0]
|
||||
if !useProviderIP && ip.Compare(newIP) != 0 {
|
||||
if ip.Compare(newIP) != 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
|
||||
@@ -17,68 +17,69 @@ import (
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
domain string
|
||||
host string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
username string
|
||||
password string
|
||||
useProviderIP bool
|
||||
domain string
|
||||
owner string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
type settings struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
UseProviderIP bool `json:"provider_ip"`
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, domain, host string,
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
p *Provider, err error) {
|
||||
var providerSpecificSettings settings
|
||||
var providerSpecificSettings struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
err = json.Unmarshal(data, &providerSpecificSettings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("json decoding provider specific settings: %w", err)
|
||||
}
|
||||
err = validateSettings(domain, host, providerSpecificSettings)
|
||||
|
||||
err = validateSettings(domain, owner,
|
||||
providerSpecificSettings.Username, providerSpecificSettings.Password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating settings: %w", err)
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
host: host,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
username: providerSpecificSettings.Username,
|
||||
password: providerSpecificSettings.Password,
|
||||
useProviderIP: providerSpecificSettings.UseProviderIP,
|
||||
domain: domain,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
username: providerSpecificSettings.Username,
|
||||
password: providerSpecificSettings.Password,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func validateSettings(domain, host string, settings settings) error {
|
||||
func validateSettings(domain, owner, username, password string) (err error) {
|
||||
err = utils.CheckDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case domain == "":
|
||||
return fmt.Errorf("%w", errors.ErrDomainNotSet)
|
||||
case host == "":
|
||||
return fmt.Errorf("%w", errors.ErrHostNotSet)
|
||||
case settings.Username == "":
|
||||
case owner == "":
|
||||
return fmt.Errorf("%w", errors.ErrOwnerNotSet)
|
||||
case username == "":
|
||||
return fmt.Errorf("%w", errors.ErrUsernameNotSet)
|
||||
case settings.Password == "":
|
||||
case password == "":
|
||||
return fmt.Errorf("%w", errors.ErrPasswordNotSet)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) String() string {
|
||||
return utils.ToString(p.domain, p.host, constants.Changeip, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.Changeip, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -94,13 +95,13 @@ func (p *Provider) Proxied() bool {
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, p.domain)
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
Provider: "<a href=\"https://www.changeip.com\">changeip.com</a>",
|
||||
IPVersion: p.ipVersion.String(),
|
||||
}
|
||||
@@ -113,11 +114,8 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
Path: "/nic/update",
|
||||
}
|
||||
values := url.Values{}
|
||||
values.Set("hostname", utils.BuildURLQueryHostname(p.host, p.domain))
|
||||
useProviderIP := p.useProviderIP && (ip.Is4() || !p.ipv6Suffix.IsValid())
|
||||
if !useProviderIP {
|
||||
values.Set("ip", ip.String())
|
||||
}
|
||||
values.Set("hostname", utils.BuildURLQueryHostname(p.owner, p.domain))
|
||||
values.Set("ip", ip.String())
|
||||
u.RawQuery = values.Encode()
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
type Provider struct {
|
||||
domain string
|
||||
host string
|
||||
owner string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
key string
|
||||
@@ -34,7 +34,7 @@ type Provider struct {
|
||||
ttl uint32
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, domain, host string,
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
p *Provider, err error) {
|
||||
extraSettings := struct {
|
||||
@@ -50,9 +50,16 @@ func New(data json.RawMessage, domain, host string,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p = &Provider{
|
||||
|
||||
err = validateSettings(domain, extraSettings.Email, extraSettings.Key, extraSettings.UserServiceKey,
|
||||
extraSettings.ZoneIdentifier, extraSettings.TTL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
host: host,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
key: extraSettings.Key,
|
||||
@@ -62,12 +69,7 @@ func New(data json.RawMessage, domain, host string,
|
||||
zoneIdentifier: extraSettings.ZoneIdentifier,
|
||||
proxied: extraSettings.Proxied,
|
||||
ttl: extraSettings.TTL,
|
||||
}
|
||||
err = p.isValid()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -76,43 +78,48 @@ var (
|
||||
regexEmail = regexp.MustCompile(`[a-zA-Z0-9-_.+]+@[a-zA-Z0-9-_.]+\.[a-zA-Z]{2,10}`)
|
||||
)
|
||||
|
||||
func (p *Provider) isValid() error {
|
||||
func validateSettings(domain, email, key, userServiceKey, zoneIdentifier string, ttl uint32) (err error) {
|
||||
err = utils.CheckDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.email != "", p.key != "": // email and key must be provided
|
||||
case email != "", key != "": // email and key must be provided
|
||||
switch {
|
||||
case !keyRegex.MatchString(p.key):
|
||||
case !keyRegex.MatchString(key):
|
||||
return fmt.Errorf("%w: key %q does not match regex %q",
|
||||
errors.ErrKeyNotValid, p.key, keyRegex)
|
||||
case !regexEmail.MatchString(p.email):
|
||||
errors.ErrKeyNotValid, key, keyRegex)
|
||||
case !regexEmail.MatchString(email):
|
||||
return fmt.Errorf("%w: email %q does not match regex %q",
|
||||
errors.ErrEmailNotValid, p.email, regexEmail)
|
||||
errors.ErrEmailNotValid, email, regexEmail)
|
||||
}
|
||||
case p.userServiceKey != "": // only user service key
|
||||
if !userServiceKeyRegex.MatchString(p.userServiceKey) {
|
||||
case userServiceKey != "": // only user service key
|
||||
if !userServiceKeyRegex.MatchString(userServiceKey) {
|
||||
return fmt.Errorf("%w: %q does not match regex %q",
|
||||
errors.ErrUserServiceKeyNotValid, p.userServiceKey, userServiceKeyRegex)
|
||||
errors.ErrUserServiceKeyNotValid, userServiceKey, userServiceKeyRegex)
|
||||
}
|
||||
default: // constants.API token only
|
||||
}
|
||||
switch {
|
||||
case p.zoneIdentifier == "":
|
||||
case zoneIdentifier == "":
|
||||
return fmt.Errorf("%w", errors.ErrZoneIdentifierNotSet)
|
||||
case p.ttl == 0:
|
||||
case ttl == 0:
|
||||
return fmt.Errorf("%w", errors.ErrTTLNotSet)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) String() string {
|
||||
return utils.ToString(p.domain, p.host, constants.Cloudflare, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.Cloudflare, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -128,13 +135,13 @@ func (p *Provider) Proxied() bool {
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, p.domain)
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
Provider: "<a href=\"https://www.cloudflare.com\">Cloudflare</a>",
|
||||
IPVersion: p.ipVersion.String(),
|
||||
}
|
||||
@@ -172,7 +179,7 @@ func (p *Provider) getRecordID(ctx context.Context, client *http.Client, newIP n
|
||||
|
||||
values := url.Values{}
|
||||
values.Set("type", recordType)
|
||||
values.Set("name", utils.BuildURLQueryHostname(p.host, p.domain))
|
||||
values.Set("name", utils.BuildURLQueryHostname(p.owner, p.domain))
|
||||
values.Set("page", "1")
|
||||
values.Set("per_page", "1")
|
||||
u.RawQuery = values.Encode()
|
||||
@@ -246,7 +253,7 @@ func (p *Provider) createRecord(ctx context.Context, client *http.Client, ip net
|
||||
TTL uint32 `json:"ttl"`
|
||||
}{
|
||||
Type: recordType,
|
||||
Name: utils.BuildURLQueryHostname(p.host, p.domain),
|
||||
Name: utils.BuildURLQueryHostname(p.owner, p.domain),
|
||||
Content: ip.String(),
|
||||
Proxied: p.proxied,
|
||||
TTL: p.ttl,
|
||||
@@ -338,7 +345,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
TTL uint32 `json:"ttl"`
|
||||
}{
|
||||
Type: recordType,
|
||||
Name: utils.BuildURLQueryHostname(p.host, p.domain),
|
||||
Name: utils.BuildURLQueryHostname(p.owner, p.domain),
|
||||
Content: ip.String(),
|
||||
Proxied: p.proxied,
|
||||
TTL: p.ttl,
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
type Provider struct {
|
||||
domain string
|
||||
host string
|
||||
owner string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
url *url.URL
|
||||
@@ -29,7 +29,7 @@ type Provider struct {
|
||||
successRegex regexp.Regexp
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, domain, host string,
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
p *Provider, err error) {
|
||||
extraSettings := struct {
|
||||
@@ -48,34 +48,40 @@ func New(data json.RawMessage, domain, host string,
|
||||
return nil, fmt.Errorf("parsing URL: %w", err)
|
||||
}
|
||||
|
||||
p = &Provider{
|
||||
err = validateSettings(domain, parsedURL, extraSettings.IPv4Key, extraSettings.IPv6Key, extraSettings.SuccessRegex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
host: host,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
url: parsedURL,
|
||||
ipv4Key: extraSettings.IPv4Key,
|
||||
ipv6Key: extraSettings.IPv6Key,
|
||||
successRegex: extraSettings.SuccessRegex,
|
||||
}
|
||||
err = p.isValid()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) isValid() error {
|
||||
func validateSettings(domain string, url *url.URL,
|
||||
ipv4Key, ipv6Key string, successRegex regexp.Regexp) (err error) {
|
||||
err = utils.CheckDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.url.String() == "":
|
||||
case url.String() == "":
|
||||
return fmt.Errorf("%w", errors.ErrURLNotSet)
|
||||
case p.url.Scheme != "https":
|
||||
return fmt.Errorf("%w: %s", errors.ErrURLNotHTTPS, p.url.Scheme)
|
||||
case p.ipv4Key == "":
|
||||
case url.Scheme != "https":
|
||||
return fmt.Errorf("%w: %s", errors.ErrURLNotHTTPS, url.Scheme)
|
||||
case ipv4Key == "":
|
||||
return fmt.Errorf("%w", errors.ErrIPv4KeyNotSet)
|
||||
case p.ipv6Key == "":
|
||||
case ipv6Key == "":
|
||||
return fmt.Errorf("%w", errors.ErrIPv6KeyNotSet)
|
||||
case p.successRegex.String() == "":
|
||||
case successRegex.String() == "":
|
||||
return fmt.Errorf("%w", errors.ErrSuccessRegexNotSet)
|
||||
default:
|
||||
return nil
|
||||
@@ -83,15 +89,15 @@ func (p *Provider) isValid() error {
|
||||
}
|
||||
|
||||
func (p *Provider) String() string {
|
||||
return utils.ToString(p.domain, p.host, constants.Custom, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.Custom, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -107,14 +113,14 @@ func (p *Provider) Proxied() bool {
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, p.domain)
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
updateHostname := p.url.Hostname()
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
Provider: fmt.Sprintf("<a href=\"https://%s/\">%s: %s</a>",
|
||||
updateHostname, constants.Custom, updateHostname),
|
||||
IPVersion: p.ipVersion.String(),
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
@@ -19,57 +18,60 @@ import (
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
domain string
|
||||
host string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
password string
|
||||
useProviderIP bool
|
||||
domain string
|
||||
owner string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
password string
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, domain, host string,
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
p *Provider, err error) {
|
||||
extraSettings := struct {
|
||||
Password string `json:"password"`
|
||||
UseProviderIP bool `json:"provider_ip"`
|
||||
Password string `json:"password"`
|
||||
}{}
|
||||
err = json.Unmarshal(data, &extraSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p = &Provider{
|
||||
domain: domain,
|
||||
host: host,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
password: extraSettings.Password,
|
||||
useProviderIP: extraSettings.UseProviderIP,
|
||||
}
|
||||
err = p.isValid()
|
||||
|
||||
err = validateSettings(domain, extraSettings.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
return p, nil
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
password: extraSettings.Password,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) isValid() error {
|
||||
if p.password == "" {
|
||||
func validateSettings(domain, password string) (err error) {
|
||||
err = utils.CheckDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||
}
|
||||
|
||||
if password == "" {
|
||||
return fmt.Errorf("%w", errors.ErrPasswordNotSet)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) String() string {
|
||||
return utils.ToString(p.domain, p.host, constants.Dd24, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.Dd24, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -85,13 +87,13 @@ func (p *Provider) Proxied() bool {
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, p.domain)
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
Provider: "<a href=\"https://www.domaindiscount24.com/\">DD24</a>",
|
||||
IPVersion: p.ipVersion.String(),
|
||||
}
|
||||
@@ -107,12 +109,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
values := url.Values{}
|
||||
values.Set("hostname", p.BuildDomainName())
|
||||
values.Set("password", p.password)
|
||||
useProviderIP := p.useProviderIP && (ip.Is4() || !p.ipv6Suffix.IsValid())
|
||||
if useProviderIP {
|
||||
values.Set("ip", "auto")
|
||||
} else {
|
||||
values.Set("ip", ip.String())
|
||||
}
|
||||
values.Set("ip", ip.String())
|
||||
u.RawQuery = values.Encode()
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
@@ -127,19 +124,16 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
s, err := utils.ReadAndCleanBody(response.Body)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("reading response body: %w", err)
|
||||
return netip.Addr{}, fmt.Errorf("reading response: %w", err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
errors.ErrHTTPStatusNotValid, response.StatusCode, utils.ToSingleLine(s))
|
||||
}
|
||||
|
||||
s = strings.ToLower(s)
|
||||
|
||||
switch {
|
||||
case strings.Contains(s, "authorization failed"):
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAuth)
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
@@ -19,68 +18,71 @@ import (
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
domain string
|
||||
host string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
username string
|
||||
password string
|
||||
dualStack bool
|
||||
useProviderIP bool
|
||||
domain string
|
||||
owner string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
username string
|
||||
password string
|
||||
dualStack bool
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, domain, host string,
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
p *Provider, err error) {
|
||||
extraSettings := struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
DualStack bool `json:"dual_stack"`
|
||||
UseProviderIP bool `json:"provider_ip"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
DualStack bool `json:"dual_stack"`
|
||||
}{}
|
||||
err = json.Unmarshal(data, &extraSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p = &Provider{
|
||||
domain: domain,
|
||||
host: host,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
username: extraSettings.Username,
|
||||
password: extraSettings.Password,
|
||||
dualStack: extraSettings.DualStack,
|
||||
useProviderIP: extraSettings.UseProviderIP,
|
||||
}
|
||||
err = p.isValid()
|
||||
|
||||
err = validateSettings(domain, owner, extraSettings.Username, extraSettings.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
return p, nil
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
username: extraSettings.Username,
|
||||
password: extraSettings.Password,
|
||||
dualStack: extraSettings.DualStack,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) isValid() error {
|
||||
func validateSettings(domain, owner, username, password string) (err error) {
|
||||
err = utils.CheckDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.username == "":
|
||||
case owner == "*":
|
||||
return fmt.Errorf("%w", errors.ErrOwnerWildcard)
|
||||
case username == "":
|
||||
return fmt.Errorf("%w", errors.ErrUsernameNotSet)
|
||||
case p.password == "":
|
||||
case password == "":
|
||||
return fmt.Errorf("%w", errors.ErrPasswordNotSet)
|
||||
case p.host == "*":
|
||||
return fmt.Errorf("%w", errors.ErrHostWildcard)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) String() string {
|
||||
return utils.ToString(p.domain, p.host, constants.DdnssDe, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.DdnssDe, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -96,13 +98,13 @@ func (p *Provider) Proxied() bool {
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, p.domain)
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
Provider: "<a href=\"https://ddnss.de/\">DDNSS.de</a>",
|
||||
IPVersion: p.ipVersion.String(),
|
||||
}
|
||||
@@ -117,15 +119,12 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
values := url.Values{}
|
||||
values.Set("user", p.username)
|
||||
values.Set("pwd", p.password)
|
||||
values.Set("host", utils.BuildURLQueryHostname(p.host, p.domain))
|
||||
useProviderIP := p.useProviderIP && (ip.Is4() || !p.ipv6Suffix.IsValid())
|
||||
if !useProviderIP {
|
||||
ipKey := "ip"
|
||||
if p.dualStack && ip.Is6() { // ipv6 update for dual stack
|
||||
ipKey = "ip6"
|
||||
}
|
||||
values.Set(ipKey, ip.String())
|
||||
values.Set("host", utils.BuildURLQueryHostname(p.owner, p.domain))
|
||||
ipKey := "ip"
|
||||
if p.dualStack && ip.Is6() { // ipv6 update for dual stack
|
||||
ipKey = "ip6"
|
||||
}
|
||||
values.Set(ipKey, ip.String())
|
||||
u.RawQuery = values.Encode()
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
@@ -140,11 +139,10 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
s, err := utils.ReadAndCleanBody(response.Body)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("reading response body: %w", err)
|
||||
return netip.Addr{}, fmt.Errorf("reading response: %w", err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
@@ -158,7 +156,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAuth)
|
||||
case strings.Contains(s, constants.Notfqdn):
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrHostnameNotExists)
|
||||
case strings.Contains(s, "Updated 1 hostname"):
|
||||
case strings.Contains(s, "updated 1 hostname"):
|
||||
return ip, nil
|
||||
default:
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, s)
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
@@ -19,60 +18,63 @@ import (
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
domain string
|
||||
host string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
token string
|
||||
useProviderIP bool
|
||||
domain string
|
||||
owner string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
token string
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, domain, host string,
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
p *Provider, err error) {
|
||||
extraSettings := struct {
|
||||
Token string `json:"token"`
|
||||
UseProviderIP bool `json:"provider_ip"`
|
||||
Token string `json:"token"`
|
||||
}{}
|
||||
err = json.Unmarshal(data, &extraSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if host == "" {
|
||||
host = "@" // default
|
||||
if owner == "" {
|
||||
owner = "@" // default
|
||||
}
|
||||
p = &Provider{
|
||||
domain: domain,
|
||||
host: host,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
token: extraSettings.Token,
|
||||
useProviderIP: extraSettings.UseProviderIP,
|
||||
}
|
||||
err = p.isValid()
|
||||
|
||||
err = validateSettings(domain, extraSettings.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
return p, nil
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
token: extraSettings.Token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) isValid() error {
|
||||
if p.token == "" {
|
||||
func validateSettings(domain, token string) (err error) {
|
||||
err = utils.CheckDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
return fmt.Errorf("%w", errors.ErrTokenNotSet)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) String() string {
|
||||
return utils.ToString(p.domain, p.host, constants.DeSEC, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.DeSEC, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -88,13 +90,13 @@ func (p *Provider) Proxied() bool {
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, p.domain)
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
Provider: "<a href=\"https://desec.io/\">deSEC</a>",
|
||||
IPVersion: p.ipVersion.String(),
|
||||
}
|
||||
@@ -108,11 +110,8 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
Path: "/nic/update",
|
||||
}
|
||||
values := url.Values{}
|
||||
values.Set("hostname", utils.BuildURLQueryHostname(p.host, p.domain))
|
||||
useProviderIP := p.useProviderIP && (ip.Is4() || !p.ipv6Suffix.IsValid())
|
||||
if useProviderIP {
|
||||
values.Set("myip", ip.String())
|
||||
}
|
||||
values.Set("hostname", utils.BuildURLQueryHostname(p.owner, p.domain))
|
||||
values.Set("myip", ip.String())
|
||||
u.RawQuery = values.Encode()
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
@@ -127,11 +126,10 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
s, err := utils.ReadAndCleanBody(response.Body)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("reading response body: %w", err)
|
||||
return netip.Addr{}, fmt.Errorf("reading response: %w", err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
switch response.StatusCode {
|
||||
case http.StatusOK:
|
||||
|
||||
@@ -19,13 +19,13 @@ import (
|
||||
|
||||
type Provider struct {
|
||||
domain string
|
||||
host string
|
||||
owner string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
token string
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, domain, host string,
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
p *Provider, err error) {
|
||||
extraSettings := struct {
|
||||
@@ -35,37 +35,43 @@ func New(data json.RawMessage, domain, host string,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p = &Provider{
|
||||
|
||||
err = validateSettings(domain, extraSettings.Token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
host: host,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
token: extraSettings.Token,
|
||||
}
|
||||
err = p.isValid()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) isValid() error {
|
||||
if p.token == "" {
|
||||
func validateSettings(domain, token string) (err error) {
|
||||
err = utils.CheckDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
return fmt.Errorf("%w", errors.ErrTokenNotSet)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) String() string {
|
||||
return utils.ToString(p.domain, p.host, constants.DigitalOcean, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.DigitalOcean, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -81,13 +87,13 @@ func (p *Provider) Proxied() bool {
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, p.domain)
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
Provider: "<a href=\"https://www.digitalocean.com/\">DigitalOcean</a>",
|
||||
IPVersion: p.ipVersion.String(),
|
||||
}
|
||||
@@ -102,7 +108,7 @@ func (p *Provider) setCommonHeaders(request *http.Request) {
|
||||
func (p *Provider) getRecordID(ctx context.Context, recordType string, client *http.Client) (
|
||||
recordID int, err error) {
|
||||
values := url.Values{}
|
||||
values.Set("name", utils.BuildURLQueryHostname(p.host, p.domain))
|
||||
values.Set("name", utils.BuildURLQueryHostname(p.owner, p.domain))
|
||||
values.Set("type", recordType)
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
@@ -173,7 +179,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
Data string `json:"data"`
|
||||
}{
|
||||
Type: recordType,
|
||||
Name: p.host,
|
||||
Name: p.owner,
|
||||
Data: ip.String(),
|
||||
}
|
||||
err = encoder.Encode(requestData)
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
@@ -20,63 +19,66 @@ import (
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
domain string
|
||||
host string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
username string
|
||||
password string
|
||||
useProviderIP bool
|
||||
domain string
|
||||
owner string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, domain, host string,
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
p *Provider, err error) {
|
||||
extraSettings := struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
UseProviderIP bool `json:"provider_ip"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}{}
|
||||
err = json.Unmarshal(data, &extraSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p = &Provider{
|
||||
domain: domain,
|
||||
host: host,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
username: extraSettings.Username,
|
||||
password: extraSettings.Password,
|
||||
useProviderIP: extraSettings.UseProviderIP,
|
||||
}
|
||||
err = p.isValid()
|
||||
|
||||
err = validateSettings(domain, extraSettings.Username, extraSettings.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
return p, nil
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
username: extraSettings.Username,
|
||||
password: extraSettings.Password,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) isValid() error {
|
||||
func validateSettings(domain, username, password string) (err error) {
|
||||
err = utils.CheckDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.username == "":
|
||||
case username == "":
|
||||
return fmt.Errorf("%w", errors.ErrUsernameNotSet)
|
||||
case p.password == "":
|
||||
case password == "":
|
||||
return fmt.Errorf("%w", errors.ErrPasswordNotSet)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) String() string {
|
||||
return utils.ToString(p.domain, p.host, constants.DNSOMatic, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.DNSOMatic, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -88,24 +90,24 @@ func (p *Provider) IPv6Suffix() netip.Prefix {
|
||||
}
|
||||
|
||||
func (p *Provider) Proxied() bool {
|
||||
return p.host == "all"
|
||||
return p.owner == "all"
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, p.domain)
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
Provider: "<a href=\"https://www.dnsomatic.com/\">dnsomatic</a>",
|
||||
IPVersion: p.ipVersion.String(),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||
// Multiple hosts can be updated in one query, see https://www.dnsomatic.com/docs/api
|
||||
// Multiple hostnames can be updated in one query, see https://www.dnsomatic.com/docs/api
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
Host: "updates.dnsomatic.com",
|
||||
@@ -113,16 +115,13 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
User: url.UserPassword(p.username, p.password),
|
||||
}
|
||||
values := url.Values{}
|
||||
useProviderIP := p.useProviderIP && (ip.Is4() || !p.ipv6Suffix.IsValid())
|
||||
if useProviderIP {
|
||||
values.Set("myip", ip.String())
|
||||
}
|
||||
values.Set("myip", ip.String())
|
||||
values.Set("wildcard", "NOCHG")
|
||||
if p.host == "*" {
|
||||
if p.owner == "*" {
|
||||
values.Set("hostname", p.domain)
|
||||
values.Set("wildcard", "ON")
|
||||
} else {
|
||||
values.Set("hostname", utils.BuildURLQueryHostname(p.host, p.domain))
|
||||
values.Set("hostname", utils.BuildURLQueryHostname(p.owner, p.domain))
|
||||
}
|
||||
values.Set("mx", "NOCHG")
|
||||
values.Set("backmx", "NOCHG")
|
||||
@@ -140,11 +139,10 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
s, err := utils.ReadAndCleanBody(response.Body)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("reading response body: %w", err)
|
||||
return netip.Addr{}, fmt.Errorf("reading response: %w", err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
@@ -180,7 +178,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
}
|
||||
|
||||
newIP = ips[0]
|
||||
if !useProviderIP && ip.Compare(newIP) != 0 {
|
||||
if ip.Compare(newIP) != 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
|
||||
@@ -7,18 +7,19 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Provider_isValid(t *testing.T) {
|
||||
func Test_validateSettings(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
provider Provider
|
||||
domain string
|
||||
username string
|
||||
password string
|
||||
errWrapped error
|
||||
errMessage string
|
||||
}{
|
||||
"empty_username": {
|
||||
provider: Provider{
|
||||
password: "password",
|
||||
},
|
||||
domain: "domain.com",
|
||||
password: "password",
|
||||
errWrapped: errors.ErrUsernameNotSet,
|
||||
errMessage: `username is not set`,
|
||||
},
|
||||
@@ -29,7 +30,7 @@ func Test_Provider_isValid(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := testCase.provider.isValid()
|
||||
err := validateSettings(testCase.domain, testCase.username, testCase.password)
|
||||
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
if testCase.errWrapped != nil {
|
||||
|
||||
@@ -20,13 +20,13 @@ import (
|
||||
|
||||
type Provider struct {
|
||||
domain string
|
||||
host string
|
||||
owner string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
token string
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, domain, host string,
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
p *Provider, err error) {
|
||||
extraSettings := struct {
|
||||
@@ -36,37 +36,43 @@ func New(data json.RawMessage, domain, host string,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p = &Provider{
|
||||
|
||||
err = validateSettings(domain, extraSettings.Token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
host: host,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
token: extraSettings.Token,
|
||||
}
|
||||
err = p.isValid()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) isValid() error {
|
||||
if p.token == "" {
|
||||
func validateSettings(domain, token string) (err error) {
|
||||
err = utils.CheckDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
return fmt.Errorf("%w", errors.ErrTokenNotSet)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) String() string {
|
||||
return utils.ToString(p.domain, p.host, constants.DNSPod, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.DNSPod, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -82,13 +88,13 @@ func (p *Provider) Proxied() bool {
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, p.domain)
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
Provider: "<a href=\"https://www.dnspod.cn/\">DNSPod</a>",
|
||||
IPVersion: p.ipVersion.String(),
|
||||
}
|
||||
@@ -116,7 +122,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
values.Set("format", "json")
|
||||
values.Set("domain", p.domain)
|
||||
values.Set("length", "200")
|
||||
values.Set("sub_domain", p.host)
|
||||
values.Set("sub_domain", p.owner)
|
||||
values.Set("record_type", recordType)
|
||||
encodedValues := values.Encode()
|
||||
buffer := bytes.NewBufferString(encodedValues)
|
||||
@@ -155,7 +161,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
|
||||
var recordID, recordLine string
|
||||
for _, record := range recordResp.Records {
|
||||
if record.Type == recordType && record.Name == p.host {
|
||||
if record.Type == recordType && record.Name == p.owner {
|
||||
receivedIP, err := netip.ParseAddr(record.Value)
|
||||
if err == nil && ip.Compare(receivedIP) == 0 {
|
||||
return ip, nil
|
||||
@@ -177,7 +183,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
values.Set("record_id", recordID)
|
||||
values.Set("value", ip.String())
|
||||
values.Set("record_line", recordLine)
|
||||
values.Set("sub_domain", p.host)
|
||||
values.Set("sub_domain", p.owner)
|
||||
encodedValues = values.Encode()
|
||||
buffer = bytes.NewBufferString(encodedValues)
|
||||
|
||||
|
||||
@@ -19,72 +19,72 @@ import (
|
||||
|
||||
type Provider struct {
|
||||
domain string
|
||||
host string
|
||||
owner string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
username string
|
||||
key string
|
||||
name string
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, domain, host string,
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
p *Provider, err error) {
|
||||
extraSettings := struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"` // retro-compatibility
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
}{}
|
||||
err = json.Unmarshal(data, &extraSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if host == "" {
|
||||
host = "@" // default
|
||||
if owner == "" {
|
||||
owner = "@" // default
|
||||
}
|
||||
if extraSettings.Password != "" { // retro-compatibility
|
||||
extraSettings.Key = extraSettings.Password
|
||||
}
|
||||
|
||||
p = &Provider{
|
||||
err = validateSettings(domain, extraSettings.Username, extraSettings.Key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
host: host,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
username: extraSettings.Username,
|
||||
key: extraSettings.Key,
|
||||
name: extraSettings.Name,
|
||||
}
|
||||
err = p.isValid()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) isValid() error {
|
||||
func validateSettings(domain, username, key string) (err error) {
|
||||
err = utils.CheckDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.username == "":
|
||||
case username == "":
|
||||
return fmt.Errorf("%w", errors.ErrUsernameNotSet)
|
||||
case p.key == "":
|
||||
case key == "":
|
||||
return fmt.Errorf("%w", errors.ErrKeyNotSet)
|
||||
case p.name == "":
|
||||
return fmt.Errorf("%w", errors.ErrNameNotSet)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) String() string {
|
||||
return utils.ToString(p.domain, p.host, constants.DonDominio, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.DonDominio, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -100,13 +100,13 @@ func (p *Provider) Proxied() bool {
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, p.domain)
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
Provider: "<a href=\"https://www.dondominio.com/\">DonDominio</a>",
|
||||
IPVersion: p.ipVersion.String(),
|
||||
}
|
||||
@@ -120,7 +120,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
RawQuery: url.Values{
|
||||
"user": {p.username},
|
||||
"apikey": {p.key},
|
||||
"host": {p.BuildDomainName()},
|
||||
"host": {utils.BuildURLQueryHostname(p.owner, p.domain)},
|
||||
"ip": {ip.String()},
|
||||
"lang": {"en"},
|
||||
}.Encode(),
|
||||
|
||||
@@ -20,13 +20,13 @@ import (
|
||||
|
||||
type Provider struct {
|
||||
domain string
|
||||
host string
|
||||
owner string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
key string
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, domain, host string,
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
p *Provider, err error) {
|
||||
extraSettings := struct {
|
||||
@@ -36,43 +36,49 @@ func New(data json.RawMessage, domain, host string,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if host == "" { // TODO-v2 remove default
|
||||
host = "@" // default
|
||||
if owner == "" { // TODO-v2 remove default
|
||||
owner = "@" // default
|
||||
}
|
||||
p = &Provider{
|
||||
|
||||
err = validateSettings(domain, extraSettings.Key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
host: host,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
key: extraSettings.Key,
|
||||
}
|
||||
err = p.isValid()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
var keyRegex = regexp.MustCompile(`^[a-zA-Z0-9]{16}$`)
|
||||
|
||||
func (p *Provider) isValid() error {
|
||||
if !keyRegex.MatchString(p.key) {
|
||||
func validateSettings(domain, key string) (err error) {
|
||||
err = utils.CheckDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||
}
|
||||
|
||||
if !keyRegex.MatchString(key) {
|
||||
return fmt.Errorf("%w: key %q does not match regex %s",
|
||||
errors.ErrKeyNotValid, p.key, keyRegex)
|
||||
errors.ErrKeyNotValid, key, keyRegex)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) String() string {
|
||||
return utils.ToString(p.domain, p.host, constants.Dreamhost, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.Dreamhost, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -88,13 +94,13 @@ func (p *Provider) Proxied() bool {
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, p.domain)
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
Provider: "<a href=\"https://www.dreamhost.com/\">Dreamhost</a>",
|
||||
IPVersion: p.ipVersion.String(),
|
||||
}
|
||||
@@ -113,7 +119,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
|
||||
var oldIP netip.Addr
|
||||
for _, data := range records.Data {
|
||||
if data.Type == recordType && data.Record == utils.BuildURLQueryHostname(p.host, p.domain) {
|
||||
if data.Type == recordType && data.Record == utils.BuildURLQueryHostname(p.owner, p.domain) {
|
||||
if data.Editable == "0" {
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrRecordNotEditable)
|
||||
}
|
||||
@@ -222,7 +228,7 @@ func (p *Provider) removeRecord(ctx context.Context, client *http.Client, ip net
|
||||
}
|
||||
values := p.defaultURLValues()
|
||||
values.Set("cmd", "dns-remove_record")
|
||||
values.Set("record", utils.BuildURLQueryHostname(p.host, p.domain))
|
||||
values.Set("record", utils.BuildURLQueryHostname(p.owner, p.domain))
|
||||
values.Set("type", recordType)
|
||||
values.Set("value", ip.String())
|
||||
u.RawQuery = values.Encode()
|
||||
@@ -270,7 +276,7 @@ func (p *Provider) createRecord(ctx context.Context, client *http.Client, ip net
|
||||
}
|
||||
values := p.defaultURLValues()
|
||||
values.Set("cmd", "dns-add_record")
|
||||
values.Set("record", utils.BuildURLQueryHostname(p.host, p.domain))
|
||||
values.Set("record", utils.BuildURLQueryHostname(p.owner, p.domain))
|
||||
values.Set("type", recordType)
|
||||
values.Set("value", ip.String())
|
||||
u.RawQuery = values.Encode()
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/ddns-updater/internal/models"
|
||||
"github.com/qdm12/ddns-updater/internal/provider/constants"
|
||||
@@ -20,62 +20,90 @@ import (
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
host string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
token string
|
||||
useProviderIP bool
|
||||
domain string
|
||||
owner string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
token string
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, _, host string,
|
||||
const eTLD = "duckdns.org"
|
||||
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
p *Provider, err error) {
|
||||
// Note domain is of the form:
|
||||
// - for retro-compatibility: "", "duckdns.org"
|
||||
// - domain.duckdns.org since duckdns.org is an eTLD.
|
||||
if domain == "" { // retro-compatibility
|
||||
domain = eTLD
|
||||
}
|
||||
if domain == eTLD { // retro-compatibility
|
||||
ownerParts := strings.Split(owner, ".")
|
||||
lastOwnerPart := ownerParts[len(ownerParts)-1]
|
||||
domain = lastOwnerPart + "." + domain // form domain.duckdns.org
|
||||
if len(ownerParts) > 1 {
|
||||
owner = strings.Join(ownerParts[:len(ownerParts)-1], ".")
|
||||
} else {
|
||||
owner = "@" // root domain
|
||||
}
|
||||
}
|
||||
|
||||
extraSettings := struct {
|
||||
Token string `json:"token"`
|
||||
UseProviderIP bool `json:"provider_ip"`
|
||||
Token string `json:"token"`
|
||||
}{}
|
||||
err = json.Unmarshal(data, &extraSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p = &Provider{
|
||||
host: host,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
token: extraSettings.Token,
|
||||
useProviderIP: extraSettings.UseProviderIP,
|
||||
}
|
||||
err = p.isValid()
|
||||
|
||||
err = validateSettings(domain, owner, extraSettings.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
return p, nil
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
token: extraSettings.Token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var tokenRegex = regexp.MustCompile(`^[a-f0-9]{8}\-[a-f0-9]{4}\-[a-f0-9]{4}\-[a-f0-9]{4}\-[a-f0-9]{12}$`)
|
||||
var (
|
||||
regexDomain = regexp.MustCompile(`^.+\.(duckdns\.org)$`)
|
||||
tokenRegex = regexp.MustCompile(`^[a-f0-9]{8}\-[a-f0-9]{4}\-[a-f0-9]{4}\-[a-f0-9]{4}\-[a-f0-9]{12}$`)
|
||||
)
|
||||
|
||||
func (p *Provider) isValid() error {
|
||||
func validateSettings(domain, owner, token string) (err error) {
|
||||
const maxDomainLabels = 3 // domain.duckdns.org
|
||||
switch {
|
||||
case !tokenRegex.MatchString(p.token):
|
||||
case !regexDomain.MatchString(domain):
|
||||
return fmt.Errorf(`%w: %q must have the effective TLD "duckdns.org"`,
|
||||
errors.ErrDomainNotValid, domain)
|
||||
case strings.Count(owner, ".") > maxDomainLabels-1:
|
||||
return fmt.Errorf("%w: %q has more than %d labels",
|
||||
errors.ErrDomainNotValid, domain, maxDomainLabels)
|
||||
case owner == "*":
|
||||
return fmt.Errorf("%w: %s", errors.ErrOwnerWildcard, owner)
|
||||
case !tokenRegex.MatchString(token):
|
||||
return fmt.Errorf("%w: token %q does not match regex %q",
|
||||
errors.ErrTokenNotValid, p.token, tokenRegex)
|
||||
case p.host == "@", p.host == "*":
|
||||
return fmt.Errorf("%w: %q is not valid",
|
||||
errors.ErrHostOnlySubdomain, p.host)
|
||||
errors.ErrTokenNotValid, token, tokenRegex)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) String() string {
|
||||
return utils.ToString("duckdns.org", p.host, constants.DuckDNS, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.DuckDNS, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return "duckdns.org"
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -91,13 +119,13 @@ func (p *Provider) Proxied() bool {
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, "duckdns.org")
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
Provider: "<a href=\"https://www.duckdns.org/\">DuckDNS</a>",
|
||||
IPVersion: p.ipVersion.String(),
|
||||
}
|
||||
@@ -111,15 +139,12 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
}
|
||||
values := url.Values{}
|
||||
values.Set("verbose", "true")
|
||||
values.Set("domains", p.host)
|
||||
values.Set("domains", p.BuildDomainName())
|
||||
values.Set("token", p.token)
|
||||
useProviderIP := p.useProviderIP && (ip.Is4() || !p.ipv6Suffix.IsValid())
|
||||
if !useProviderIP {
|
||||
if ip.Is6() {
|
||||
values.Set("ipv6", ip.String())
|
||||
} else {
|
||||
values.Set("ip", ip.String())
|
||||
}
|
||||
if ip.Is6() {
|
||||
values.Set("ipv6", ip.String())
|
||||
} else {
|
||||
values.Set("ip", ip.String())
|
||||
}
|
||||
u.RawQuery = values.Encode()
|
||||
|
||||
@@ -135,11 +160,10 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
s, err := utils.ReadAndCleanBody(response.Body)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("reading response body: %w", err)
|
||||
return netip.Addr{}, fmt.Errorf("reading response: %w", err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
@@ -150,9 +174,9 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
switch {
|
||||
case len(s) < minChars:
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrResponseTooShort, s)
|
||||
case s[0:minChars] == "KO":
|
||||
case s[0:minChars] == "ko":
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAuth)
|
||||
case s[0:minChars] == "OK":
|
||||
case s[0:minChars] == "ok":
|
||||
var ips []netip.Addr
|
||||
if ip.Is6() {
|
||||
ips = ipextract.IPv6(s)
|
||||
@@ -163,7 +187,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrReceivedNoIP)
|
||||
}
|
||||
newIP = ips[0]
|
||||
if !useProviderIP && newIP.Compare(ip) != 0 {
|
||||
if newIP.Compare(ip) != 0 {
|
||||
return netip.Addr{}, fmt.Errorf("%w: sent ip %s to update but received %s",
|
||||
errors.ErrIPReceivedMismatch, ip, newIP)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
@@ -20,14 +19,14 @@ import (
|
||||
|
||||
type Provider struct {
|
||||
domain string
|
||||
host string
|
||||
owner string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
username string
|
||||
clientKey string
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, domain, host string,
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
p *Provider, err error) {
|
||||
extraSettings := struct {
|
||||
@@ -45,41 +44,46 @@ func New(data json.RawMessage, domain, host string,
|
||||
clientKey = extraSettings.Password
|
||||
}
|
||||
|
||||
p = &Provider{
|
||||
err = validateSettings(domain, extraSettings.Username, clientKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
host: host,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
username: extraSettings.Username,
|
||||
clientKey: clientKey,
|
||||
}
|
||||
err = p.isValid()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) isValid() error {
|
||||
func validateSettings(domain, username, clientKey string) (err error) {
|
||||
err = utils.CheckDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.username == "":
|
||||
case username == "":
|
||||
return fmt.Errorf("%w", errors.ErrUsernameNotSet)
|
||||
case p.clientKey == "":
|
||||
case clientKey == "":
|
||||
return fmt.Errorf("%w", errors.ErrPasswordNotSet)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) String() string {
|
||||
return utils.ToString(p.domain, p.host, constants.Dyn, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.Dyn, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -95,13 +99,13 @@ func (p *Provider) Proxied() bool {
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, p.domain)
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
Provider: "<a href=\"https://dyn.com/\">Dyn DNS</a>",
|
||||
IPVersion: p.ipVersion.String(),
|
||||
}
|
||||
@@ -116,7 +120,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
Path: "/v3/update",
|
||||
}
|
||||
values := url.Values{}
|
||||
values.Set("hostname", utils.BuildURLQueryHostname(p.host, p.domain))
|
||||
values.Set("hostname", utils.BuildURLQueryHostname(p.owner, p.domain))
|
||||
values.Set("myip", ip.String())
|
||||
u.RawQuery = values.Encode()
|
||||
|
||||
@@ -132,11 +136,10 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
s, err := utils.ReadAndCleanBody(response.Body)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("reading response body: %w", err)
|
||||
return netip.Addr{}, fmt.Errorf("reading response: %w", err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
@@ -19,73 +18,75 @@ import (
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
domain string
|
||||
host string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
username string
|
||||
password string
|
||||
useProviderIP bool
|
||||
group string
|
||||
domain string
|
||||
owner string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
username string
|
||||
password string
|
||||
group string
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, domain, host string,
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
p *Provider, err error) {
|
||||
extraSettings := struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
UseProviderIP bool `json:"provider_ip"`
|
||||
Group string `json:"group"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Group string `json:"group"`
|
||||
}{}
|
||||
err = json.Unmarshal(data, &extraSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
host = "@" // default
|
||||
if owner == "" {
|
||||
owner = "@" // default
|
||||
}
|
||||
|
||||
p = &Provider{
|
||||
domain: domain,
|
||||
host: host,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
group: extraSettings.Group,
|
||||
username: extraSettings.Username,
|
||||
password: extraSettings.Password,
|
||||
useProviderIP: extraSettings.UseProviderIP,
|
||||
}
|
||||
err = p.isValid()
|
||||
err = validateSettings(domain, owner, extraSettings.Username, extraSettings.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
return p, nil
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
group: extraSettings.Group,
|
||||
username: extraSettings.Username,
|
||||
password: extraSettings.Password,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) isValid() error {
|
||||
func validateSettings(domain, owner, username, password string) (err error) {
|
||||
err = utils.CheckDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.username == "":
|
||||
case owner == "*":
|
||||
return fmt.Errorf("%w", errors.ErrOwnerWildcard)
|
||||
case username == "":
|
||||
return fmt.Errorf("%w", errors.ErrUsernameNotSet)
|
||||
case p.password == "":
|
||||
case password == "":
|
||||
return fmt.Errorf("%w", errors.ErrPasswordNotSet)
|
||||
case p.host == "*":
|
||||
return fmt.Errorf("%w", errors.ErrHostWildcard)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) String() string {
|
||||
return utils.ToString(p.domain, p.host, constants.Dynu, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.Dynu, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -101,13 +102,13 @@ func (p *Provider) Proxied() bool {
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, p.domain)
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
Provider: "<a href=\"https://dynu.com/\">Dynu</a>",
|
||||
IPVersion: p.ipVersion.String(),
|
||||
}
|
||||
@@ -123,15 +124,12 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
values.Set("username", p.username)
|
||||
values.Set("password", p.password)
|
||||
values.Set("location", p.group)
|
||||
hostname := utils.BuildDomainName(p.host, p.domain)
|
||||
hostname := utils.BuildDomainName(p.owner, p.domain)
|
||||
values.Set("hostname", hostname)
|
||||
useProviderIP := p.useProviderIP && (ip.Is4() || !p.ipv6Suffix.IsValid())
|
||||
if !useProviderIP {
|
||||
if ip.Is6() {
|
||||
values.Set("myipv6", ip.String())
|
||||
} else {
|
||||
values.Set("myip", ip.String())
|
||||
}
|
||||
if ip.Is6() {
|
||||
values.Set("myipv6", ip.String())
|
||||
} else {
|
||||
values.Set("myip", ip.String())
|
||||
}
|
||||
u.RawQuery = values.Encode()
|
||||
|
||||
@@ -147,11 +145,10 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
s, err := utils.ReadAndCleanBody(response.Body)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("reading response body: %w", err)
|
||||
return netip.Addr{}, fmt.Errorf("reading response: %w", err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||
|
||||
@@ -17,60 +17,63 @@ import (
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
domain string
|
||||
host string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
token string
|
||||
useProviderIP bool
|
||||
domain string
|
||||
owner string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
token string
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, domain, host string,
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
p *Provider, err error) {
|
||||
extraSettings := struct {
|
||||
Token string `json:"token"`
|
||||
UseProviderIP bool `json:"provider_ip"`
|
||||
Token string `json:"token"`
|
||||
}{}
|
||||
err = json.Unmarshal(data, &extraSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p = &Provider{
|
||||
domain: domain,
|
||||
host: host,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
token: extraSettings.Token,
|
||||
useProviderIP: extraSettings.UseProviderIP,
|
||||
}
|
||||
err = p.isValid()
|
||||
|
||||
err = validateSettings(domain, owner, extraSettings.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
return p, nil
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
token: extraSettings.Token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) isValid() error {
|
||||
func validateSettings(domain, owner, token string) (err error) {
|
||||
err = utils.CheckDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.token == "":
|
||||
case owner == "*":
|
||||
return fmt.Errorf("%w", errors.ErrOwnerWildcard)
|
||||
case token == "":
|
||||
return fmt.Errorf("%w", errors.ErrTokenNotSet)
|
||||
case p.host == "*":
|
||||
return fmt.Errorf("%w", errors.ErrHostWildcard)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) String() string {
|
||||
return utils.ToString(p.domain, p.host, constants.DynV6, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.DynV6, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -86,13 +89,13 @@ func (p *Provider) Proxied() bool {
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, p.domain)
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
Provider: "<a href=\"https://dynv6.com/\">DynV6 DNS</a>",
|
||||
IPVersion: p.ipVersion.String(),
|
||||
}
|
||||
@@ -113,16 +116,11 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
}
|
||||
values := url.Values{}
|
||||
values.Set("token", p.token)
|
||||
values.Set("zone", utils.BuildURLQueryHostname(p.host, p.domain))
|
||||
ipValue := ip.String()
|
||||
useProviderIP := p.useProviderIP && (ip.Is4() || !p.ipv6Suffix.IsValid())
|
||||
if useProviderIP {
|
||||
ipValue = "auto"
|
||||
}
|
||||
values.Set("zone", utils.BuildURLQueryHostname(p.owner, p.domain))
|
||||
if isIPv4 {
|
||||
values.Set("ipv4", ipValue)
|
||||
values.Set("ipv4", ip.String())
|
||||
} else {
|
||||
values.Set("ipv6", ipValue)
|
||||
values.Set("ipv6", ip.String())
|
||||
}
|
||||
u.RawQuery = values.Encode()
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
@@ -19,63 +18,66 @@ import (
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
domain string
|
||||
host string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
username string
|
||||
token string
|
||||
useProviderIP bool
|
||||
domain string
|
||||
owner string
|
||||
ipVersion ipversion.IPVersion
|
||||
ipv6Suffix netip.Prefix
|
||||
username string
|
||||
token string
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, domain, host string,
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
p *Provider, err error) {
|
||||
extraSettings := struct {
|
||||
Username string `json:"username"`
|
||||
Token string `json:"token"`
|
||||
UseProviderIP bool `json:"provider_ip"`
|
||||
Username string `json:"username"`
|
||||
Token string `json:"token"`
|
||||
}{}
|
||||
err = json.Unmarshal(data, &extraSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p = &Provider{
|
||||
domain: domain,
|
||||
host: host,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
username: extraSettings.Username,
|
||||
token: extraSettings.Token,
|
||||
useProviderIP: extraSettings.UseProviderIP,
|
||||
}
|
||||
err = p.isValid()
|
||||
|
||||
err = validateSettings(domain, extraSettings.Username, extraSettings.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
return p, nil
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
username: extraSettings.Username,
|
||||
token: extraSettings.Token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) isValid() error {
|
||||
func validateSettings(domain, username, token string) (err error) {
|
||||
err = utils.CheckDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.username == "":
|
||||
case username == "":
|
||||
return fmt.Errorf("%w", errors.ErrUsernameNotSet)
|
||||
case p.token == "":
|
||||
case token == "":
|
||||
return fmt.Errorf("%w", errors.ErrTokenNotSet)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) String() string {
|
||||
return utils.ToString(p.domain, p.host, constants.EasyDNS, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.EasyDNS, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -91,13 +93,13 @@ func (p *Provider) Proxied() bool {
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, p.domain)
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
Provider: "<a href=\"https://easydns.com\">EasyDNS</a>",
|
||||
IPVersion: p.ipVersion.String(),
|
||||
}
|
||||
@@ -112,12 +114,9 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
User: url.UserPassword(p.username, p.token),
|
||||
}
|
||||
values := url.Values{}
|
||||
values.Set("hostname", utils.BuildURLQueryHostname(p.host, p.domain))
|
||||
useProviderIP := p.useProviderIP && (ip.Is4() || !p.ipv6Suffix.IsValid())
|
||||
if !useProviderIP {
|
||||
values.Set("myip", ip.String())
|
||||
}
|
||||
if p.host == "*" {
|
||||
values.Set("hostname", utils.BuildURLQueryHostname(p.owner, p.domain))
|
||||
values.Set("myip", ip.String())
|
||||
if p.owner == "*" {
|
||||
values.Set("wildcard", "ON")
|
||||
}
|
||||
u.RawQuery = values.Encode()
|
||||
@@ -134,11 +133,10 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(response.Body)
|
||||
s, err := utils.ReadAndCleanBody(response.Body)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("reading response body: %w", err)
|
||||
return netip.Addr{}, fmt.Errorf("reading response: %w", err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return netip.Addr{}, fmt.Errorf("%w: %d: %s", errors.ErrHTTPStatusNotValid,
|
||||
@@ -148,13 +146,13 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
switch {
|
||||
case s == "":
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrReceivedNoResult)
|
||||
case strings.Contains(s, "NO_SERVICE"):
|
||||
case strings.Contains(s, "no_service"):
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrNoService)
|
||||
case strings.Contains(s, "NO_ACCESS"):
|
||||
case strings.Contains(s, "no_access"):
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrAuth)
|
||||
case strings.Contains(s, "ILLEGAL_INPUT"), strings.Contains(s, "TOO_SOON"):
|
||||
case strings.Contains(s, "illegal_input"), strings.Contains(s, "too_soon"):
|
||||
return netip.Addr{}, fmt.Errorf("%w", errors.ErrBannedAbuse)
|
||||
case strings.Contains(s, "NO_ERROR"), strings.Contains(s, "OK"):
|
||||
case strings.Contains(s, "no_error"), strings.Contains(s, "ok"):
|
||||
return ip, nil
|
||||
default:
|
||||
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrUnknownResponse, utils.ToSingleLine(s))
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
@@ -20,7 +19,7 @@ import (
|
||||
|
||||
type Provider struct {
|
||||
domain string
|
||||
host string
|
||||
owner string
|
||||
// TODO: remove ipVersion and ipv6Suffix if the provider does not support IPv6.
|
||||
// Usually they do support IPv6 though.
|
||||
ipVersion ipversion.IPVersion
|
||||
@@ -29,23 +28,28 @@ type Provider struct {
|
||||
password string
|
||||
}
|
||||
|
||||
func New(data json.RawMessage, domain, host string,
|
||||
func New(data json.RawMessage, domain, owner string,
|
||||
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||
provider *Provider, err error) {
|
||||
var providerSpecificSettings settings
|
||||
var providerSpecificSettings struct {
|
||||
// TODO adapt to the provider specific settings.
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
err = json.Unmarshal(data, &providerSpecificSettings)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding provider specific settings: %w", err)
|
||||
return nil, fmt.Errorf("json decoding provider specific settings: %w", err)
|
||||
}
|
||||
|
||||
err = validateSettings(providerSpecificSettings, domain, host)
|
||||
err = validateSettings(domain, owner,
|
||||
providerSpecificSettings.Username, providerSpecificSettings.Password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||
}
|
||||
|
||||
return &Provider{
|
||||
domain: domain,
|
||||
host: host,
|
||||
owner: owner,
|
||||
ipVersion: ipVersion,
|
||||
ipv6Suffix: ipv6Suffix,
|
||||
username: providerSpecificSettings.Username,
|
||||
@@ -53,29 +57,26 @@ func New(data json.RawMessage, domain, host string,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO adapt to the provider specific settings.
|
||||
type settings struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func validateSettings(providerSpecificSettings settings, domain, host string) error {
|
||||
func validateSettings(domain, owner, username, password string) (err error) {
|
||||
// TODO: update this switch to be as restrictive as possible
|
||||
// to fail early for the user. Use errors already defined
|
||||
// in the internal/provider/errors package, or add your own
|
||||
// if really necessary. When returning an error, always use
|
||||
// fmt.Errorf (to enforce the caller to use errors.Is()).
|
||||
err = utils.CheckDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case domain == "":
|
||||
return fmt.Errorf("%w", errors.ErrDomainNotSet)
|
||||
case host == "":
|
||||
return fmt.Errorf("%w", errors.ErrHostNotSet)
|
||||
// TODO: does the provider support wildcard hosts? If not, disallow * hosts
|
||||
// case host == "*":
|
||||
// return fmt.Errorf("%w", errors.ErrHostWildcard)
|
||||
case providerSpecificSettings.Username == "":
|
||||
case owner == "":
|
||||
return fmt.Errorf("%w", errors.ErrOwnerNotSet)
|
||||
// TODO: does the provider support wildcard owners? If not, disallow * owners
|
||||
// case owner == "*":
|
||||
// return fmt.Errorf("%w", errors.ErrOwnerWildcard)
|
||||
case username == "":
|
||||
return fmt.Errorf("%w", errors.ErrUsernameNotSet)
|
||||
case providerSpecificSettings.Password == "":
|
||||
case password == "":
|
||||
return fmt.Errorf("%w", errors.ErrPasswordNotSet)
|
||||
}
|
||||
return nil
|
||||
@@ -84,15 +85,15 @@ func validateSettings(providerSpecificSettings settings, domain, host string) er
|
||||
func (p *Provider) String() string {
|
||||
// TODO update the name of the provider and add it to the
|
||||
// internal/provider/constants package.
|
||||
return utils.ToString(p.domain, p.host, constants.Dyn, p.ipVersion)
|
||||
return utils.ToString(p.domain, p.owner, constants.Dyn, p.ipVersion)
|
||||
}
|
||||
|
||||
func (p *Provider) Domain() string {
|
||||
return p.domain
|
||||
}
|
||||
|
||||
func (p *Provider) Host() string {
|
||||
return p.host
|
||||
func (p *Provider) Owner() string {
|
||||
return p.owner
|
||||
}
|
||||
|
||||
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||
@@ -108,13 +109,13 @@ func (p *Provider) Proxied() bool {
|
||||
}
|
||||
|
||||
func (p *Provider) BuildDomainName() string {
|
||||
return utils.BuildDomainName(p.host, p.domain)
|
||||
return utils.BuildDomainName(p.owner, p.domain)
|
||||
}
|
||||
|
||||
func (p *Provider) HTML() models.HTMLRow {
|
||||
return models.HTMLRow{
|
||||
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
|
||||
Host: p.Host(),
|
||||
Owner: p.Owner(),
|
||||
// TODO: update the provider name and link below
|
||||
Provider: "<a href=\"https://dyn.com/\">Dyn DNS</a>",
|
||||
IPVersion: p.ipVersion.String(),
|
||||
@@ -132,7 +133,7 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
Path: "/nic/update",
|
||||
}
|
||||
values := url.Values{}
|
||||
values.Set("hostname", utils.BuildURLQueryHostname(p.host, p.domain))
|
||||
values.Set("hostname", utils.BuildURLQueryHostname(p.owner, p.domain))
|
||||
values.Set("myip", ip.String())
|
||||
u.RawQuery = values.Encode()
|
||||
|
||||
@@ -152,11 +153,10 @@ func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Add
|
||||
|
||||
// TODO handle the encoding of the response body properly. Often it can be JSON,
|
||||
// see other provider code for examples on how to decode JSON.
|
||||
b, err := io.ReadAll(response.Body)
|
||||
s, err := utils.ReadAndCleanBody(response.Body)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("reading response body: %w", err)
|
||||
return netip.Addr{}, fmt.Errorf("reading response: %w", err)
|
||||
}
|
||||
s := string(b)
|
||||
|
||||
// TODO handle every possible status codes from the provider API.
|
||||
// If undocumented, try them out by sending bogus HTTP requests to see
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user