mirror of
https://github.com/qdm12/ddns-updater.git
synced 2026-03-31 06:24:00 -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
|
||||
- deSEC
|
||||
- DigitalOcean
|
||||
- Domeneshop
|
||||
- DonDominio
|
||||
- DNSOMatic
|
||||
- DNSPod
|
||||
@@ -218,6 +219,7 @@ Check the documentation for your DNS provider:
|
||||
- [deSEC](docs/desec.md)
|
||||
- [DigitalOcean](docs/digitalocean.md)
|
||||
- [DD24](docs/dd24.md)
|
||||
- [Domeneshop](docs/domeneshop.md)
|
||||
- [DonDominio](docs/dondominio.md)
|
||||
- [DNSOMatic](docs/dnsomatic.md)
|
||||
- [DNSPod](docs/dnspod.md)
|
||||
|
||||
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"
|
||||
DNSOMatic models.Provider = "dnsomatic"
|
||||
DNSPod models.Provider = "dnspod"
|
||||
Domeneshop models.Provider = "domeneshop"
|
||||
DonDominio models.Provider = "dondominio"
|
||||
Dreamhost models.Provider = "dreamhost"
|
||||
DuckDNS models.Provider = "duckdns"
|
||||
@@ -65,6 +66,7 @@ func ProviderChoices() []models.Provider {
|
||||
DigitalOcean,
|
||||
DNSOMatic,
|
||||
DNSPod,
|
||||
Domeneshop,
|
||||
DonDominio,
|
||||
Dreamhost,
|
||||
DuckDNS,
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"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/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/dreamhost"
|
||||
"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)
|
||||
case constants.DNSPod:
|
||||
return dnspod.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.Domeneshop:
|
||||
return domeneshop.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
case constants.DonDominio:
|
||||
return dondominio.New(data, domain, owner, ipVersion, ipv6Suffix)
|
||||
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