Skip to content

Instantly share code, notes, and snippets.

@crozone
Last active November 14, 2023 06:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save crozone/1dcb7c0dba2362f128fe98575661f57e to your computer and use it in GitHub Desktop.
Save crozone/1dcb7c0dba2362f128fe98575661f57e to your computer and use it in GitHub Desktop.
Script for automatically updating the DNS record in CloudFlare, for dynamic DNS.
#!/bin/bash
# Dynamic DNS auto-update script for CloudFlare API
# Version 3 - Ryan Crosby 2023
#
# Supports IPv4 and IPv6 (A and AAAA records)
#
# Requires curl and jq
echo "Dynamic DNS auto-update script for CloudFlare API"
echo "Version 3"
# =====================
# CONFIGURATION
# =====================
# The name of the interface that holds your public IP address. Eg. enp1s0 or eth0.
INTERFACE_NAME="enp1s0"
# The name of the A and/or AAAA record to be updated.
DNS_ADDRESS="your.dns.record.name.com"
# The CloudFlare API token used to access the API. Must have DNS Zone Read/Write capabilities.
CLOUDFLARE_API_TOKEN="YOUR_CLOUDFLARE_DNS_ZONE_WRITE_TOKEN"
# The CloudFlare API Zone ID
ZONE_ID="YOUR_CLOUDFLARE_ZONE_ID"
# CloudFlare API URL
CLOUDFLARE_HOST="https://api.cloudflare.com"
# =====================
# Do case insensitive string comparisons in if statements
# IPv6 is technically case insensitive so it's easier just to do all comparisons case insensitive.
shopt -s nocasematch
# =====================
# Functions
# =====================
# Returns an interface's global (aka public) IP address.
# Arg 1: Interface Name
# Arg 2: Protocol Family (inet = IPv4, inet6 = IPv6)
get_my_ip() {
# Get public IP address from the internet facing interface
ip -json -family ${2} address show dev ${1} scope global | jq -r '.[0].addr_info[0].local | values'
# Or, if your interface isn't internet facing (and doesn't directly hold your public IP), you can
# get your external IP address from this free, totally trustworthy* web service.
# *I have no idea how trustworthy this random webservice is, or if it will even still exist by the time you find this script.
# _Use at your own risk_
#CURL_IP_PROTOCOL=$([ "${2}" == "inet6" ] && echo "--ipv6" || echo "--ipv4")
#curl --silent ${CURL_IP_PROTOCOL} 'https://api.ipify.org?format=json' | jq -r .ip
}
# Updates a CloudFlare DNS record with the given IP address.
# Arg 1: Record name (example.com)
# Arg 2: Record type (A for IPv4, AAAA for IPv6)
# Arg 3: Record value (external IP address)
update_dns() {
RECORD_NAME="${1}"
RECORD_TYPE="${2}"
RECORD_VALUE="${3}"
# curl note: -f returns non-zero error code for non-OK HTTP response codes. -s stops all stderr output. -S outputs errors when -s is active.
echo -n "Getting DNS ${RECORD_TYPE} records for ${RECORD_NAME} ..."
# Get all zones that match the DNS record name
GET_DNS_LIST_RESULT="$(curl -f -s -S -X GET "${CLOUDFLARE_HOST}/client/v4/zones/${ZONE_ID}/dns_records?type=${RECORD_TYPE}&name=${RECORD_NAME}&order=type&direction=desc&match=all" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}")"
[ $? -eq 0 ] || exit 2
# jq note: -e returns non-zero error code for null result, -r returns raw output.
# Grab the first record's ID
DNS_ID="$(jq -e -r .result[0].id <<< "${GET_DNS_LIST_RESULT}")"
[ $? -eq 0 ] || exit 2
echo "first record ID: ${DNS_ID}"
echo -n "Getting value for record ID ${DNS_ID} ..."
# Get the current zone DNS details
GET_DNS_RESULT="$(curl -f -s -S -X GET "${CLOUDFLARE_HOST}/client/v4/zones/${ZONE_ID}/dns_records/${DNS_ID}" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}")"
[ $? -eq 0 ] || exit 2
CURRENT_RECORD_VALUE="$(jq -e -r .result.content <<< "${GET_DNS_RESULT}")"
[ $? -eq 0 ] || exit 2
echo "${CURRENT_RECORD_VALUE}"
# Check if current IP in DNS entry matches our interface IP
echo "Checking if current DNS value matches external IP address ..."
echo "Current: ${CURRENT_RECORD_VALUE}"
echo "Target : ${RECORD_VALUE}"
if [ "${RECORD_VALUE}" = "${CURRENT_RECORD_VALUE}" ]
then
echo "Match."
echo "Not updating record."
return 0
else
echo "Mismatch."
echo -n "Updating record to ${RECORD_VALUE} ..."
# Update the DNS A record to point to the new IP address
PUT_DNS_RESULT="$(curl -s -S -X PUT "${CLOUDFLARE_HOST}/client/v4/zones/${ZONE_ID}/dns_records/${DNS_ID}" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
--data "{\"type\":\"${RECORD_TYPE}\",\"name\":\"${RECORD_NAME}\",\"content\":\"${RECORD_VALUE}\"}")"
[ $? -eq 0 ] || exit 2
PUT_DNS_SUCCESS="$(jq -e -r .success <<< "${PUT_DNS_RESULT}")"
[ $? -eq 0 ] || exit 2
if [ "${PUT_DNS_SUCCESS}" = "true" ]
then
echo "success."
return 1
else
echo "error."
echo "${PUT_DNS_RESULT}"
return 3
fi
fi
}
# =====================
# Start of main script
# =====================
ANY_SUCCESS=0
echo "Checking IPv4 (A record) ..."
EXTERNAL_IPV4="$(get_my_ip ${INTERFACE_NAME} inet)"
if [ -n "${EXTERNAL_IPV4}" ]
then
echo "External IPv4 address: ${EXTERNAL_IPV4}"
update_dns "${DNS_ADDRESS}" "A" "${EXTERNAL_IPV4}"
ret_val=$?
echo "Return value: ${ret_val}"
ANY_SUCCESS=1
else
echo "No external IPv4 address on adapter ${INTERFACE_NAME}"
fi
echo "Checking IPv6 (AAAA record) ..."
EXTERNAL_IPV6="$(get_my_ip ${INTERFACE_NAME} inet6)"
if [ -n "${EXTERNAL_IPV6}" ]
then
echo "External IPv6 address: ${EXTERNAL_IPV6}"
update_dns "${DNS_ADDRESS}" "AAAA" "${EXTERNAL_IPV6}"
ret_val=$?
echo "Return value: ${ret_val}"
ANY_SUCCESS=1
else
echo "No external IPv6 address on adapter ${INTERFACE_NAME}"
fi
if [ "${ANY_SUCCESS}" -ne 1 ]
then
echo "Adapter ${INTERFACE_NAME} had no external IP addresses, indicating network outage."
echo "Aborting."
exit 4
fi
echo "Exiting."
exit $ret_val
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment