endef
+define Package/ddns-scripts-porkbun
+ $(call Package/ddns-scripts/Default)
+ TITLE:=Extension for porkbun.com API v3
+ DEPENDS:=ddns-scripts +curl
+ PROVIDES:=ddns-scripts_porkbun.com-v3
+endef
+
+define Package/ddns-scripts-porkbun/description
+ Dynamic DNS Client scripts extension for porkbun.com API v3 (require curl)
+ It requires:
+ "option username" to be a Porkbun API key
+ "option password" to be the corresponding Porkbun API secret key
+ "option domain" to be the FQDN for which to configure DDNS
+endef
+
+
define Build/Configure
endef
rm $(1)/usr/share/ddns/default/transip.nl.json
rm $(1)/usr/share/ddns/default/ns1.com.json
rm $(1)/usr/share/ddns/default/one.com.json
+ rm $(1)/usr/share/ddns/default/porkbun.com-v3.json
endef
endef
+define Package/ddns-scripts-porkbun/install
+ $(INSTALL_DIR) $(1)/usr/lib/ddns
+ $(INSTALL_BIN) ./files/usr/lib/ddns/update_porkbun_v3.sh \
+ $(1)/usr/lib/ddns
+
+ $(INSTALL_DIR) $(1)/usr/share/ddns/default
+ $(INSTALL_DATA) ./files/usr/share/ddns/default/porkbun.com-v3.json \
+ $(1)/usr/share/ddns/default/
+endef
+
+define Package/ddns-scripts-porkbun/prerm
+#!/bin/sh
+if [ -z "$${IPKG_INSTROOT}" ]; then
+ /etc/init.d/ddns stop
+fi
+exit 0
+endef
+
+
$(eval $(call BuildPackage,ddns-scripts))
$(eval $(call BuildPackage,ddns-scripts-services))
$(eval $(call BuildPackage,ddns-scripts-utils))
$(eval $(call BuildPackage,ddns-scripts-transip))
$(eval $(call BuildPackage,ddns-scripts-ns1))
$(eval $(call BuildPackage,ddns-scripts-one))
+$(eval $(call BuildPackage,ddns-scripts-porkbun))
--- /dev/null
+#
+# Distributed under the terms of the GNU General Public License (GPL) version 2.0
+# 2024 Ansel Horn <dev@cahorn.net>
+#
+# Script for DDNS support via Porkbun's v3 API for the OpenWRT ddns-scripts package.
+#
+# Will attempt to create a new or edit an existing A or AAAA record for the
+# given domain and subdomain. Existing CNAME and ALIAS records WILL NOT BE
+# EDITED OR DELETED! "username" and "password" configurations should be set to
+# Porkbun API key and secret key, respectively.
+#
+# Porkbun API documentation:
+# https://porkbun.com/api/json/v3/documentation#DNS%20Create%20Record
+#
+
+# Source JSON parser
+. /usr/share/libubox/jshn.sh
+
+# Set API base URL
+# Porkbun has warned it may change API hostname in the future:
+# https://porkbun.com/api/json/v3/documentation#apiHost
+__API="https://api.porkbun.com/api/json/v3"
+
+# Check availability of cURL with SSL
+[ -z "$CURL" ] && [ -z "$CURL_SSL" ] && write_log 14 "cURL with SSL support required! Please install"
+
+# Validate configuration
+[ -z "$domain" ] && write_log 14 "Service section not configured correctly! Missing 'domain'"
+[ -z "$username" ] && write_log 14 "Service section not configured correctly! Missing 'username'"
+[ -z "$password" ] && write_log 14 "Service section not configured correctly! Missing 'password'"
+
+# Split FQDN into domain and subdomain(s)
+__DOMAIN_REGEX='^\(\(.*\)\.\)\?\([^.]\+\.[^.]\+\)$'
+echo $domain | grep "$__DOMAIN_REGEX" > /dev/null || write_log 14 "Invalid domain! Check 'domain' config"
+__DOMAIN=$(echo $domain | sed -e "s/$__DOMAIN_REGEX/\3/")
+__SUBDOMAIN=$(echo $domain | sed -e "s/$__DOMAIN_REGEX/\2/")
+
+# Determine IPv4 or IPv6 address and record type
+if [ "$use_ipv6" -eq 1 ]; then
+ expand_ipv6 "$__IP" __ADDR
+ __TYPE="AAAA"
+else
+ __ADDR="$__IP"
+ __TYPE="A"
+fi
+
+
+# Inject authentication into API request JSON payload
+function json_authenticate() {
+ json_add_string "apikey" "$username"
+ json_add_string "secretapikey" "$password"
+}
+
+# Make Porkbun API call
+# $1 - Porkbun API endpoint
+# $2 - request JSON payload
+function api_call() {
+ local response url
+ url="$__API/$1"
+ write_log 7 "API endpoint URL: $url"
+ write_log 7 "API request JSON payload: $2"
+ response=$($CURL --data "$2" "$url")
+ write_log 7 "API response JSON payload: $response"
+ echo "$response"
+
+
+# Check Porkbun API response status
+function json_check_status() {
+ local status
+ json_get_var status "status"
+ [ "$status" == "SUCCESS" ] || write_log 14 "API request failed!"
+}
+
+# Review DNS record and, if it is the record we're looking for, get its id
+function callback_review_record() {
+ local id name type
+ json_select "$2"
+ json_get_var id "id"
+ json_get_var name "name"
+ json_get_var type "type"
+ [ "$name" == "$domain" -a "$type" == "$__TYPE" ] && echo "$id"
+ json_select ..
+}
+
+# Retrieve all DNS records, find the first appropriate A/AAAA record, and get its id
+function find_existing_record_id() {
+ local request response
+ json_init
+ json_authenticate
+ request=$(json_dump)
+ response=$(api_call "/dns/retrieve/$__DOMAIN" "$request")
+ json_load "$response"
+ json_check_status
+ json_for_each_item callback_review_record "records"
+}
+
+# Create a new A/AAAA record
+function create_record() {
+ local request response
+ json_init
+ json_authenticate
+ json_add_string "name" "$__SUBDOMAIN"
+ json_add_string "type" "$__TYPE"
+ json_add_string "content" "$__ADDR"
+ request=$(json_dump)
+ response=$(api_call "/dns/create/$__DOMAIN" "$request")
+ json_load "$response"
+ json_check_status
+}
+
+# Retrieve an existing record and get its content
+# $1 - record id to retrieve
+function retrieve_record_content() {
+ local content request response
+ json_init
+ json_authenticate
+ request=$(json_dump)
+ response=$(api_call "/dns/retrieve/$__DOMAIN/$1" "$request")
+ json_load "$response"
+ json_check_status
+ json_select "records"
+ json_select 1
+ json_get_var content "content"
+ echo "$content"
+}
+
+# Edit an existing A/AAAA record
+# $1 - record id to edit
+function edit_record() {
+ local request response
+ json_init
+ json_authenticate
+ json_add_string "type" "$__TYPE"
+ json_add_string "content" "$__ADDR"
+ request=$(json_dump)
+ response=$(api_call "/dns/edit/$__DOMAIN/$1" "$request")
+ json_load "$response"
+ json_check_status
+}
+
+
+# Try to identify an appropriate existing DNS record to update
+if [ -z $rec_id]; then
+ write_log 7 "Retrieving DNS $__TYPE record"
+ __ID=$(find_existing_record_id)
+else
+ write_log 7 "Using user-supplied DNS record id: $rec_id"
+ __ID=$rec_id
+fi
+
+# Create or update DNS record with current IP address
+if [ -z "$__ID" ]; then
+ write_log 7 "Creating new DNS $__TYPE record"
+ create_record
+else
+ write_log 7 "Updating existing DNS $__TYPE record"
+ if [ "$(retrieve_record_content $__ID)" == "$__ADDR" ]; then
+ write_log 7 "Skipping Porkbun-unsupported forced noop update"
+ else
+ edit_record "$__ID"
+ fi
+fi