Cloudflare Dynamic DNS
143 linesSmall bash
script to dynamically update Cloudflare DNS
records to the current external IP
addresses.
- Supports both
IPv4
andIPv6
:A
andAAAA
- Multi-source comparison (prevents against source poisoning/failure)
- Cloudflare API v4
- Configurable
#!/bin/bash
# https://reuben.science
red='\033[1;31m'
yellow='\033[1;33m'
green='\033[1;32m'
purple='\033[1;35m'
reset='\033[0m'
# Cloudflare Zone ID - copy from Cloudflare DNS page
ZONE_ID=979f5e5c765123d539fad5e744d3fd10
# Full domain to be updated
DNS_RECORD=reuben.science
# Cloudflare Token - scoped for write access to the desired Zone.Zone, Zone.DNS
CLOUDFLARE_TOKEN=6ktwpvFqyn5U0m0SBEyLt2nPQzSTLy3K_TLZVfXi
# How many IP addresses must agree, in order for the result to be used?
IPv4_THRESHOLD=3
IPv6_THRESHOLD=3
# Get the current external IP addresses
IPv4=(
$(curl -s -m 4 https://api.ipify.org)
$(curl -s -m 4 https://ipv4.wtfismyip.com/text)
$(curl -s -m 4 https://api4.my-ip.io/ip)
$(curl -s -m 4 https://v4.ipv6-test.com/api/myip.php)
$(curl -s -m 4 https://ipv4.lookup.test-ipv6.com/ | jq -r '.ip')
)
IPv6=(
$(curl -s -m 4 https://api6.ipify.org)
$(curl -s -m 4 https://ipv6.wtfismyip.com/text)
$(curl -s -m 4 https://api6.my-ip.io/ip)
$(curl -s -m 4 https://v6.ipv6-test.com/api/myip.php)
$(curl -s -m 4 https://ipv6.lookup.test-ipv6.com/ | jq -r '.ip')
)
# Pass Results to awk
IPv4_AWK=($(printf '%s\n' "${IPv4[@]}" | awk ' \
BEGIN {va = 0; max = 0} \
max < ++c[$0] {if(length($0) != 0){max = c[$0]; addr = $0}} \
{if(length($0) != 0){va = ++va}} \
END {print (length(addr) != 0) ? addr : "NULL", va, max, NR} \
'))
IPv6_AWK=($(printf '%s\n' "${IPv6[@]}" | awk ' \
BEGIN {va = 0; max = 0} \
max < ++c[$0] {if(length($0) != 0){max = c[$0]; addr = $0}} \
{if(length($0) != 0){va = ++va}} \
END {print (length(addr) != 0) ? addr : "NULL", va, max, NR} \
'))
# Get the most common value for each service, ignoring empty responses
IPv4_ADDR=${IPv4_AWK[0]/NULL/}
IPv6_ADDR=${IPv6_AWK[0]/NULL/}
# How many services returned addresses?
IPv4_RETURNED=${IPv4_AWK[1]}
IPv6_RETURNED=${IPv6_AWK[1]}
# How many services agreed?
IPv4_AGREED=${IPv4_AWK[2]}
IPv6_AGREED=${IPv6_AWK[2]}
# How many services in total?
IPv4_TOTAL=${IPv4_AWK[3]}
IPv6_TOTAL=${IPv6_AWK[3]}
# Successful?
IPv4_SUCCESS=""
IPv6_SUCCESS=""
if [ -z "$IPv4_ADDR" ]
then
echo -e "${red}No IPv4 Address Found${reset}" >&2
elif [ "$((IPv4_AGREED))" -ge "$((IPv4_THRESHOLD))" ] # Protect against poisoned data
then
DNS_RECORDIDv4=$(curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=A&name=$DNS_RECORD" \
-H "Authorization: Bearer $CLOUDFLARE_TOKEN" \
-H "Content-Type: application/json" | jq -r '{"result"}[] | .[0] | .id')
response=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$DNS_RECORDIDv4" \
-H "Authorization: Bearer $CLOUDFLARE_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"$DNS_RECORD\",\"content\":\"$IPv4_ADDR\",\"ttl\":1,\"proxied\":false}")
if [ "$(echo "${response}" | jq -r '.success')" = "true" ]
then
echo -e "A Record Updated -> ${yellow}$IPv4_ADDR${reset} ${green}${IPv4_AGREED}${reset}/${IPv4_RETURNED}/${IPv4_TOTAL}"
IPv4_SUCCESS="YES"
else
echo -e "A Record Failed: ${red}${response}${reset}" >&2
fi
else
echo -e "Not Enough IPv4 Addresses Agreed: ${red}${IPv4_AGREED}${reset}/${IPv4_RETURNED}/${IPv4_TOTAL}" >&2
fi
if [ -z "$IPv6_ADDR" ]
then
echo -e "${red}No IPv6 Address Found${reset}" >&2
elif [ "$((IPv6_AGREED))" -ge "$((IPv6_THRESHOLD))" ] # Protect against poisoned data
then
DNS_RECORDIDv6=$(curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=AAAA&name=$DNS_RECORD" \
-H "Authorization: Bearer $CLOUDFLARE_TOKEN" \
-H "Content-Type: application/json" | jq -r '{"result"}[] | .[0] | .id')
response=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$DNS_RECORDIDv6" \
-H "Authorization: Bearer $CLOUDFLARE_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"type\":\"AAAA\",\"name\":\"$DNS_RECORD\",\"content\":\"$IPv6_ADDR\",\"ttl\":1,\"proxied\":false}")
if [ "$(echo "${response}" | jq -r '.success')" = "true" ]
then
echo -e "AAAA Record Updated -> ${purple}$IPv6_ADDR${reset} ${green}${IPv6_AGREED}${reset}/${IPv6_RETURNED}/${IPv6_TOTAL}"
IPv6_SUCCESS="YES"
else
echo -e "AAAA Record Failed: ${red}${response}${reset}" >&2
fi
else
echo -e "Not Enough IPv6 Addresses Agreed: ${red}${IPv6_AGREED}${reset}/${IPv6_RETURNED}/${IPv6_TOTAL}" >&2
fi
if [ -n "$IPv4_ADDR" ]
then
if [ -z "$IPv4_SUCCESS" ]
then
exit 1
fi
fi
if [ -n "$IPv6_ADDR" ]
then
if [ -z "$IPv6_SUCCESS" ]
then
exit 1
fi
fi
exit 0