Files
porkbun-ddns/main.go
T
2026-04-14 23:12:26 -05:00

131 lines
3.1 KiB
Go

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