-
-
Save chappy84/9606755 to your computer and use it in GitHub Desktop.
#!/bin/sh | |
# | |
# CloudFlare Dynamic DNS | |
# | |
# Updates CloudFlare records with the current public IP address | |
# | |
# Takes the same basic arguments as A/CNAME updates in the CloudFlare v4 API | |
# https://www.cloudflare.com/docs/client-api.html#s5.2 | |
# | |
# Use with cron jobs etc. | |
# | |
# e.g. | |
# | |
# manually run: | |
# cloudflare_dyn_dns.sh -key 404613183ab3971a2118ae5bf03d63e032f9e -email [email protected] -zone example.com -name extra | |
# | |
# cronjob entry to run every 5 minutes: | |
# */5 * * * * /path/to/cloudflare_dyn_dns.sh -key 404613183ab3971a2118ae5bf03d63e032f9e -email [email protected] -zone example.com -name extra >> /path/to/cloudflare_dyn_dns.log | |
# | |
# will both set the type A DNS record for extra.example.com to the current public IP address for user [email protected] with the provided API key | |
key= | |
email= | |
zone= | |
zone_id= | |
type=A | |
rec_id= | |
name= | |
content= | |
ttl=1 | |
proxied=false | |
while [ "$1" != "" ]; do | |
case $1 in | |
-key ) shift | |
key=$1 | |
;; | |
-email ) shift | |
email=$1 | |
;; | |
-zone ) shift | |
zone=$1 | |
;; | |
-zone_id ) shift | |
zone_id=$1 | |
;; | |
-type ) shift | |
type=$1 | |
;; | |
-rec_id ) shift | |
rec_id=$1 | |
;; | |
-name ) shift | |
name=$1 | |
;; | |
-content ) shift | |
content=$1 | |
;; | |
-ttl ) shift | |
ttl=$1 | |
;; | |
-proxied ) shift | |
proxied=$1 | |
;; | |
* ) echo "unknown parameter $1" | |
exit 1 | |
esac | |
shift | |
done | |
if [ "$content" = "" ] | |
then | |
content=`curl -s http://myexternalip.com/raw` | |
if [ "$content" = "" ] | |
then | |
date | |
echo "No IP address to set record value with." | |
exit 1 | |
fi | |
fi | |
if [ "$name" = "" ] | |
then | |
echo "You must provide the name of the record you wish to change." | |
exit 1 | |
fi | |
if [ "$zone" = "" ] | |
then | |
echo "You must provide the domain you wish to change." | |
exit 1 | |
fi | |
if [ "$name" = "$zone" ] | |
then | |
hostname="$name" | |
else | |
hostname="$name.$zone" | |
fi | |
command -v host > /dev/null 2>&1 | |
if [ "$?" = "1" ] | |
then | |
command -v nslookup > /dev/null 2>&1 | |
if [ "$?" = "1" ] | |
then | |
echo "Cannot find a way to check existing $type record for $hostname" | |
exit 1 | |
fi | |
existing_content=`nslookup -type=$type $hostname | awk -F 'Address: ' 'NR==6 { print $2 }'` | |
else | |
existing_content=`host -t $type $hostname | sed -E 's/.+?\s+([^\s]+)$/\1/'` | |
fi | |
if [ "$content" = "$existing_content" ] | |
then | |
echo "Existing record value $existing_content is the same as provided content $content. Exiting." | |
exit | |
fi | |
if [ "$key" = "" ] | |
then | |
echo "You must provide your user API token." | |
exit 1 | |
fi | |
if [ "$email" = "" ] | |
then | |
echo "You must provide your user email." | |
exit 1 | |
fi | |
# Get the zone id for the entry we're trying to change if it's not provided | |
if [ "$zone_id" = "" ] | |
then | |
zone_response_json=`curl -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone" -H "X-Auth-Email: $email" -H "X-Auth-Key: $key" -H "Content-Type: application/json"` | |
zone_id=`echo $zone_response_json | sed -E "s/.+\"result\":\[\{\"id\":\"([a-f0-9]+)\"[^\}]+$zone.+/\1/g"` | |
if [ "$zone_id" = "" ] | |
then | |
echo "Cloudflare DNS Zone id could not be found, please make sure it exists" | |
exit 1 | |
fi | |
fi | |
# Get the record id for the entry we're trying to change if it's not provided | |
if [ "$rec_id" = "" ] | |
then | |
rec_response_json=`curl -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?name=$hostname" -H "X-Auth-Email: $email" -H "X-Auth-Key: $key" -H "Content-Type: application/json"` | |
rec_id=`echo $rec_response_json | sed -E "s/.+\"result\":\[\{\"id\":\"([a-f0-9]+)\"[^\}]+\"type\":\"$type\"[^\}]+$hostname.+/\1/g"` | |
if [ "$rec_id" = "" ] | |
then | |
echo "Cloudflare DNS Record id could not be found, please make sure it exists" | |
exit 1 | |
fi | |
fi | |
# Update the DNS record | |
update_response=`curl -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$rec_id" -H "X-Auth-Email: $email" -H "X-Auth-Key: $key" -H "Content-Type: application/json" --data "{\"id\":\"$rec_id\",\"type\":\"$type\",\"name\":\"$hostname\",\"content\":\"$content\",\"ttl\":$ttl,\"proxied\":$proxied}"` | |
success_val=`echo $update_response | sed -E "s/.+\"success\":(true|false).+/\1/g"` | |
if [ "$success_val" = "true" ] | |
then | |
echo "Record Updated." | |
else | |
echo "Record update failed." | |
exit 1 | |
fi |
add "content=" )))
Hi @anshgh
apologies for the delayed response.
You are quite correct, using an command line argument of -content 8.8.8.8
will ensure the script ignores the current public IP address and sets the content of the DNS record to 8.8.8.8
The script was originally only intended for boxes with one public IP address, hence defaulting to the public IP address of whichever is the default network adapter
got Stuck at line "++ curl -s http://myexternalip.com/raw"
replaces it with this line, to make it work:
curl -s checkip.dyndns.org | sed -e 's/.Current IP Address: //' -e 's/<.$//'
Not sure why this isn't working for you @ftahirops , possibly some network issues? Could investigate with a traceroute
?
If myexternalip.com doesn't work, can I suggest using a provider that doesn't require string a replace on the returned content? e.g. https://api.ipify.org/ or http://ipv4.icanhazip.com/
I've got this
./cloudflare.sh -key <redacted> -email <redacted> -zone mandarin.web.id -name mandarin.web.id
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1713 0 1713 0 0 5023 0 --:--:-- --:--:-- --:--:-- 5023
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 135 0 135 0 0 403 0 --:--:-- --:--:-- --:--:-- 404
curl: (3) [globbing] nested brace in column 99
Record update failed.
@wsokc I edited your comment to redact the key and e-mail in-case these were valid for your Cloudflare account. May I also suggest you refresh the key via Cloudflare's UI, otherwise if they were valid you may find some of your DNS records getting modified without your permission for anyone that had a copy.
I've updated the script to deal with the scenario you've provided. The script was originally written to deal with sub-hosts, not with the top level domain record, it should now deal with this scenario (e.g. it could deal with the record for www.example.com, but not for example.com . It should now deal with both.)
Anyone looking at this, it's also worth noting, ddclient
now allows updates of Cloudflare (make sure you use v3.9+)
This may be easier with your setup, e.g.
/etc/ddclient/ddclient.conf
use=web, web=https://myexternalip.com/raw
ssl=yes
syslog=yes
# Cloudflare
protocol=cloudflare
zone=example.com
ttl=1
[email protected]
password=404613183ab3971a2118ae5bf03d63e032f9e
extra.example.com
I got this
./cloudflare.sh -key <redacted> -email <redacted> -zone abc.com -name 999
...
curl: (3) [globbing] nested brace in column 99
Update: got it worked by edit line 149 with jq
rec_id=`echo $rec_response_json | jq -r '.result[].id'`
I had the same problem as hitmanbabyvn, and his fix also fixed my problem. Thanks!
I've got this
./cloudflare.sh -key <redacted> -email <redacted> -zone mandarin.web.id -name mandarin.web.id % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 1713 0 1713 0 0 5023 0 --:--:-- --:--:-- --:--:-- 5023 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 135 0 135 0 0 403 0 --:--:-- --:--:-- --:--:-- 404 curl: (3) [globbing] nested brace in column 99 Record update failed.
You could use this script to fix(as cloudflare changed its response, the regex should be changed as well)
rec_response_json=`curl -X GET "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?name=$hostname" -H "X-Auth-Email: $email" -H "X-Auth-Key: $key" -H "Content-Type: application/json"`
rec_id=`echo $rec_response_json | sed -E "s/.+\"result\":\[\{\"id\":\"([a-f0-9]+)\"[^\}]+\$hostname\",\"type\":\"$type\"[^\}]+.+/\1/g"`
@d0zingcat that's not working for me
I'm getting
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 185 0 185 0 0 1201 0 --:--:-- --:--:-- --:--:-- 1201
curl: (3) nested brace in URL position 70:
https://api.cloudflare.com/client/v4/zones/{"success":false,"errors":[{"code":6003,"message":"Invalid request headers","error_chain":[{"code":6103,"message":"Invalid format for X-Auth-Key header"}]}],"messages":[],"result":null}/dns_records?name=<redacted_url>
^
Cloudflare DNS Record id could not be found, please make sure it exists
@alexkreidler yours is failing, as it says in the embedded JSON, because:
"Invalid format for X-Auth-Key header"
Your API key is invalid. I'd recommend starting by looking at that.
As for the changes @d0zingcat has suggested, the literal \$hostname
added (which won't be variable interpolated due to the preceding slash) shouldn't be part of the ID in the returned JSON according to the API, it should just be a hash: https://api.cloudflare.com/#dns-records-for-a-zone-list-dns-records
For those using jq
in their scripts, I deliberately didn't use this when I wrote it, as it's an optional extra on most unix box base installs. The point, in-part, was to avoid having to install any extra packages on the box. If you're installing extra packages, I'd recommend using ddclient
instead, as above.
I got this
./cloudflare.sh -key <redacted> -email <redacted> -zone abc.com -name 999 ... curl: (3) [globbing] nested brace in column 99
Update: got it worked by edit line 149 with jq
rec_id=`echo $rec_response_json | jq -r '.result[].id'`
Owner
Author
@chappy84
use-H "Authorization: Bearer $key"
This would require a separate input variable -token
. Tokens and Keys are different and confusing the use of this variable would not be a good thing to do.
If you'd prefer to use tokens, please feel free to fork the script 🙂
and
new rec_respose have zone_id
so
use
echo $rec_response_json | sed -E "s/.+\"result\":\[\{\"id\":\"([a-f0-9]+)\"[^\}].+/\1/g"
If you're referring to line 149, the expanded regex including the type
check was added to make sure the correct record type was being retrieved. for the correct hostname
, e.g. in-case these are on the base domain, and there is say A
, AAAA
, and MX
records for example.com
.
@0neday if that linked script is what you're trying to achieve, and update multiple records with this one script, then this script isn't for that. Feel free to fork, and do what you wish.
As for when $type doesn't exist in the returned json, then by removing that out of the check, you're masking what could be a much bigger issue, as you've easily got a chance of changing the wrong DNS record, hence, as explained before, why the check is there, to get the correct ID for the correct DNS record type, on the correct hostname
@chappy84 thank you for reply. I use your scripts cannot get record_id
, I just do a little of optimization. thanks again for your method that use sed
to parse json
.
@0neday Regular expressions shouldn't be used to parse JSON (similar to the reasons why you shouldn't parse X/HTML with RegEx). This script is a perfect example of why not, as it relies on the order of the properties in the object, which the JSON spec specifically defines as incorrect. If cloudflare have switched / do switch to a server-side technology that doesn't always output the properties in that specific order, then this will break.
As in my last response, by hacking around with that regex, you're masking the issue, and potentially creating a far worse issue by updating the wrong records. Please consider using a much better solution, i.e. not bash, and use something like ddclient
instead.
Anyone coming to this, please consider this script "abandonware".
I've not maintained it, or used it myself, in years, instead switching to ddclient
. It was a quick hacky script that's purpose can be much better served, as mentioned above, by using ddclient
, now that they support cloudflare, which they didn't when I wrote this script.
If I could archive it (like you can projects on github), I would, but they've not ported that feature to gists yet.
N/B: Comments after this will be deleted to make this more obvious, so it's not lost in a sea of comments.
Thank you!
script works:
[root @ router1 sbin] # /etc/rc.d/cf_eth0.sh
Attempting to get current public IP Address for content.
Record Updated.
But I there are several IP addresses. How can I update a specific (one of the four IP)?