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