feat(providers): add myaddr.tools (#885)

This commit is contained in:
Brian Shea
2024-12-24 03:51:51 -05:00
committed by GitHub
parent be6679273f
commit 03154c35f8
6 changed files with 181 additions and 1 deletions

View File

@@ -78,6 +78,7 @@ This readme and the [docs/](docs/) directory are **versioned** to match the prog
- Linode
- Loopia
- LuaDNS
- Myaddr
- Name.com
- Namecheap
- Netcup
@@ -243,6 +244,7 @@ Check the documentation for your DNS provider:
- [Linode](docs/linode.md)
- [Loopia](docs/loopia.md)
- [LuaDNS](docs/luadns.md)
- [Myaddr](docs/myaddr.md)
- [Name.com](docs/name.com.md)
- [Namecheap](docs/namecheap.md)
- [Netcup](docs/netcup.md)

33
docs/myaddr.md Normal file
View File

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

View File

@@ -12,6 +12,7 @@ import (
"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"
@@ -145,7 +146,10 @@ func extractAllSettings(jsonBytes []byte) (
return allProviders, warnings, nil
}
var ErrProviderNoLongerSupported = errors.New("provider no longer supported")
var (
ErrProviderNoLongerSupported = errors.New("provider no longer supported")
ErrProviderMultipleDomains = errors.New("provider does not support multiple domains")
)
func makeSettingsFromObject(common commonSettings, rawSettings json.RawMessage,
retroGlobalIPv6Suffix netip.Prefix) (
@@ -178,6 +182,11 @@ func makeSettingsFromObject(common commonSettings, rawSettings json.RawMessage,
}
}
if common.Provider == string(constants.Myaddr) && len(owners) > 1 {
return nil, nil, fmt.Errorf("%w: %s for parent domain %q",
ErrProviderMultipleDomains, common.Provider, domain)
}
if common.IPVersion == "" {
common.IPVersion = ipversion.IP4or6.String()
}

View File

@@ -37,6 +37,7 @@ const (
Linode models.Provider = "linode"
Loopia models.Provider = "loopia"
LuaDNS models.Provider = "luadns"
Myaddr models.Provider = "myaddr"
Namecheap models.Provider = "namecheap"
NameCom models.Provider = "name.com"
Netcup models.Provider = "netcup"
@@ -90,6 +91,7 @@ func ProviderChoices() []models.Provider {
Linode,
Loopia,
LuaDNS,
Myaddr,
Namecheap,
NameCom,
Njalla,

View File

@@ -43,6 +43,7 @@ import (
"github.com/qdm12/ddns-updater/internal/provider/providers/linode"
"github.com/qdm12/ddns-updater/internal/provider/providers/loopia"
"github.com/qdm12/ddns-updater/internal/provider/providers/luadns"
"github.com/qdm12/ddns-updater/internal/provider/providers/myaddr"
"github.com/qdm12/ddns-updater/internal/provider/providers/namecheap"
"github.com/qdm12/ddns-updater/internal/provider/providers/namecom"
"github.com/qdm12/ddns-updater/internal/provider/providers/netcup"
@@ -148,6 +149,8 @@ func New(providerName models.Provider, data json.RawMessage, domain, owner strin
return loopia.New(data, domain, owner, ipVersion, ipv6Suffix)
case constants.LuaDNS:
return luadns.New(data, domain, owner, ipVersion, ipv6Suffix)
case constants.Myaddr:
return myaddr.New(data, domain, owner, ipVersion, ipv6Suffix)
case constants.Namecheap:
return namecheap.New(data, domain, owner)
case constants.NameCom:

View File

@@ -0,0 +1,131 @@
package myaddr
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/netip"
"net/url"
"strings"
"github.com/qdm12/ddns-updater/internal/models"
"github.com/qdm12/ddns-updater/internal/provider/constants"
"github.com/qdm12/ddns-updater/internal/provider/errors"
"github.com/qdm12/ddns-updater/internal/provider/headers"
"github.com/qdm12/ddns-updater/internal/provider/utils"
"github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
)
type Provider struct {
domain string
owner string
ipVersion ipversion.IPVersion
ipv6Suffix netip.Prefix
key string
}
func New(data json.RawMessage, domain, owner string,
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix,
) (*Provider, error) {
var providerSpecificSettings struct {
Key string `json:"key"`
}
err := json.Unmarshal(data, &providerSpecificSettings)
if err != nil {
return nil, fmt.Errorf("json decoding provider specific settings: %w", err)
}
err = validateSettings(domain, providerSpecificSettings.Key)
if err != nil {
return nil, fmt.Errorf("validating provider specific settings: %w", err)
}
return &Provider{
domain: domain,
owner: owner,
ipVersion: ipVersion,
ipv6Suffix: ipv6Suffix,
key: providerSpecificSettings.Key,
}, nil
}
func validateSettings(domain, key string) (err error) {
err = utils.CheckDomain(domain)
if err != nil {
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
}
if key == "" {
return fmt.Errorf("%w", errors.ErrKeyNotSet)
}
return nil
}
func (p *Provider) String() string {
return utils.ToString(p.Domain(), p.Owner(), constants.Myaddr, p.IPVersion())
}
func (p *Provider) Domain() string {
return p.domain
}
func (p *Provider) Owner() string {
return p.owner
}
func (p *Provider) BuildDomainName() string {
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()),
Owner: p.Owner(),
Provider: "<a href=\"https://myaddr.tools/\">myaddr</a>",
IPVersion: p.IPVersion().String(),
}
}
func (p *Provider) Proxied() bool {
return false
}
func (p *Provider) IPVersion() ipversion.IPVersion {
return p.ipVersion
}
func (p *Provider) IPv6Suffix() netip.Prefix {
return p.ipv6Suffix
}
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (netip.Addr, error) {
u := &url.URL{
Scheme: "https",
Host: "myaddr.tools",
Path: "/update",
}
v := url.Values{}
v.Set("key", p.key)
v.Set("ip", ip.String())
buffer := strings.NewReader(v.Encode())
request, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), buffer)
if err != nil {
return netip.Addr{}, fmt.Errorf("creating http request: %w", err)
}
headers.SetContentType(request, "application/x-www-form-urlencoded")
headers.SetUserAgent(request)
response, err := client.Do(request)
if err != nil {
return netip.Addr{}, err
}
defer response.Body.Close()
switch response.StatusCode {
case http.StatusOK:
return ip, nil
case http.StatusBadRequest:
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrBadRequest, utils.BodyToSingleLine(response.Body))
case http.StatusNotFound:
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrKeyNotValid, utils.BodyToSingleLine(response.Body))
default:
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
errors.ErrHTTPStatusNotValid, response.StatusCode, utils.BodyToSingleLine(response.Body))
}
}