Initial commit
This commit is contained in:
@@ -0,0 +1,90 @@
|
|||||||
|
package digitalocean
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type client struct {
|
||||||
|
baseUrl string
|
||||||
|
domain string
|
||||||
|
token string
|
||||||
|
httpClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(baseUrl, token, domain string) *client {
|
||||||
|
return &client{
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
domain: domain,
|
||||||
|
token: token,
|
||||||
|
httpClient: &http.Client{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) GetARecordID() (string, error) {
|
||||||
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/v2/domains/%s/records", c.baseUrl, c.domain), nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.token))
|
||||||
|
|
||||||
|
var records RecordsResponse
|
||||||
|
err = c.doRequest(req, &records)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, record := range records.DomainRecords {
|
||||||
|
if record.Name == "vpn" && record.Type == "A" {
|
||||||
|
return strconv.Itoa(int(record.Id)), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) SetARecord(id, ip string) error {
|
||||||
|
body := fmt.Sprintf(`{"data":%q}`, strings.ReplaceAll(ip, "\n", ""))
|
||||||
|
req, err := http.NewRequest(http.MethodPatch, fmt.Sprintf("%s/v2/domains/%s/records/%s", c.baseUrl, c.domain, id), bytes.NewBufferString(body))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.token))
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
|
||||||
|
var resp map[string]interface{}
|
||||||
|
err = c.doRequest(req, &resp)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) doRequest(req *http.Request, output interface{}) error {
|
||||||
|
resp, err := c.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode >= http.StatusInternalServerError {
|
||||||
|
return fmt.Errorf("%w: statuscode %d", ErrServerError, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode >= http.StatusBadRequest {
|
||||||
|
return fmt.Errorf("%w: statuscode %d", ErrClientError, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(body, &output)
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package digitalocean
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotFound = errors.New("not found")
|
||||||
|
ErrClientError = errors.New("client error")
|
||||||
|
ErrServerError = errors.New("server error")
|
||||||
|
)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package digitalocean
|
||||||
|
|
||||||
|
type DomainRecords struct {
|
||||||
|
Data string `json:"data"`
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecordsResponse struct {
|
||||||
|
DomainRecords []DomainRecords `json:"domain_records"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/davidlick/digitalocean-ddns/digitalocean"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
client = &http.Client{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
token := os.Getenv("DIGITALOCEAN_TOKEN")
|
||||||
|
doBaseUrl := os.Getenv("DIGITALOCEAN_URL")
|
||||||
|
doDomain := os.Getenv("DIGITALOCEAN_DOMAIN")
|
||||||
|
|
||||||
|
client := digitalocean.NewClient(doBaseUrl, token, doDomain)
|
||||||
|
|
||||||
|
ip, err := getPublicIP()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to get public ip: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := client.GetARecordID()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to get A record ID: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.SetARecord(id, ip)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to set A record ip: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("successfully set DNS A record id %s to %s", id, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPublicIP() (string, error) {
|
||||||
|
resp, err := http.Get("https://icanhazip.com")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(body), nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user