mirror of
https://github.com/qdm12/ddns-updater.git
synced 2026-04-05 00:43:53 -04:00
feat(provider): domene.shop (#810)
This commit is contained in:
@@ -56,6 +56,7 @@ This readme and the [docs/](docs/) directory are **versioned** to match the prog
|
|||||||
- DDNSS.de
|
- DDNSS.de
|
||||||
- deSEC
|
- deSEC
|
||||||
- DigitalOcean
|
- DigitalOcean
|
||||||
|
- Domeneshop
|
||||||
- DonDominio
|
- DonDominio
|
||||||
- DNSOMatic
|
- DNSOMatic
|
||||||
- DNSPod
|
- DNSPod
|
||||||
@@ -218,6 +219,7 @@ Check the documentation for your DNS provider:
|
|||||||
- [deSEC](docs/desec.md)
|
- [deSEC](docs/desec.md)
|
||||||
- [DigitalOcean](docs/digitalocean.md)
|
- [DigitalOcean](docs/digitalocean.md)
|
||||||
- [DD24](docs/dd24.md)
|
- [DD24](docs/dd24.md)
|
||||||
|
- [Domeneshop](docs/domeneshop.md)
|
||||||
- [DonDominio](docs/dondominio.md)
|
- [DonDominio](docs/dondominio.md)
|
||||||
- [DNSOMatic](docs/dnsomatic.md)
|
- [DNSOMatic](docs/dnsomatic.md)
|
||||||
- [DNSPod](docs/dnspod.md)
|
- [DNSPod](docs/dnspod.md)
|
||||||
|
|||||||
31
docs/domeneshop.md
Normal file
31
docs/domeneshop.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Domeneshop.no
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"provider": "domeneshop",
|
||||||
|
"domain": "domain.com,seconddomain.com",
|
||||||
|
"token": "token",
|
||||||
|
"secret": "secret",
|
||||||
|
"ip_version": "ipv4",
|
||||||
|
"ipv6_suffix": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compulsory parameters
|
||||||
|
|
||||||
|
- `"domain"` is the domain to update. It can be `example.com` (root domain) or `sub.example.com` (subdomain of `example.com`)
|
||||||
|
- `"token"` See [api.domeneshop.no/docs/](https://api.domeneshop.no/docs/) for instructions on how to generate credentials.
|
||||||
|
- `"secret"`
|
||||||
|
|
||||||
|
### Optional parameters
|
||||||
|
|
||||||
|
- `"ip_version"` can be `ipv4` (A records), or `ipv6` (AAAA records) or `ipv4 or ipv6` (update one of the two, depending on the public ip found). It defaults to `ipv4 or ipv6`.
|
||||||
|
- `"ipv6_suffix"` is the IPv6 interface identifier suffix to use. It can be for example `0:0:0:0:72ad:8fbb:a54e:bedd/64`. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating.
|
||||||
@@ -15,6 +15,7 @@ const (
|
|||||||
DigitalOcean models.Provider = "digitalocean"
|
DigitalOcean models.Provider = "digitalocean"
|
||||||
DNSOMatic models.Provider = "dnsomatic"
|
DNSOMatic models.Provider = "dnsomatic"
|
||||||
DNSPod models.Provider = "dnspod"
|
DNSPod models.Provider = "dnspod"
|
||||||
|
Domeneshop models.Provider = "domeneshop"
|
||||||
DonDominio models.Provider = "dondominio"
|
DonDominio models.Provider = "dondominio"
|
||||||
Dreamhost models.Provider = "dreamhost"
|
Dreamhost models.Provider = "dreamhost"
|
||||||
DuckDNS models.Provider = "duckdns"
|
DuckDNS models.Provider = "duckdns"
|
||||||
@@ -65,6 +66,7 @@ func ProviderChoices() []models.Provider {
|
|||||||
DigitalOcean,
|
DigitalOcean,
|
||||||
DNSOMatic,
|
DNSOMatic,
|
||||||
DNSPod,
|
DNSPod,
|
||||||
|
Domeneshop,
|
||||||
DonDominio,
|
DonDominio,
|
||||||
Dreamhost,
|
Dreamhost,
|
||||||
DuckDNS,
|
DuckDNS,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/qdm12/ddns-updater/internal/provider/providers/digitalocean"
|
"github.com/qdm12/ddns-updater/internal/provider/providers/digitalocean"
|
||||||
"github.com/qdm12/ddns-updater/internal/provider/providers/dnsomatic"
|
"github.com/qdm12/ddns-updater/internal/provider/providers/dnsomatic"
|
||||||
"github.com/qdm12/ddns-updater/internal/provider/providers/dnspod"
|
"github.com/qdm12/ddns-updater/internal/provider/providers/dnspod"
|
||||||
|
"github.com/qdm12/ddns-updater/internal/provider/providers/domeneshop"
|
||||||
"github.com/qdm12/ddns-updater/internal/provider/providers/dondominio"
|
"github.com/qdm12/ddns-updater/internal/provider/providers/dondominio"
|
||||||
"github.com/qdm12/ddns-updater/internal/provider/providers/dreamhost"
|
"github.com/qdm12/ddns-updater/internal/provider/providers/dreamhost"
|
||||||
"github.com/qdm12/ddns-updater/internal/provider/providers/duckdns"
|
"github.com/qdm12/ddns-updater/internal/provider/providers/duckdns"
|
||||||
@@ -100,6 +101,8 @@ func New(providerName models.Provider, data json.RawMessage, domain, owner strin
|
|||||||
return dnsomatic.New(data, domain, owner, ipVersion, ipv6Suffix)
|
return dnsomatic.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||||
case constants.DNSPod:
|
case constants.DNSPod:
|
||||||
return dnspod.New(data, domain, owner, ipVersion, ipv6Suffix)
|
return dnspod.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||||
|
case constants.Domeneshop:
|
||||||
|
return domeneshop.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||||
case constants.DonDominio:
|
case constants.DonDominio:
|
||||||
return dondominio.New(data, domain, owner, ipVersion, ipv6Suffix)
|
return dondominio.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||||
case constants.Dreamhost:
|
case constants.Dreamhost:
|
||||||
|
|||||||
155
internal/provider/providers/domeneshop/provider.go
Normal file
155
internal/provider/providers/domeneshop/provider.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package domeneshop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"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
|
||||||
|
token string
|
||||||
|
secret string
|
||||||
|
ipVersion ipversion.IPVersion
|
||||||
|
ipv6Suffix netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(data json.RawMessage, domain, owner string,
|
||||||
|
ipVersion ipversion.IPVersion, ipv6Suffix netip.Prefix) (
|
||||||
|
provider *Provider, err error) {
|
||||||
|
var providerSpecificSettings struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(data, &providerSpecificSettings)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("json decoding provider specific settings: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateSettings(domain, owner,
|
||||||
|
providerSpecificSettings.Token, providerSpecificSettings.Secret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("validating provider specific settings: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Provider{
|
||||||
|
domain: domain,
|
||||||
|
owner: owner,
|
||||||
|
token: providerSpecificSettings.Token,
|
||||||
|
secret: providerSpecificSettings.Secret,
|
||||||
|
ipVersion: ipVersion,
|
||||||
|
ipv6Suffix: ipv6Suffix,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSettings(domain, owner, token, secret string) (err error) {
|
||||||
|
err = utils.CheckDomain(domain)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %w", errors.ErrDomainNotValid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case owner == "":
|
||||||
|
return fmt.Errorf("%w", errors.ErrOwnerNotSet)
|
||||||
|
case owner == "*":
|
||||||
|
return fmt.Errorf("%w", errors.ErrOwnerWildcard)
|
||||||
|
case token == "":
|
||||||
|
return fmt.Errorf("%w", errors.ErrTokenNotSet)
|
||||||
|
case secret == "":
|
||||||
|
return fmt.Errorf("%w", errors.ErrSecretNotSet)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) String() string {
|
||||||
|
return utils.ToString(p.domain, p.owner, constants.Domeneshop, p.ipVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) Domain() string {
|
||||||
|
return p.domain
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) Owner() string {
|
||||||
|
return p.owner
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) IPVersion() ipversion.IPVersion {
|
||||||
|
return p.ipVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) IPv6Suffix() netip.Prefix {
|
||||||
|
return p.ipv6Suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) Proxied() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
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://domene.shop/\">Domeneshop</a>",
|
||||||
|
IPVersion: p.ipVersion.String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link to documentation:
|
||||||
|
// https://api.domeneshop.no/docs/#tag/ddns/paths/~1dyndns~1update/get
|
||||||
|
func (p *Provider) Update(ctx context.Context, client *http.Client, ip netip.Addr) (newIP netip.Addr, err error) {
|
||||||
|
u := url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: "api.domeneshop.no",
|
||||||
|
Path: "/v0/dyndns/update",
|
||||||
|
}
|
||||||
|
values := url.Values{}
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return netip.Addr{}, fmt.Errorf("creating http request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
request.SetBasicAuth(p.token, p.secret)
|
||||||
|
headers.SetUserAgent(request)
|
||||||
|
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return netip.Addr{}, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
b, err := io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return netip.Addr{}, fmt.Errorf("reading response body: %w", err)
|
||||||
|
}
|
||||||
|
s := string(b)
|
||||||
|
|
||||||
|
switch response.StatusCode {
|
||||||
|
case http.StatusNoContent:
|
||||||
|
return ip, nil
|
||||||
|
case http.StatusNotFound:
|
||||||
|
return netip.Addr{}, fmt.Errorf("%w: %s", errors.ErrHostnameNotExists, utils.ToSingleLine(s))
|
||||||
|
default:
|
||||||
|
return netip.Addr{}, fmt.Errorf("%w: %d: %s",
|
||||||
|
errors.ErrHTTPStatusNotValid, response.StatusCode, utils.ToSingleLine(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user