-
Star
(173)
You must be signed in to star a gist -
Fork
(49)
You must be signed in to fork a gist
-
-
Save larrybolt/6295160 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash | |
set -o errexit | |
set -o nounset | |
set -o pipefail | |
# Automatically update your CloudFlare DNS record to the IP, Dynamic DNS | |
# Can retrieve cloudflare Domain id and list zone's, because, lazy | |
# Place at: | |
# /usr/local/bin/cf-ddns.sh | |
# run `crontab -e` and add next line: | |
# 0 * * * * /usr/local/bin/cf-ddns.sh >/dev/null 2>&1 | |
# if you're lazy (like me) copy/paste the command BETWEEN the EOT | |
: <<'EOT' | |
curl https://gist.githubusercontent.com/larrybolt/6295160/raw > /usr/local/bin/cf-ddns.sh && chmod +x /usr/local/bin/cf-ddns.sh | |
(crontab -l 2>/dev/null; echo "0 * * * * /usr/local/bin/cf-ddns.sh >/dev/null 2>&1") | crontab - | |
$EDITOR /usr/local/bin/cf-ddns.sh | |
/usr/local/bin/cf-ddns.sh | |
EOT | |
# run /usr/local/bin/cf-ddns.sh in terminal to check all settings are valid | |
# Usage: | |
# cf-ddns.sh -k cloudflare-api-key \ | |
# -u [email protected] \ | |
# -h host.example.com \ # fqdn of the record you want to update | |
# -z example.com \ # will show you all zones if forgot, but you need this | |
# Optional flags: | |
# -i cloudflare-record-id \ # script will show this | |
# -a true|false \ # auto get zone list and record id | |
# -f false|true \ # force dns update, disregard local stored ip | |
# default config | |
# API key, see https://www.cloudflare.com/a/account/my-account, | |
# incorrect api-key results in E_UNAUTH error | |
CFKEY= | |
# Zone name, will list all possible if missing, eg: example.com | |
CFZONE= | |
# Domain id, will retrieve itself by default | |
CFID= | |
# Username, eg: [email protected] | |
CFUSER= | |
# Hostname to update, eg: homeserver.example.com | |
CFHOST= | |
# Cloudflare TTL for record, between 120 and 86400 seconds | |
CFTTL=3600 | |
# Get domain ID from Cloudflare using awk/sed and python json.tool | |
GETID=true | |
# Ignore local file, update ip anyway | |
FORCE=false | |
# Site to retrieve WAN ip, other examples are: bot.whatismyipaddress.com, https://api.ipify.org/ ... | |
WANIPSITE="http://icanhazip.com" | |
# get parameter | |
while getopts a:k:i:u:h:z:f: opts; do | |
case ${opts} in | |
a) GETID=${OPTARG} ;; | |
k) CFKEY=${OPTARG} ;; | |
i) CFID=${OPTARG} ;; | |
u) CFUSER=${OPTARG} ;; | |
h) CFHOST=${OPTARG} ;; | |
z) CFZONE=${OPTARG} ;; | |
f) FORCE=${OPTARG} ;; | |
esac | |
done | |
# If required settings are missing just exit | |
if [ "$CFKEY" = "" ]; then | |
echo "Missing api-key, get at: https://www.cloudflare.com/a/account/my-account" | |
echo "and save in ${0} or using the -k flag" | |
exit 2 | |
fi | |
if [ "$CFUSER" = "" ]; then | |
echo "Missing username, probably your email-address" | |
echo "and save in ${0} or using the -u flag" | |
exit 2 | |
fi | |
if [ "$CFHOST" = "" ]; then | |
echo "Missing hostname, what host do you want to update?" | |
echo "save in ${0} or using the -h flag" | |
exit 2 | |
fi | |
# If the hostname is not a FQDN | |
if [ "$CFHOST" != "$CFZONE" ] && ! [ -z "${CFHOST##*$CFZONE}" ]; then | |
CFHOST="$CFHOST.$CFZONE" | |
echo " => Hostname is not a FQDN, assuming $CFHOST" | |
fi | |
# If CFZONE is missing, retrieve them all from CF | |
if [ "$CFZONE" = "" ]; then | |
echo "Missing zone" | |
if ! [ "$GETID" == true ]; then exit 2; fi | |
echo "listing all zones: (if api-key is valid)" | |
curl -s https://www.cloudflare.com/api_json.html \ | |
-d a=zone_load_multi \ | |
-d tkn=$CFKEY \ | |
-d email=$CFUSER \ | |
| grep -Eo '"zone_name":"([^"]+)"' \ | |
| cut -d':' -f2 \ | |
| awk '{gsub("\"","");print "* "$1}' | |
echo "Please specify the matching zone in ${0} or specify using the -z flag" | |
exit 2 | |
fi | |
# Get current and old WAN ip | |
WAN_IP=`curl -s ${WANIPSITE}` | |
if [ -f $HOME/.wan_ip-cf.txt ]; then | |
OLD_WAN_IP=`cat $HOME/.wan_ip-cf.txt` | |
else | |
echo "No file, need IP" | |
OLD_WAN_IP="" | |
fi | |
# If WAN IP is unchanged an not -f flag, exit here | |
if [ "$WAN_IP" = "$OLD_WAN_IP" ] && [ "$FORCE" = false ]; then | |
echo "WAN IP Unchanged, to update anyway use flag -f true" | |
exit 0 | |
fi | |
# If CFID is missing retrieve and use it | |
if [ "$CFID" = "" ]; then | |
echo "Missing DNS record ID" | |
if ! [ "$GETID" == true ]; then exit 2; fi | |
echo "fetching from Cloudflare..." | |
if ! CFID=$( | |
curl -s https://www.cloudflare.com/api_json.html \ | |
-d a=rec_load_all \ | |
-d tkn=$CFKEY \ | |
-d email=$CFUSER \ | |
-d z=$CFZONE \ | |
| grep -Eo '"(rec_id|name|type)":"([^"]+)"' \ | |
| cut -d':' -f2 \ | |
| awk 'NR%3{gsub("\"","");printf $0" ";next;}1' \ | |
| grep -E "${CFHOST//./\\.}" \ | |
| grep -e '"A"' \ | |
| grep -Eo "(^|\s)(\d+)(\s|$)" | |
); then | |
echo " => Incorrect zone, or zone doesn't contain the A-record ${CFHOST}!" | |
echo "listing all records for zone ${CFZONE}:" | |
(printf "ID RECORD TYPE\n"; | |
curl -s https://www.cloudflare.com/api_json.html \ | |
-d a=rec_load_all \ | |
-d tkn=$CFKEY \ | |
-d email=$CFUSER \ | |
-d z=$CFZONE \ | |
| grep -Eo '"(rec_id|name|type)":"([^"]+)"' \ | |
| cut -d':' -f2 \ | |
| awk 'NR%3{gsub("\"","");printf $0" ";next;}1' | |
)| column -t | |
exit 2 | |
fi | |
echo " => Found CFID=${CFID}, advising to save this to ${0} or set it using the -i flag" | |
fi | |
# If WAN is changed, update cloudflare | |
echo "Updating DNS to $WAN_IP" | |
RESPONSE=$( | |
curl -s https://www.cloudflare.com/api_json.html \ | |
-d a=rec_edit \ | |
-d tkn=$CFKEY \ | |
-d email=$CFUSER \ | |
-d z=$CFZONE \ | |
-d id=$CFID \ | |
-d ttl=$CFTTL \ | |
-d type=A \ | |
-d name=$CFHOST \ | |
-d "content=$WAN_IP" | |
) | |
if [ "$RESPONSE" != "${RESPONSE%success*}" ]; then | |
echo "Updated succesfuly!" | |
echo $WAN_IP > $HOME/.wan_ip-cf.txt | |
exit | |
else | |
echo 'Something went wrong :(' | |
echo "Response: $RESPONSE" | |
exit 1 | |
fi |
I'm working on a modified version of this script but i keep getting this error when trying to update:
"Editing types after a record has been created is not allowed"
Has anyone come across this error before - im stuck.
Fantastic - Had an issue where a firewall was blocking anything that was coming in via Dynamic DNS - I was using a CNAME to a no-ip.org address to get to my home server. Whacking this on the Raspberry Pi sorted the issue in under 10 minutes :) I'll keep the no-ip agent running to ensure some level of failover, fantastic script :)
I have updated it to use the dig +short myip.opendns.com @resolver1.opendns.com command instead and so far perfect.
@snipermd sounds like the record you created wasn't an A-record, if you are still having problems mail me (see github profile) and I'll help you as good as I can!
@adammatthews Awesome! 😄 Glad to know it works out of the box on RPi! Right? Otherwise that awk/sed-voodoo was for nothing :(
Made it easier and clearer, removed python so should work in a mostly POSIX-compliant OS (I think)
I'm too lazy to actually find out zone and record ID. So I made the same thing in Python. Only real parameter is the subdomain. E.g. you can update server host1.tld.com by calling
cloudflare-update.py host1
https://gist.github.com/m3nu/62ffc2d402166f3e0694
Only dependency is the python-requests
package that should be available in all distributions.
(Update 2020: Fix broken link.)
Or just use a CNAME to an NO-IP 💃
Hey man, really cool script, thanks for making it!!
I had a small issue, the script wasn't finding my host and saying => Incorrect zone, or zone doesn't contain the A-record myhost.mydomain.com!
, even though it was on list, so I checked out the code and line 144 was broken for me, to fix it I had to replace (^|\s)(\d+)(\s|$)
for (^|\s)([0-9]+)(\s|$)
, I know it's the same thing, but now it's working perfectly.
FYI - using grep (GNU grep) 2.23
Thanks @vhugo, just had the same problem and applying your fix made it work.
Thanks @vhugo, that worked for me too. Why not change the piped and nested code to something clearer using arrays?
# If CFZONE is missing, retrieve them all from CF
if [ "$CFZONE" = "" ]; then
echo "Missing zone"
if ! [ "$GETID" == true ]; then exit 2; fi
echo "listing all zones: (if api-key is valid)"
JSON=$(curl -s https://www.cloudflare.com/api_json.html \
-d a=zone_load_multi \
-d tkn=$CFKEY \
-d email=$CFUSER | sed -e 's/":"/:/g')
if [ -n "$(echo $JSON|grep -Po '"err_code:\K[^"]*')" ]; then
echo "Error:" $(echo -e $JSON|grep -Po '"msg:\K[^"]*')
exit 2
fi
for m in $(echo $JSON|grep -Po '"zone_name:\K[^"]*'); do
echo "* $m"
done
echo "Please specify the matching zone in ${0} or specify using the -z flag"
exit 2
fi
# If CFID is missing retrieve and use it
if [ "$CFID" = "" ]; then
echo "Missing DNS record ID"
if ! [ "$GETID" == true ]; then exit 2; fi
echo "fetching from Cloudflare..."
JSON=$(curl -s https://www.cloudflare.com/api_json.html \
-d a=rec_load_all \
-d tkn=$CFKEY \
-d email=$CFUSER \
-d z=$CFZONE | sed -e 's/":"/:/g')
if [ -n "$(echo $JSON|grep -Po '"err_code:\K[^"]*')" ]; then
echo "Error:" $(echo -e $JSON|grep -Po '"msg:\K[^"]*')
exit 2
fi
IDS=($(echo $JSON|grep -Po '"rec_id:\K[^"]*'))
NAMES=($(echo $JSON|grep -Po '"name:\K[^"]*'))
TYPES=($(echo $JSON|grep -Po '"type:\K[^"]*'))
RECORDS="ID RECORD TYPE";
for key in $(seq 0 $((${#IDS[@]}-1)) ); do
if [ "${NAMES[$key]}" == "$CFHOST" ] && [ "${TYPES[$key]}" == "A" ]; then
CFID=${IDS[$key]}
fi
RECORDS="${RECORDS}\n${IDS[$key]} ${NAMES[$key]} ${TYPES[$key]}"
done
if [ -z "$CFID" ]; then
echo " => Incorrect zone, or zone doesn't contain the A-record ${CFHOST}!"
echo "listing all records for zone ${CFZONE}:"
echo -e "$RECORDS"|column -t
exit 2
fi
@gfjardim implemented your changes, works great. You are missing an "fi" at the end.
@vhugo and @heypete made the updates as well.
@larrybolt thanks for the script. Simple and effective.
@toxiicdev Using a CNAME to a no-ip address has drawbacks including revealing your real IP address and some lookup performance considerations.
line 72: getopts: not found
I have a dual WAN connection. One of the connections is a static IP address but the faster consumer line is a dynamic ip address. This script helped me solve the problem of tracking it. :)
Hello,
I update my (dynamic) ip cloudflare by pfsense. I normally use API v1. Now cloudflare will close it and i have to use API v4.
For v1 i use this url =
https://www.cloudflare.com/api_json.html?a=rec_edit&tkn=d82f21f45473a631584b06b4xxxxxxxxxxxxxxxxxx&id=xxxxxx9306&[email protected]&z=xxxxxxxxx.info&type=A&name=xxxxxxxxxx.info&content=%IP%&service_mode=0&ttl=1
For v4 it suppose to be =
https://api.cloudflare.com/client/v4...... .....
Need help...
use this script for v4 https://gist.github.com/benkulbertis/fff10759c2391b6618dd
WARNING Noob here. :)
Can this be into a DD-WRT config via their Command Shell or other means? If so, which of the above methods is best suited for DD-WRT methods?
If not, any ideas on how to effect the change in CF via a DD-WRT router?
I recently made a script that you all may be of interest to you all. Here is a link: Github link
Thanks, nice script 😁
I had an error => Incorrect zone, or zone doesn't contain the A-record myhost.mydomain.com!
, even though it was on list.
@vhugo has the fix above but I'll post it here again:
On line 144 replace grep -Eo "(^|\s)(\d+)(\s|$)
with grep -Eo "(^|\s)([0-9]+)(\s|$)
because grep -E doesn't have the \d
escape. Alternatively you can use grep -Po "(^|\s)([0-9]+)(\s|$)
"` which uses posix regex (which most of us are more familiar with).
I am using grep (GNU grep) 2.27.
Another problem I came across was multiple matches when trying to update the root domain. For example, trying to update example.com
would update a.example.com
because it contains the word example.com
. The fix for this is:
On line 142 replace grep -E "${CFHOST//./\\.}"
with grep -E "\s${CFHOST//./\\.}\s"
. The \s
means that the grep expression will only match the whole word as opposed to parts of a word.
Forked to https://gist.github.com/DarkMatterMatt/b366e7706693edc23b8aed3cd50525a9
A very long time ago I linked my versions of these scripts.
I've just got around to updating these scripts to the latest Cloudflare APIs, and pushed them to here. They're still aimed mainly at running via crontab on a raspberry pi, so tested on Raspbian Stretch.
This link is broken. Would you care to update it?
Sure. Just changed my username since then. New link is:
Updated this script to work again, it's not a cloudflare cli management tool, it just does the job