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)
}
}
|