Skip to content

Commit

Permalink
Updating to support getting DNS IP automatically (non-proxied record …
Browse files Browse the repository at this point in the history
…no longer needed)
  • Loading branch information
MachineITSvcs committed Aug 12, 2024
1 parent 486faaf commit 7e13512
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 169 deletions.
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
This was a project I began working on when I founded <a href="https://www.machineitservices.com/" target="_blank">Machine IT Services</a>, a web hosting, web development, and IT company based in Louisville, KY.
This script has the potential to update the DNS Records (A and AAAA) of a multitude of domains/zones on Cloudflare, while also providing flexibility by allowing the update of specific subdomains/records for each domain specified in the config file, compatible with both IPv4 and IPv6 addresses.
You may refer to included config-example.sh for config file format. In order to run the script, just execute it with either the config file name (if in the same directory, i.e. config.sh) or the config file location (i.e. /your/config/directory/config.sh) as your first argument. You can specify numerous custom record sections, with each containing unique or different sets of subdomains/records for each zone specified. You can list the same zone more than once in separate records groups, setting the proxy option for each group as needed.
It will also accept a second argument. This is to specify an additional script to run. Any additional arguments will be passed to the additional script. This is helpful in the situation that your server is also a DNS server or needs the IP address updated somewhere in it's own files or databases as it will include the $oldip4/6 and $newip4/6 variables, as well as the $updatedir/cloudflare-ddns-update.sh script location..
It will also accept a second argument. This is to specify an additional script to run. Any additional arguments will be passed to the additional script. This is helpful in the situation that your server is also a DNS server or needs the IP address updated somewhere in it's own files or databases as it will include the $oldip4/6 and $newip4/6 variables, as well as the $updatedir/cloudflare-ddns-update.sh script location.
This is the perfect script if you're managing multiple sites on your dynamic server, and want to update several individual zones for separate Cloudflare accounts in one script. This script will allow you to specify your accounts, zones, records, and whether or not to use the Cloudflare proxy.

## Usage
Expand All @@ -24,11 +24,6 @@ This is the perfect script if you're managing multiple sites on your dynamic ser

Of course, I'd recommend using a cronjob to run this script automatically at set intervals; Once every minute should be fine.

Please set the `hostnameaddr` variable in your config file to a record that is NOT proxied, and INCLUDE it in your list of records to update.
Otherwise it will always run, as it is the address checked against your current server address. This is IMPORTANT!

NOTE: In the future, I may just use the API to retrieve each record's currently set address in Cloudflare to do away with this "non-proxied" record requirement.

## Contact

For assistance with the use or operation of this utility, feel free to <a href="mailto:[email protected]">email me</a>. Please no spam or solicitation.
Expand Down
282 changes: 140 additions & 142 deletions cloudflare-ddns-update.sh
Original file line number Diff line number Diff line change
@@ -1,172 +1,170 @@
#!/bin/bash


updatedir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

if [ "${1}" != "" ]; then
if [ -e "${1}" ]; then
source "${1}"
elif [ -e "${updatedir}/${1}" ]; then
source "${updatedir}/${1}"
else
echo Config File Not Found... Exiting...
if [ -e "${1}" ]; then
source "${1}"
elif [ -e "${updatedir}/${1}" ]; then
source "${updatedir}/${1}"
else
echo Config File Not Found... Exiting...
exit
fi
fi
shift
else
echo Config File Not Specified... Exiting...
exit
echo Config File Not Specified... Exiting...
exit
fi

if [ "${ids_dir}" != "" ]; then
if [ ! -d "${ids_dir}" ]; then
if [ -d "${updatedir}/${ids_dir}" ]; then
if [ ! -d "${ids_dir}" ]; then
if [ -d "${updatedir}/${ids_dir}" ]; then
ids_dir="${updatedir}/${ids_dir}"
else
mkdir -p "${updatedir}/${ids_dir}"
ids_dir="${updatedir}/${ids_dir}"
fi
fi
fi
else
echo IDS Storage Location Not Specified in Config File...
echo Using Script Location Instead...
echo IDS Storage Location Not Specified in Config File...
echo Using Script Location Instead...
ids_dir="${updatedir}"
fi

if [ "${force}" == "on" ]; then
setip4=0.0.0.0
setip6=::
else
setip4=$(dig +short a ${hostnameaddr})
setip6=$(dig +short aaaa ${hostnameaddr})
fi

pushd "${updatedir}" > /dev/null 2>&1

export newip4=$(curl -4 -s myipv4.machineitservices.com)
export newip6=$(curl -6 -s myipv6.machineitservices.com)

if $([[ "${setip4}" == *"connection timed out"* ]] || [ "${newip4}" == "" ] || [ "${setip4}" == "" ] || $(unset newip4 && echo false)) && \
$([[ "${setip6}" == *"connection timed out"* ]] || [ "${newip6}" == "" ] || [ "${setip6}" == "" ] || $(unset newip6 && echo false)); then
echo Obtaining Addresses Failed. Now Exiting...
exit
else

new_ips="ip4 ip6"
for i in ${new_ips[@]}; do

setip="$(set_ip="set${i}"; setip=$(eval echo '${'${set_ip}'}'); echo ${setip};)"
newip="$(new_ip="new${i}"; newip=$(eval echo '${'${new_ip}'}'); echo ${newip};)"

if [ "${newip}" != "" ] && [ "${setip}" != "${newip}" ]; then
if [ "${i}" == "ip4" ]; then
echo -e "\nChecking IPv4 Address\n"
rec_type="A"
elif [ "${i}" == "ip6" ]; then
echo -e "\nChecking IPv6 Address\n"
rec_type="AAAA"
fi

if [ -f "${updatedir}/new_${i}.txt" ]; then
mv "${updatedir}/new_${i}.txt" "${updatedir}/old_${i}.txt"
elif [ "${setip}" != "" ]; then
echo "${setip}"> "${updatedir}/old_${i}.txt"
else
echo ERROR! Unable To Obtain Old Hostname IP Address
fi

ip_file="${updatedir}/old_${i}.txt"
if [ -f "${ip_file}" ]; then export oldip="$(cat "${updatedir}/old_${i}.txt")"; fi
echo "${newip}"> "${updatedir}/new_${i}.txt"

echo IP Discrepancy Detected
echo Saved IP: ${oldip}
echo Live IP: ${setip}
echo Current IP: ${newip}
echo DNS ${rec_type} Record Updates Required
echo Updating Cloudflare DNS Records
a=1
while [ ${a} -le ${custom_records_num} ]; do
echo -e "\nLoading Custom ${a}"
load_zone="custom${a}_zones[@]"
cur_zone="(${!load_zone})"
for b in $(eval echo '${!'${load_zone}'}'); do
load_zone="custom${a}_zones[${b}]"
g="${!load_zone}"
if [ "$g" != "" ]; then
load_record="custom${a}_records[@]"
cur_record="(${!load_record})"
for c in $(eval echo '${!'$load_record'}'); do
load_record="custom${a}_records[${c}]"
h="${!load_record}"
if [ "$h" != "" ]; then q="."; else q=""; fi
declare ${zones[${g}]//.}_file[${c}]=${ids_dir}/cf-${i}-${h}${q}${zones[${g}]}.ids
id_file=$(eval "echo \"\${zones[${g}]//.}_file[${c}]\"")
echo -e "\nUpdating Record for ${h}${q}${zones[${g}]}"
s=1
while [ ${s} -le ${user_creds_num} ]; do
load_creds="user${s}_credzone[@]"
for t in $(eval echo '${!'${load_creds}'}'); do
load_creds="user${s}_credzone[${t}]"
p="${!load_creds}"
if [ "${p}" == "${g}" ]; then
u="user${s}_creds[0]"
p="user${s}_creds[1]"
if [ -f ${!id_file} ] && [ $(wc -l ${!id_file} | cut -d " " -f 1) == 2 ]; then
echo Reading Identifiers from Saved File: cf-${i}-${h}${q}${zones[${g}]}.ids
zone_identifier=$(head -1 ${!id_file})
record_identifier=$(tail -1 ${!id_file})
else
echo Using API to GET Identifiers
zone_identifier="$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${zones[${g}]}" -H "X-Auth-Email: ${!u}" -H "X-Auth-Key: ${!p}" -H "Content-Type: application/json" | grep -Po '(?<="id":")[^"]*' | head -1 )"
record_identifier="$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${zone_identifier}/dns_records?name=${h}${q}${zones[${g}]}&type=${rec_type}" -H "X-Auth-Email: ${!u}" -H "X-Auth-Key: ${!p}" -H "Content-Type: application/json" | grep -Po '(?<="id":")[^"]*' | head -1 )"
echo Writing Identifiers to Save File: cf-${h}${q}${zones[${g}]}.ids
echo "${zone_identifier}" > "${!id_file}"
echo "${record_identifier}" >> "${!id_file}"
fi

proxy_check="custom${a}_proxied"
proxied=${!proxy_check}
if [ "${proxied}" == "no" ] || [ "${proxied}" == "0" ]; then
update="$(curl -s -X PUT https://api.cloudflare.com/client/v4/zones/${zone_identifier}/dns_records/${record_identifier} -H "X-Auth-Email: ${!u}" -H "X-Auth-Key: ${!p}" -H "Content-Type: application/json" --data "{\"type\":\"${rec_type}\",\"name\":\"${h}${q}${zones[${g}]}\",\"content\":\"${newip}\",\"proxied\":false}")"
new_ips="ip4 ip6"
for i in ${new_ips[@]}; do
newip="$(new_ip="new${i}"; newip=$(eval echo '${'${new_ip}'}'); echo ${newip};)"

if [ "${newip}" != "" ]; then
if [ "${i}" == "ip4" ]; then
echo -e "\nChecking IPv4 Address\n"
rec_type="A"
elif [ "${i}" == "ip6" ]; then
echo -e "\nChecking IPv6 Address\n"
rec_type="AAAA"
fi

elif [ "${proxied}" == "yes" ] || [ "${proxied}" == "1" ]; then
update="$(curl -s -X PUT https://api.cloudflare.com/client/v4/zones/${zone_identifier}/dns_records/${record_identifier} -H "X-Auth-Email: ${!u}" -H "X-Auth-Key: ${!p}" -H "Content-Type: application/json" --data "{\"type\":\"${rec_type}\",\"name\":\"${h}${q}${zones[${g}]}\",\"content\":\"${newip}\",\"proxied\":true}")"
else echo Proxy Value Not Defined in Custom $a; echo Attempting to Update Record Without Proxy Definition in Request
update=$(curl -s -X PUT https://api.cloudflare.com/client/v4/zones/${zone_identifier}/dns_records/${record_identifier} -H "X-Auth-Email: ${!u}" -H "X-Auth-Key: ${!p}" -H "Content-Type: application/json" --data "{\"type\":\"${rec_type}\",\"name\":\"${h}${q}${zones[${g}]}\",\"content\":\"${newip}\"}")
fi
if [[ ${update} == *"\"success\":false"* ]]; then
message="API UPDATE FAILED FOR: \"${h}${q}${zones[${g}]}\" DUMPING RESULTS:\n${update}"
echo -e "${message}"
else
message="IP changed to: ${newip}\nProxied: $(echo "${update}" | grep -Po '(?<="proxied":)[^,]*' | head -1)"
echo -e "${message}"
fi
break
fi
ip_file="${updatedir}/old_${i}.txt"

a=1
while [ ${a} -le ${custom_records_num} ]; do
echo -e "\nLoading Custom ${a}"
load_zone="custom${a}_zones[@]"
cur_zone="(${!load_zone})"
for b in $(eval echo '${!'${load_zone}'}'); do
load_zone="custom${a}_zones[${b}]"
g="${!load_zone}"
if [ "$g" != "" ]; then
load_record="custom${a}_records[@]"
cur_record="(${!load_record})"
for c in $(eval echo '${!'$load_record'}'); do
load_record="custom${a}_records[${c}]"
h="${!load_record}"
if [ "$h" != "" ]; then q="."; else q=""; fi
declare ${zones[${g}]//.}_file[${c}]=${ids_dir}/cf-${i}-${h}${q}${zones[${g}]}.ids
id_file=$(eval "echo \"\${zones[${g}]//.}_file[${c}]\"")
echo -e "\nUpdating Record for ${h}${q}${zones[${g}]}"
s=1
while [ ${s} -le ${user_creds_num} ]; do
load_creds="user${s}_credzone[@]"
for t in $(eval echo '${!'${load_creds}'}'); do
load_creds="user${s}_credzone[${t}]"
p="${!load_creds}"
if [ "${p}" == "${g}" ]; then
u="user${s}_creds[0]"
p="user${s}_creds[1]"
unset zone_identifier
unset record_identifier
unset current_ip
if [ -f ${!id_file} ] && [ $(wc -l ${!id_file} | cut -d " " -f 1) == 2 ]; then
echo Reading Identifiers from Saved File: cf-${i}-${h}${q}${zones[${g}]}.ids
zone_identifier=$(head -1 ${!id_file})
record_identifier=$(tail -1 ${!id_file})
fi

if [ "${zone_identifier}" != "" ] && [ "${record_identifier}" != "" ]; then
current_ip="$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${zone_identifier}/dns_records/${record_identifier}" -H "X-Auth-Email: ${!u}" -H "X-Auth-Key: ${!p}" -H "Content-Type: application/json" | grep -Po '(?<="content":")[^"]*' | head -1 )"
fi

if [ "${current_ip}" == "" ]; then
echo Using API to GET Identifiers
zone_identifier="$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${zones[${g}]}" -H "X-Auth-Email: ${!u}" -H "X-Auth-Key: ${!p}" -H "Content-Type: application/json" | grep -Po '(?<="id":")[^"]*' | head -1 )"
if [ "${zone_identifier}" != "" ]; then
record_identifier="$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${zone_identifier}/dns_records?name=${h}${q}${zones[${g}]}&type=${rec_type}" -H "X-Auth-Email: ${!u}" -H "X-Auth-Key: ${!p}" -H "Content-Type: application/json" | grep -Po '(?<="id":")[^"]*' | head -1 )"
if [ "${record_identifier}" != "" ]; then
echo Writing Identifiers to Save File: cf-${i}-${h}${q}${zones[${g}]}.ids
echo "${zone_identifier}" > "${!id_file}"
echo "${record_identifier}" >> "${!id_file}"
current_ip="$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${zone_identifier}/dns_records/${record_identifier}" -H "X-Auth-Email: ${!u}" -H "X-Auth-Key: ${!p}" -H "Content-Type: application/json" | grep -Po '(?<="content":")[^"]*' | head -1 )"
else
echo DNS Record not found. Attempting to create new $rec_type record for ${h}${q}${zones[${g}]}
proxy_check="custom${a}_proxied"
proxied=${!proxy_check}
if [ "${proxied}" == "no" ] || [ "${proxied}" == "0" ]; then
proxied_bool="false"
else
proxied_bool="true"
fi
record_identifier="$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/${zone_identifier}/dns_records" -H "X-Auth-Email: ${!u}" -H "X-Auth-Key: ${!p}" -H "Content-Type: application/json" --data "{\"type\":\"${rec_type}\",\"name\":\"${h}${q}${zones[${g}]}\",\"content\":\"${newip}\",\"proxied\":${proxied_bool}}" | grep -Po '(?<="id":")[^"]*' | head -1 )"
if [ "${record_identifier}" != "" ]; then
echo Writing Identifiers to Save File: cf-${i}-${h}${q}${zones[${g}]}.ids
echo "${zone_identifier}" > "${!id_file}"
echo "${record_identifier}" >> "${!id_file}"
current_ip="${newip}"
else
echo Unable to retrieve DNS Record Identifier. DNS Record may or may not have been created.
echo Please try running again.
fi
fi
else
echo DNS Zone not found... Continuing...
fi
fi

if [ "${zone_identifier}" != "" ] && [ "${record_identifier}" != "" ]; then
echo "Current IP for ${h}${q}${zones[${g}]} is ${current_ip}"
if [ "${newip}" != "${current_ip}" ]; then
proxy_check="custom${a}_proxied"
proxied=${!proxy_check}
if [ "${proxied}" == "no" ] || [ "${proxied}" == "0" ]; then
update="$(curl -s -X PUT https://api.cloudflare.com/client/v4/zones/${zone_identifier}/dns_records/${record_identifier} -H "X-Auth-Email: ${!u}" -H "X-Auth-Key: ${!p}" -H "Content-Type: application/json" --data "{\"type\":\"${rec_type}\",\"name\":\"${h}${q}${zones[${g}]}\",\"content\":\"${newip}\",\"proxied\":false}")"
elif [ "${proxied}" == "yes" ] || [ "${proxied}" == "1" ]; then
update="$(curl -s -X PUT https://api.cloudflare.com/client/v4/zones/${zone_identifier}/dns_records/${record_identifier} -H "X-Auth-Email: ${!u}" -H "X-Auth-Key: ${!p}" -H "Content-Type: application/json" --data "{\"type\":\"${rec_type}\",\"name\":\"${h}${q}${zones[${g}]}\",\"content\":\"${newip}\",\"proxied\":true}")"
else
echo Proxy Value Not Defined in Custom $a; echo Attempting to Update Record Without Proxy Definition in Request
update=$(curl -s -X PUT https://api.cloudflare.com/client/v4/zones/${zone_identifier}/dns_records/${record_identifier} -H "X-Auth-Email: ${!u}" -H "X-Auth-Key: ${!p}" -H "Content-Type: application/json" --data "{\"type\":\"${rec_type}\",\"name\":\"${h}${q}${zones[${g}]}\",\"content\":\"${newip}\"}")
fi

if [[ ${update} == *"\"success\":false"* ]]; then
message="API UPDATE FAILED FOR: \"${h}${q}${zones[${g}]}\" DUMPING RESULTS:\n${update}"
echo -e "${message}"
else
message="IP changed to: ${newip}\nProxied: $(echo "${update}" | grep -Po '(?<="proxied":)[^,]*' | head -1)"
echo -e "${message}"
fi
else
echo "No IP change detected for ${h}${q}${zones[${g}]}. No update required."
fi
break
else
echo DNS Zone or Record not found... Continuing...
fi
fi
done
((s++))
done
done
((s++))
done
else
echo No Zones Listed... Continuing...
fi
done
else echo No Zones Listed... Continuing...
fi
done
((a++))
done

echo "${newip}" > ${ip_file}

if [ "${1}" != "" ]; then
call_script="${1}"
shift
if [ -e "${call_script}" ]; then
source "${call_script}" "${@}"
elif [ -e "${updatedir}/${call_script}" ]; then
source "${updatedir}/${call_script}" "${@}"
else
echo Secondary Script Specified But File Not Found... Continuing...
fi
fi
((a++))
done
fi
done
fi
Loading

0 comments on commit 7e13512

Please sign in to comment.