I setup something similar with my own custom domain, using a free Cloudflare account to get the DDNS functionality. I looked at the packages available for dynamic DNS functionality but ended up modifying code I found online and putting together a shell script that does everything for me - this just gets run with cron. I think I might have needed to install a package to get the dig
command to work (can’t recall).
YMMV, the code is not well documented, probably breaks all proper conventions, and if this breaks your setup I take no responsibility, but for what it’s worth, my shell script looks like this:
#!/bin/bash
# Step 1: Fill in EMAIL, TOKEN, ZONE_NAME (domain) and REC_NAME (subdomain). Your token is here: https://www.cloudflare.com/my-account
# Step 2: Create an A record on Cloudflare with the subdomain you chose
# Step 3: Run "./ddns.sh -l" to get the ZONE_ID and REC_ID for the domain and subdomain.
# Fill in REC_ID and ZONE_ID below
# Step 4: Run "./ddns.sh". It should tell you that record was updated or that it didn't need updating.
# Step 5: Run regularly with cron.
# */5 * * * * /path/to/ddns.sh > /path/to/log/file
# Cloudflare parameters
EMAIL='youremailaddress'
TOKEN='yourCFtoken'
ZONE_NAME='yourtopleveldomain'
ZONE_ID='yourCFzoneid'
REC_NAME='yourCFsubdomain'
REC_ID='yourCFsubdomainrecordID'
# If specified, obtain and print records then exit
if [ "$1" == "-l" ]; then
ZONE_ID=$( curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$ZONE_NAME" \
-H "X-Auth-Email: $EMAIL" \
-H "X-Auth-Key: $TOKEN" \
-H "Content-Type: application/json" \
| grep -Po '(?<="id":")[^"]*' \
| head -1 )
REC_ID=$( curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?name=$REC_NAME" \
-H "X-Auth-Email: $EMAIL" \
-H "X-Auth-Key: $TOKEN" \
-H "Content-Type: application/json" \
| grep -Po '(?<="id":")[^"]*' )
echo Zone Name: $ZONE_NAME
echo Zone ID: $ZONE_ID
echo Record Name: $REC_NAME
echo Record ID: $REC_ID
exit 0
fi
# If specified, obtain and print A record then exit
if [ "$1" == "-a" ]; then
CFIP=$( curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=A&name=$REC_NAME&match=all" \
-H "X-Auth-Email: $EMAIL" \
-H "X-Auth-Key: $TOKEN" \
-H "Content-Type: application/json" \
| grep -Po '(?<="content":")[^"]*' )
echo Record Name: $REC_NAME
echo A Record: $CFIP
exit 0
fi
# Resolve current IP address via DNS
IP=$(dig +short myip.opendns.com @resolver1.opendns.com)
if ! [[ $IP =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
[ "$1" != "-s" ] && echo "Cannot determine public IP address, exiting"
exit 1
fi
# Resolve domain to IP address
DNSIP=$(host $REC_NAME)
# Exit if domain cannot be resolved
if [ $? -eq 1 ]; then
[ "$1" != "-s" ] && echo "Cannot resolve $REC_NAME, exiting"
exit 1
fi
DNSIP=$(echo $DNSIP | awk '{ print $NF }')
# No action required if current IP matches resolved IP
if [ "$IP" == "$DNSIP" ]; then
[ "$1" != "-s" ] && echo "$REC_NAME resolves to public IP ($IP), exiting"
exit 0
fi
# At this point public IP and resolved IP records exist and do not match
[ "$1" != "-s" ] && echo "Public IP ($IP) does not match DNS record ($DNSIP), checking Cloudflare"
# Retrieve the Cloudflare A record IP address
CFIP=$( curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=A&name=$REC_NAME&match=all" \
-H "X-Auth-Email: $EMAIL" \
-H "X-Auth-Key: $TOKEN" \
-H "Content-Type: application/json" \
| grep -Po '(?<="content":")[^"]*' )
# If Cloudflare IP record matches public IP then there must be a DNS issue, no update required
if [ "$CFIP" == "$IP" ]; then
[ "$1" != "-s" ] && echo "Cloudflare IP ($CFIP) matches public IP ($IP), exiting"
exit 0
fi
# At this point the public IP does not match the IP address stored in Cloudflare
[ "$1" != "-s" ] && echo "Cloudflare IP ($CFIP) does not match public IP ($IP), update required"
[ "$1" != "-s" ] && echo "Setting Cloudflare DNS record to public IP"
# Set Cloudflare DNS record
RESULT=$( curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$REC_ID" \
-H "X-Auth-Email: $EMAIL" \
-H "X-Auth-Key: $TOKEN" \
-H "Content-Type: application/json" \
--data "{\"id\":\"$ZONE_ID\",\"type\":\"A\",\"name\":\"$REC_NAME\",\"content\":\"$IP\"}")
if [[ $RESULT == *"\"success\":false"* ]]; then
echo "API update failed. Dumping results:"
echo -e $RESULT
/home/osmc/push.sh "Cloudflare API update failed, check log for details"
exit 1
else
echo "API update successful"
/home/osmc/push.sh "DNS records have been updated, public IP is $IP"
exit 0
fi
It might not be pretty, but I have had this script running every 5 minutes for the past 6 months and it’s worked perfectly, including the basic error catching that I built into it.
Each time the script updates my DDNS records it also pushes a notification to my phone - details on how that was set up is in my HowTo.