Skip to content

Instantly share code, notes, and snippets.

@Gondost
Forked from lifehome/README.md
Last active March 16, 2020 16:43
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 Gondost/c8f76c2d41b0176e385014a15b073b0f to your computer and use it in GitHub Desktop.
Save Gondost/c8f76c2d41b0176e385014a15b073b0f to your computer and use it in GitHub Desktop.
Cloudflare API v4 Dynamic DNS Update in Bash

Cloudflare DDNS bash client with systemd

This is a bash script to act as a Cloudflare DDNS client, useful replacement for ddclient.

How to use?

  1. Put the cfupdater files to /usr/local/bin
  • If you are using IPv4 for A record, append -v4 to cfupdater in the following systemd service unit.
  • If you are using IPv6 for AAAA record, append -v6 to cfupdater in the following systemd service unit.
  • If you prefer a dual-stack record, append -dualstack to cfupdater in the following systemd service unit.
  1. chmod +x /usr/local/bin/cfupdater
  2. Create a systemd service unit at /etc/systemd/system/, the cfupdate.service is shown as an example.
  3. Create a systemd timer unit at the same location of the service unit, the cfupdate.timer is shown as an example.
  4. sudo systemctl enable cfupdate.timer
  5. sudo systemctl start cfupdate.timer

Note

The default cfupdate.timer is set to execute the script every minute.
Please keep in mind not to spam the API or you will be rate limited.

The dual-stack script has NOT been tested, use with caution. The dual-stack script will always sync upon either IPv4 or IPv6 has changed.

A quote from Cloudflare FAQ:

All calls through the Cloudflare Client API are rate-limited to 1200 every 5 minutes.

-- https://support.cloudflare.com/hc/en-us/articles/200171456

[Unit]
Description=Cloudflare DDNS service
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/cfupdater
[Install]
WantedBy=multi-user.target
[Unit]
Description=Run cfupdate.service every minute
[Timer]
OnCalendar=*:0/1
#!/bin/bash
# Forked by benkulbertis/cloudflare-update-record.sh
# CHANGE THESE
auth_email="john.appleseed@example.org" # The email used to login 'https://dash.cloudflare.com'
auth_key="f1nd7h47fuck1n6k3y1ncl0udfl4r3c0n50l3" # Top right corner, "My profile" > "Global API Key"
zone_identifier="f1nd7h3fuck1n6z0n31d3n71f13r4l50" # Can be found in the "Overview" tab of your domain
record_name="ipv4.example.org" # Which record you want to be synced
# DO NOT CHANGE LINES BELOW
ip4=$(curl -s https://ipv4.icanhazip.com/)
ip6=$(curl -s https://ipv6.icanhazip.com/)
# SCRIPT START
echo "[Cloudflare DDNS] Check Initiated"
# Seek for the record
record4=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records?name=$record_name&type=A" -H "X-Auth-Email: $auth_email" -H "X-Auth-Key: $auth_key" -H "Content-Type: application/json")
record6=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records?name=$record_name&type=AAAA" -H "X-Auth-Email: $auth_email" -H "X-Auth-Key: $auth_key" -H "Content-Type: application/json")
# Can't do anything without both record
if [[ $record4 == *"\"count\":0"* || $record6 == *"\"count\":0"* ]]; then
>&2 echo -e "[Cloudflare DDNS] Dual stack records do not exist, perhaps create them first?"
exit 1
fi
# Set existing IP address from the fetched record
old_ip4=$(echo "$record4" | grep -Po '(?<="content":")[^"]*' | head -1)
old_ip6=$(echo "$record6" | grep -Po '(?<="content":")[^"]*' | head -1)
# Compare either one is the same
# NOTE: The script will update even one IP remains the same.
if [[ $ip4 == $old_ip4 && $ip6 == $old_ip6 ]]; then
echo "[Cloudflare DDNS] IPs have not changed."
exit 0
fi
# Set the record identifier from result
record4_identifier=$(echo "$record4" | grep -Po '(?<="id":")[^"]*' | head -1)
record6_identifier=$(echo "$record6" | grep -Po '(?<="id":")[^"]*' | head -1)
# The execution of update
update4=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records/$record4_identifier" -H "X-Auth-Email: $auth_email" -H "X-Auth-Key: $auth_key" -H "Content-Type: application/json" --data "{\"id\":\"$zone_identifier\",\"type\":\"A\",\"proxied\":false,\"name\":\"$record_name\",\"content\":\"$ip4\"}")
update6=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records/$record6_identifier" -H "X-Auth-Email: $auth_email" -H "X-Auth-Key: $auth_key" -H "Content-Type: application/json" --data "{\"id\":\"$zone_identifier\",\"type\":\"AAAA\",\"proxied\":false,\"name\":\"$record_name\",\"content\":\"$ip6\"}")
# The moment of truth
if [[ $update4 == *"\"success\":false"* || $update6 == *"\"success\":false"* ]]; then
>&2 echo -e "[Cloudflare DDNS] Update failed. DUMPING RESULTS:\n$update4\n$update6"
exit 1
else
echo "[Cloudflare DDNS] IPv4 address '$ip4' and IPv6 address '$ip6' has been synced to Cloudflare."
fi
#!/bin/bash
# CHANGE THESE
declare -A EMAILKEY=(
# Login email and Global API key
# [auth_email]=auth_key
[email@domain.com]=yourcloudflareapikey
)
declare -A RECORDEMAIL=(
# [domain]=auth_email
[sub.domain.com]=email@domain.com
[domain.com]=email@domain.com
)
declare -A RECORDZONE=(
# [record_name]=zone_identifier
[sub.domain.com]=yourcloudflaredomainzoneid
[domain.com]=yourcloudflaredomainzoneid
)
# DO NOT CHANGE LINES BELOW
ip=$(curl -s https://ipv4.icanhazip.com/)
# SCRIPT START
for record_name in "${!RECORDZONE[@]}"
do
# Get all the required values from associative arrays
zone_identifier=${RECORDZONE[$record_name]}
auth_email=${RECORDEMAIL[$record_name]}
auth_key=${EMAILKEY[$auth_email]}
echo "[Cloudflare DDNS] Check Initiated for $record_name"
# Seek for the record
record=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records?name=$record_name" -H "X-Auth-Email: $auth_email" -H "X-Auth-Key: $auth_key" -H "Content-Type: application/json")
# Can't do anything without the record
if [[ $record == *"\"count\":0"* ]]; then
>&2 echo -e "[Cloudflare DDNS] Record does not exist, perhaps create one first?"
continue
fi
# Set existing IP address from the fetched record
old_ip=$(echo "$record" | grep -Po '(?<="content":")[^"]*' | head -1)
# Compare if they're the same
if [ $ip == $old_ip ]; then
echo "[Cloudflare DDNS] IP for $record_name has not changed."
continue
fi
# Set the record identifier from result
echo "[Cloudflare DDNS] Old IP was $old_ip, trying to set new IP $ip"
record_identifier=$(echo "$record" | grep -Po '(?<="id":")[^"]*' | head -1)
# The execution of update
update=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records/$record_identifier" -H "X-Auth-Email: $auth_email" -H "X-Auth-Key: $auth_key" -H "Content-Type: application/json" --data "{\"id\":\"$zone_identifier\",\"type\":\"A\",\"proxied\":false,\"name\":\"$record_name\",\"content\":\"$ip\"}")
# The moment of truth
case "$update" in
*"\"success\":false"*)
>&2 echo -e "[Cloudflare DDNS] Update failed for $record_identifier. IP is still $old_ip. DUMPING RESULTS:\n$update"
continue;;
*)
echo "[Cloudflare DDNS] IPv4 context '$ip' for $record_name has been synced to Cloudflare.";;
esac
done
#!/bin/bash
# Forked by benkulbertis/cloudflare-update-record.sh
# CHANGE THESE
auth_email="john.appleseed@example.org" # The email used to login 'https://dash.cloudflare.com'
auth_key="f1nd7h47fuck1n6k3y1ncl0udfl4r3c0n50l3" # Top right corner, "My profile" > "Global API Key"
zone_identifier="f1nd7h3fuck1n6z0n31d3n71f13r4l50" # Can be found in the "Overview" tab of your domain
record_name="ipv6.example.org" # Which record you want to be synced
# DO NOT CHANGE LINES BELOW
ip=$(curl -s https://ipv6.icanhazip.com/)
# SCRIPT START
echo "[Cloudflare DDNS] Check Initiated"
# Seek for the record
record=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records?name=$record_name" -H "X-Auth-Email: $auth_email" -H "X-Auth-Key: $auth_key" -H "Content-Type: application/json")
# Can't do anything without the record
if [[ $record == *"\"count\":0"* ]]; then
>&2 echo -e "[Cloudflare DDNS] Record does not exist, perhaps create one first?"
exit 1
fi
# Set existing IP address from the fetched record
old_ip=$(echo "$record" | grep -Po '(?<="content":")[^"]*' | head -1)
# Compare if they're the same
if [ $ip == $old_ip ]; then
echo "[Cloudflare DDNS] IP has not changed." | systemd-cat -p notice
exit 0
fi
# Set the record identifier from result
record_identifier=$(echo "$record" | grep -Po '(?<="id":")[^"]*' | head -1)
# The execution of update
update=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records/$record_identifier" -H "X-Auth-Email: $auth_email" -H "X-Auth-Key: $auth_key" -H "Content-Type: application/json" --data "{\"id\":\"$zone_identifier\",\"type\":\"AAAA\",\"proxied\":false,\"name\":\"$record_name\",\"content\":\"$ip\"}")
# The moment of truth
case "$update" in
*"\"success\":false"*)
>&2 echo -e "[Cloudflare DDNS] Update failed for $record_identifier. DUMPING RESULTS:\n$update"
exit 1;;
*)
echo "[Cloudflare DDNS] IPv6 address '$ip6' has been synced to Cloudflare.";;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment