Skip to content

Instantly share code, notes, and snippets.

@lifehome
Forked from benkulbertis/cloudflare-update-record.sh
Last active February 25, 2024 06:34
Show Gist options
  • Save lifehome/eb3f7d798f9bc6720cdc7d2be1238d4f to your computer and use it in GitHub Desktop.
Save lifehome/eb3f7d798f9bc6720cdc7d2be1238d4f 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.

Look out!

A newer version is available!

This gist will no longer update, instead please go to https://github.com/lifehome/systemd-cfddns for more updated versions.

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
[Unit]
Description=Run cfupdate.service every minute
[Timer]
OnCalendar=*:0/1
Unit=cfupdate.service
[Install]
WantedBy=multi-user.target
#!/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
# 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
ip=$(curl -s https://ipv4.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."
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\":\"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. DUMPING RESULTS:\n$update"
exit 1;;
*)
echo "[Cloudflare DDNS] IPv4 context '$ip' has been synced to Cloudflare.";;
esac
#!/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 '$ip' has been synced to Cloudflare.";;
esac
@netzzo
Copy link

netzzo commented Jul 6, 2018

Hi,
The https://v4.ifconfig.co/ip return incorrect data. I changed to http://ipv4.icanhazip.com/ and it's working fine.

@lifehome
Copy link
Author

@netzzo Thanks and updated!

@Noxturnix
Copy link

Works perfectly.

@badrianiulian
Copy link

I like your script and I modified it a little for IPv6.
I'm new to IPv6 (forced by my ISP) and I may have some errors in judgement but the modified script works with both IPv6 and IPv4 records of the same domain
https://gist.github.com/badrianiulian/7abf81d4c814d699091b8beff439ad34#file-cfupdater

@ofit
Copy link

ofit commented Jul 19, 2018

Hello. Can you change script to work with many sub domains like s1.example.com, s2.example.com, s3.example.com and so on?

@lifehome
Copy link
Author

lifehome commented Jul 20, 2018

@Noxturnix Thanks for the compliment! 😆

@badrianiulian Thanks! I have made a script for dual stack record 👍 And it looks good for the dual stack script u modified 😄

@ofit My idea is to update one entry at a time, so either you could CNAME all necessary subdomain to a single A record, or create multiple copies of bash script and systemd units. You may reference the following idea for the DNS management:

;; CNAME Records
sub1.example.org.	1	IN	CNAME	ddns.example.org.
sub2.example.org.	1	IN	CNAME	ddns.example.org.

;; A Records (IPv4 addresses)
ddns.example.org.	1	IN	A	1.2.3.4

@badrianiulian
Copy link

badrianiulian commented Jul 20, 2018

I've updated the script using @ofit 's ideea ... to update multiple sub domains like s1.example.com, s2.example.com, s3.example.com ... but also to a more programmer like idea... using bash functions and arrays
Also there was a bug when updating proxied IP's... default settings set by the API is false ... so I had to read besides "id", those optional settings: "proxiable","proxied" and "ttl". After that I added them to the update string.
Using those options, now the IP stays behind Cloudflare's IP's (like I wanted them to be)

@mistermatt2u
Copy link

Great work on this, thank you. I had to make a few minor edits to the cfupdater-dualstack file to make it work. I only tested it standalone (.sh), no systemd. Prior to the changes below, I was getting a bash error for line 33, and a "7001" error from Cloudflare, "Method PUT not available for that URI."

Line 12 should read ip6=$(curl -s https://ipv6.icanhazip.com/) instead of ip6=$(curl -s https://ipv46.icanhazip.com/)

  • Wrong URL (typo)

Line 33 should read if [[ $ip4 == $old_ip4 && $ip6 == $old_ip6 ]]; then instead of if [ $ip4 == $old_ip4 && $ip6 == $old_ip6 ]; then

  • Double brackets required for compound "if"

Line 43 should read 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\"}")

instead of update4=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records/$record_identifier4" -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\"}")

  • Wrong variable name (correct one is $record4_identifier not $record_identifier4)

Line 44 should read 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\"}")

instead of update6=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records/$record_identifier6" -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\"}")

  • Wrong variable name (correct one is $record6_identifier not $record_identifier6)

@lifehome
Copy link
Author

Hello @mistermatt2u, thanks for pointing out the mistakes, I have corrected them and tested the script out. Despite it's kinda working, I would say more tests would be needed.

@lifehome
Copy link
Author

Have updated the systemd unit files, please run sudo systemctl daemon-reload and step 5 and 6 in tutorial if you have updated the systemd files.

@anazhd
Copy link

anazhd commented Nov 27, 2018

{"success":false,"errors":[{"code":7001,"message":"Method PUT not available for that URI."}],"messages":[],"result":null}

still looking on what can I do to fix that. ipv4 only.

edit: resolved after a reboot. probably something borked left when I update some packages.

@wsokc
Copy link

wsokc commented Dec 7, 2018

Hi,
Just wondering how to set this up for multiple domain ?

@lifehome
Copy link
Author

Hi @anazhd, glad things worked out :D Lemme know if you have trouble using the script!

@wsokc, please refer to the above conversation, as this script only support (at most) dual-stack DNS record update, by modifying the record you will need array/iterate on the record variable, as well as loop over the update functions.

@minhng99
Copy link

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\",\"ttl\":120}")
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\",\"ttl\":120}")

This will be for lowest TTL

@Gondost
Copy link

Gondost commented Apr 25, 2019

Updated the v4 script to accept an array of domains or subdomains and loop through them, updating each as needed. I updated some of the script echoes as well to be more descriptive with the new loop.

#!/bin/bash

# CHANGE THESE
auth_email="john.appleseed@example.org"            # The email used to login 'https://dash.cloudflare.com'
auth_key="f1nd7h476k3y1ncl0udfl4r3c0n50l3"   # Top right corner, "My profile" > "Global API Key"
zone_identifier="f1nd7h3z0n31d3n71f13r4l50" # Can be found in the "Overview" tab of your domain
record_name_array=( "ip1v4.example.org" "ip2v4.example.org" )  # Which record(s) you want to be synced

# DO NOT CHANGE LINES BELOW
ip=$(curl -s https://ipv4.icanhazip.com/)

# SCRIPT START

for record_name in "${record_name_array[@]}"
do

  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?"
    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 for $record_name has not changed."
    exit 0
  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"
    exit 1;;
  *)
    echo "[Cloudflare DDNS] IPv4 context '$ip' for $record_name has been synced to Cloudflare.";;
  esac

done

@minhazulOO7
Copy link

minhazulOO7 commented May 6, 2019

Thanks @lifehome!

I edited and made this gist.

Hope it helps somebody.

@BoKKeR
Copy link

BoKKeR commented Sep 13, 2019

cfupdater-v4
has this line:
echo "[Cloudflare DDNS] IPv4 context '$ip4' has been synced to Cloudflare.";;
but ip4 is never defined!

I would recommend running the scripts trough this site:
https://www.shellcheck.net

@lifehome
Copy link
Author

@BoKKeR Sorry for the typo, it has been corrected.

@niklasmato
Copy link

Anyone having issues since today with the update script?
Seems i get a good answer from CF but the ip's are not updated.

@Gondost
Copy link

Gondost commented Mar 16, 2020

Here's an update that allows you to specify multiple domains, sub domains, zones, and accounts, all from one script.

#!/bin/bash

# CHANGE THESE
declare -A EMAILKEY=(
        # Login email and Global API key
        # [auth_email]=auth_key
        [cfloginemail@domain.com]=yourcloudflareapikey
)

declare -A RECORDEMAIL=(
        # [domain]=auth_email
        [sub.domain.com]=cfloginemail@domain.com
        [domain.com]=cfloginemail@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```

@kyreal
Copy link

kyreal commented May 18, 2020

Anyone having issues since today with the update script?
Seems i get a good answer from CF but the ip's are not updated.

I've been having issues with it recently. Running it on a mac and it seems like the grep -Po commands aren't returning anything any more when they used to.

I've got a different version of grep from homebrew to see if that resolves it and although other grep -Po commands are working these specific ones aren't returning anything. I don't know enough about regex or grep to get the right stuff out.

@lifehome
Copy link
Author

I've been having issues with it recently. Running it on a mac and it seems like the grep -Po commands aren't returning anything any more when they used to.

I've got a different version of grep from homebrew to see if that resolves it and although other grep -Po commands are working these specific ones aren't returning anything. I don't know enough about regex or grep to get the right stuff out.

Hi @kyreal, sorry to hear your experience. It is unfortunate that macOS is using a different version of grep, and thus this script is no way to be executed correctly on macOS. I am currently brewing in a new repository, but yet to have an ETA.

@dinamic
Copy link

dinamic commented Mar 4, 2021

@kyreal you could refactor out the usage of grep - I've used jq in my custom script that's heavily inspired by this one.

https://gist.github.com/dinamic/335bb0ed0e73e1b6af3b3f59c9fe08d1

@AjmalPraveeN
Copy link

Updated the v4 script to accept an array of domains or subdomains and loop through them, updating each as needed. I updated some of the script echoes as well to be more descriptive with the new loop.

#!/bin/bash

# CHANGE THESE
auth_email="john.appleseed@example.org"            # The email used to login 'https://dash.cloudflare.com'
auth_key="f1nd7h476k3y1ncl0udfl4r3c0n50l3"   # Top right corner, "My profile" > "Global API Key"
zone_identifier="f1nd7h3z0n31d3n71f13r4l50" # Can be found in the "Overview" tab of your domain
record_name_array=( "ip1v4.example.org" "ip2v4.example.org" )  # Which record(s) you want to be synced

# DO NOT CHANGE LINES BELOW
ip=$(curl -s https://ipv4.icanhazip.com/)

# SCRIPT START

for record_name in "${record_name_array[@]}"
do

  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?"
    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 for $record_name has not changed."
    exit 0
  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"
    exit 1;;
  *)
    echo "[Cloudflare DDNS] IPv4 context '$ip' for $record_name has been synced to Cloudflare.";;
  esac

done

❤ Thank you..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment