summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsotrali <vesspa0330@gmail.com>2026-04-14 23:12:26 -0500
committersotrali <vesspa0330@gmail.com>2026-04-14 23:12:26 -0500
commit7801a9af98c3fea51f24942586ce8d43f36afb69 (patch)
treea76e577ebf188eb9dd319da2fe3a32f2c7fc06e8
hello world
-rw-r--r--README.md13
-rwxr-xr-xdynamic-ddnsbin0 -> 9179802 bytes
-rw-r--r--main.go130
3 files changed, 143 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9d05bf3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+# Dynamic DNS Client for Porkbun API
+
+To use, put binary in a folder with a file named ".env".
+The contents of the .env file should be JSON in the following format:
+ ```{
+ "secretapikey": "XXXXXXXXXXXXX",
+ "apikey": "XXXXXXXXXXXXX",
+ "domains": [
+ "example.com",
+ "example.net",
+ "example.io"
+ ]
+}```
diff --git a/dynamic-ddns b/dynamic-ddns
new file mode 100755
index 0000000..d3e3106
--- /dev/null
+++ b/dynamic-ddns
Binary files differ
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..f91c9a6
--- /dev/null
+++ b/main.go
@@ -0,0 +1,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)
+ }
+}
+