summaryrefslogtreecommitdiffstats
path: root/net/ddns-scripts/files/usr/lib/ddns/update_porkbun_v3.sh
blob: 658fb1ff694a837f6579d7f31aeaa304eeffa467 (plain)
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#
# 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" -s -X POST "$url" -H "Content-Type: application/json" --data "$2" -o "$DATFILE" 2>"$ERRFILE")"
	write_log 7 "API response JSON payload: $(cat "$DATFILE")"
	echo "$(cat "$DATFILE")"
}

# 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 "name" "$__SUBDOMAIN"
	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