summaryrefslogtreecommitdiff
path: root/main.go
blob: f91c9a661253de5e3de92060e0d0b14f73174fe5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"net/http"
	"encoding/json"
	"strings"
)


const API_DOMAIN string = "https://api.porkbun.com"
const ENDPOINT_GET string = "/api/json/v3/dns/retrieve/"
const ENDPOINT_SET string = "/api/json/v3/dns/edit/"

// error helper
func checkE(e error) {
	if e != nil {
		log.Fatal(e)
		panic(e)
	}
}

// http request helper
func createBody(bodyMap map[string]string) io.Reader {
  bodyData, err := json.Marshal(bodyMap)
  checkE(err)
	body := string(bodyData)
  return strings.NewReader(body)
}

// http response helper
func readBody(res *http.Response) []byte {
	body, err := io.ReadAll(res.Body)
	res.Body.Close()
	if res.StatusCode > 299 {
		log.Fatalf("Response failed with status code: %d and\nbody: %s\n", res.StatusCode, body)
	}
	checkE(err) 
	return body
}

func syncDns(domain string, apiSecret string, apiKey string, serverIp string) bool {
  // construct endpoint url
	url := API_DOMAIN + ENDPOINT_GET + domain

	// use api keys to construct request body for porkbun
	body := createBody(map[string]string{
		"secretapikey" : apiSecret,
		"apikey" : apiKey,
	})
	
  // submit request for domain's DNS records
	res, err := http.Post(url, "application/json", body)
	checkE(err)

	// parse records out from response body
	var bodyJson map[string]interface{}
	err = json.Unmarshal(readBody(res), &bodyJson)
	checkE(err)
	dnsRecords := bodyJson["records"].([]interface{})

	// identify outdated A records 
	aRecordsToUpdate := make(map[string]string)
	for i := range len(dnsRecords) {
		record := dnsRecords[i].(map[string]interface{})
		if record["type"].(string) != "A" { continue }
		if record["content"].(string) == serverIp { continue }
		aRecordsToUpdate[record["name"].(string)] = record["id"].(string)
	}

	// we are potentially done now
	if len(aRecordsToUpdate) == 0 {
		fmt.Println(domain + " is up to date")
		return false
	} 

	// update each outdated A record 
	for recordName, recordId := range aRecordsToUpdate {
		url = API_DOMAIN + ENDPOINT_SET + domain + "/" + recordId
		subdomain := strings.Split(recordName, ".")[0]
		body = createBody(map[string]string{
			"secretapikey" : apiSecret,
			"apikey" : apiKey,
			"content" : serverIp,
			"name" : subdomain,
			"type": "A",
			"ttl": "600",
		})

		// make request to update A record
		res, err = http.Post(url, "application/json", body)
		checkE(err)
		fmt.Println("UPDATED " + recordName)
	}

	return true
}

func main() {
	// open + deserialize env JSON 
	envJson, err := os.ReadFile(".env")
	checkE(err)
	var env map[string]interface{}
	err = json.Unmarshal(envJson, &env)
	checkE(err)

	// parse/organize api keys + domains to update
	apiSecret := env["secretapikey"].(string)
	apiKey 		:= env["apikey"].(string)
	domains		:= env["domains"].([]interface{})

	// determine server's current ip
 	res, err := http.Get("https://api.ipify.org")
	checkE(err)
	ip := string(readBody(res))

	fmt.Println("SERVER IP: " + ip)

	// loop through all domains + update DNS records
	var domain string
	for i := range len(domains) {
		domain = domains[i].(string)
		fmt.Println("checking " + domain)
		syncDns(domain, apiSecret, apiKey, ip)
	}
}