refactored providers setup, added porkbun
This commit is contained in:
parent
7fd7334ee7
commit
ab0de2b77f
26
main.go
26
main.go
|
@ -5,31 +5,25 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"ncddns/mod"
|
||||
"ncddns/providers/gandi"
|
||||
"ncddns/providers/namecheap"
|
||||
"ncddns/providers/porkbun"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
baseURL = "https://dynamicdns.park-your-domain.com/update?"
|
||||
)
|
||||
|
||||
var (
|
||||
byName = map[string]IPSetter{
|
||||
"namecheap": NameCheap{},
|
||||
"gandi": Gandi{},
|
||||
"namecheap": namecheap.Client{},
|
||||
"gandi": gandi.Client{},
|
||||
"porkbun": porkbun.Client{},
|
||||
}
|
||||
)
|
||||
|
||||
type IPSetRequest struct {
|
||||
IP string
|
||||
Domain string
|
||||
Password string
|
||||
Host string
|
||||
}
|
||||
|
||||
type IPSetter interface {
|
||||
IPSet([]IPSetRequest) error
|
||||
IPSet([]mod.IPSetRequest) error
|
||||
}
|
||||
|
||||
type DomainTuple struct {
|
||||
|
@ -83,7 +77,7 @@ func main() {
|
|||
fatal("config", err)
|
||||
for _, cfgEntry := range cfg {
|
||||
fmt.Println(cfgEntry.Domain)
|
||||
err = cfgEntry.Setter.IPSet([]IPSetRequest{
|
||||
err = cfgEntry.Setter.IPSet([]mod.IPSetRequest{
|
||||
{
|
||||
IP: ip,
|
||||
Domain: cfgEntry.Domain,
|
||||
|
@ -113,5 +107,5 @@ func IP() (string, error) {
|
|||
return "", fmt.Errorf("read body: %w", err)
|
||||
}
|
||||
|
||||
return string(body), nil
|
||||
return strings.TrimSpace(string(body)), nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
package mod
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
Verbose bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&Verbose, "v", false, "verbose")
|
||||
}
|
||||
|
||||
type IPSetRequest struct {
|
||||
IP string
|
||||
Domain string
|
||||
Password string
|
||||
Host string
|
||||
}
|
||||
|
||||
func MustJSON(v any) []byte {
|
||||
bytes, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
func TryBody(body io.Reader) string {
|
||||
content, err := io.ReadAll(body)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("failed reading body: %s", err.Error())
|
||||
}
|
||||
return string(content)
|
||||
}
|
||||
|
||||
func Verboseln(args ...any) {
|
||||
if Verbose {
|
||||
fmt.Println(args...)
|
||||
}
|
||||
}
|
||||
|
||||
func Verbosefln(format string, args ...any) {
|
||||
if Verbose {
|
||||
fmt.Printf(format+"\n", args...)
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
package main
|
||||
package gandi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"ncddns/mod"
|
||||
|
||||
"github.com/go-gandi/go-gandi"
|
||||
"github.com/go-gandi/go-gandi/config"
|
||||
|
@ -25,9 +26,9 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
type Gandi struct{}
|
||||
type Client struct{}
|
||||
|
||||
func (g Gandi) IPSet(reqs []IPSetRequest) error {
|
||||
func (g Client) IPSet(reqs []mod.IPSetRequest) error {
|
||||
if len(reqs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -53,7 +54,7 @@ func (g Gandi) IPSet(reqs []IPSetRequest) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (Gandi) EnsureAllDomainsAreSame(reqs []IPSetRequest) error {
|
||||
func (Client) EnsureAllDomainsAreSame(reqs []mod.IPSetRequest) error {
|
||||
first := reqs[0].Domain
|
||||
for _, dom := range reqs {
|
||||
if first != dom.Domain {
|
|
@ -1,16 +1,21 @@
|
|||
package main
|
||||
package namecheap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"ncddns/mod"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
)
|
||||
|
||||
type NameCheap struct{}
|
||||
const (
|
||||
baseURL = "https://dynamicdns.park-your-domain.com/update?"
|
||||
)
|
||||
|
||||
func (nc NameCheap) IPSet(reqs []IPSetRequest) error {
|
||||
type Client struct{}
|
||||
|
||||
func (nc Client) IPSet(reqs []mod.IPSetRequest) error {
|
||||
for _, req := range reqs {
|
||||
if err := nc.SetSingle(req); err != nil {
|
||||
return fmt.Errorf("%s: %w", req.Domain, err)
|
||||
|
@ -19,7 +24,7 @@ func (nc NameCheap) IPSet(reqs []IPSetRequest) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (NameCheap) SetSingle(req IPSetRequest) error {
|
||||
func (Client) SetSingle(req mod.IPSetRequest) error {
|
||||
params := url.Values{}
|
||||
params.Add("host", req.Host)
|
||||
params.Add("domain", req.Domain)
|
|
@ -0,0 +1,36 @@
|
|||
package porkbun
|
||||
|
||||
type EditRequest struct {
|
||||
Request
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
IP string `json:"content"`
|
||||
TTL string `json:"ttl"`
|
||||
}
|
||||
|
||||
func (EditRequest) FromRecord(r Record, keys Request) EditRequest {
|
||||
return EditRequest{
|
||||
Request: keys,
|
||||
Name: r.Name,
|
||||
Type: r.Type,
|
||||
IP: r.IP,
|
||||
TTL: "300",
|
||||
}
|
||||
}
|
||||
|
||||
type retrieveResponse struct {
|
||||
Status string `json:"status"`
|
||||
Records []Record `json:"records"`
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
APIKey string `json:"apikey"`
|
||||
Secret string `json:"secretapikey"`
|
||||
}
|
||||
|
||||
type Record struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
IP string `json:"content"`
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package porkbun
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"ncddns/mod"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
baseURL = "https://porkbun.com/api/json/v3"
|
||||
retrieve = "/dns/retrieve/"
|
||||
edit = "/dns/edit/"
|
||||
keySplitter = ":"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
}
|
||||
|
||||
func (Client) retrieve(domain string, keys Request) ([]Record, error) {
|
||||
url := baseURL + retrieve + domain
|
||||
mod.Verboseln("POST", url)
|
||||
resp, err := http.Post(url, "application/json", bytes.NewBuffer(mod.MustJSON(keys)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("POST retrieve: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("non-ok status code [%d]: %s", resp.StatusCode, mod.TryBody(resp.Body))
|
||||
}
|
||||
res := retrieveResponse{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||
return nil, fmt.Errorf("json: %w", err)
|
||||
}
|
||||
if res.Status != "SUCCESS" {
|
||||
return nil, fmt.Errorf("non-SUCCESS status: %s", res.Status)
|
||||
}
|
||||
return res.Records, nil
|
||||
}
|
||||
|
||||
func (Client) edit(req EditRequest, id, domain string) error {
|
||||
url := baseURL + edit + domain + "/" + id
|
||||
mod.Verboseln("POST", url)
|
||||
resp, err := http.Post(url, "application/json", bytes.NewReader(mod.MustJSON(req)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("POST edit: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("non-ok status code [%d]: %s", resp.StatusCode, mod.TryBody(resp.Body))
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Client) IPSet(reqs []mod.IPSetRequest) error {
|
||||
keyParts := strings.Split(reqs[0].Password, keySplitter)
|
||||
if len(keyParts) != 2 {
|
||||
panic(fmt.Sprintf("non-2 length split for password [%s]", reqs[0].Password))
|
||||
}
|
||||
keys := Request{
|
||||
APIKey: keyParts[0],
|
||||
Secret: keyParts[1],
|
||||
}
|
||||
var err error
|
||||
domainRecords := map[string][]Record{}
|
||||
for _, req := range reqs {
|
||||
records, ok := domainRecords[req.Domain]
|
||||
if !ok {
|
||||
records, err = c.retrieve(req.Domain, keys)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s]: retrieve: %w", req.Domain, err)
|
||||
}
|
||||
domainRecords[req.Domain] = records
|
||||
}
|
||||
for _, record := range records {
|
||||
if record.Type != "A" && record.Type != "AAAA" {
|
||||
mod.Verbosefln("Skipping [%s/%s/%s] due to type [%s]", record.IP, record.Name, record.IP, record.Type)
|
||||
continue
|
||||
}
|
||||
mod.Verbosefln("Current IP for [%s/%s]: [%s], want [%s]", req.Domain, record.Type, record.IP, req.IP)
|
||||
if req.IP == record.IP {
|
||||
mod.Verboseln("Skipping...")
|
||||
continue
|
||||
}
|
||||
edit := EditRequest{}.FromRecord(record, keys)
|
||||
edit.IP = req.IP
|
||||
|
||||
if err := c.edit(edit, record.ID, req.Domain); err != nil {
|
||||
return fmt.Errorf("[%s]: edit: %w", req.Domain, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue