Skip to content

Instantly share code, notes, and snippets.

@lyoshenka
Forked from kevinoconnor7/c-ddns.sh
Last active October 11, 2023 04:28
Show Gist options
  • Save lyoshenka/6257440 to your computer and use it in GitHub Desktop.
Save lyoshenka/6257440 to your computer and use it in GitHub Desktop.
Quick and dirty DDNS using Bash and Cloudflare (API v4 compatible)
#!/usr/bin/env bash
# Step 1: Fill in EMAIL, TOKEN, DOMAIN and SUBDOMAIN. Your API token is here: https://dash.cloudflare.com/profile/api-tokens
# Make sure the token is the Global token, or has these permissions: #zone:read, #dns_record:read, #dns_records:edit
# If you want to set the root domain instead of a subdomain, set SUBDOMAIN to "@"
# 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 of the record you created.
# Fill in ZONE_ID and REC_ID below
# This step is optional, but will save you 2 requests every time you run this script
# Step 4: Run "./ddns.sh". It should tell you that record was updated or that it didn't need updating.
# Step 5: Run it every hour with cron. Use the '-s' flag to silence normal output
# 0 * * * * /path/to/ddns.sh -s
EMAIL=''
TOKEN=''
DOMAIN=''
SUBDOMAIN=''
ZONE_ID=''
REC_ID=''
set -euo pipefail
VERBOSE="true"
LOOKUP="false"
while getopts ":lsd" opt; do
case ${opt} in
l ) LOOKUP="true" ;;
s ) VERBOSE="false" ;;
d ) set -x ;; # for debugging
\? ) echo -e "Usage: $(basename "$0") [-l] [-s] [-d]\nRead the script source for detailed instructions" && exit 1 ;;
esac
done
[[ "$SUBDOMAIN" = "@" ]] && FULL_DOMAIN="$DOMAIN" || FULL_DOMAIN="$SUBDOMAIN.$DOMAIN"
API_URL="https://api.cloudflare.com/client/v4"
CURL="curl -s \
-H Content-Type:application/json \
-H X-Auth-Key:$TOKEN \
-H X-Auth-Email:$EMAIL "
if [ -z "$ZONE_ID" ] || $LOOKUP; then
ZONE_ID="$($CURL "$API_URL/zones?name=$DOMAIN" | sed -e 's/[{}]/\n/g' | grep '"name":"'"$DOMAIN"'"' | sed -e 's/,/\n/g' | grep '"id":"' | cut -d'"' -f4)"
$VERBOSE && echo "ZONE_ID='$ZONE_ID'"
fi
if [ -z "$REC_ID" ] || $LOOKUP; then
#REC_ID="$($CURL "$API_URL/zones/$ZONE_ID/dns_records" | grep -C 5 '"name": "'"$FULL_DOMAIN"'"' | grep '"id": "' | cut -d'"' -f4)"
REC_ID="$($CURL "$API_URL/zones/$ZONE_ID/dns_records" | sed -e 's/[{}]/\n/g' | grep '"name":"'"$FULL_DOMAIN"'"' | sed -e 's/,/\n/g' | grep '"id":"' | cut -d'"' -f4)"
$VERBOSE && echo "REC_ID='$REC_ID'"
fi
$LOOKUP && exit 0
set +e
for IP_URL in "http://ifconfig.me/ip" "http://ipv4.icanhazip.com"; do
IP="$(curl -s "$IP_URL")"
[ -n "$IP" ] && break
done
set -e
if [ -z "$IP" ]; then
echo "Could not get external IP"
exit 1
fi
RECORD_IP="$($CURL "$API_URL/zones/$ZONE_ID/dns_records/$REC_ID" | grep -o '"content":"[^"]\+' | cut -d '"' -f4)"
if [ "$IP" == "$RECORD_IP" ]; then
$VERBOSE && echo "IP Unchanged"
exit 0
fi
$VERBOSE && echo "Setting IP to $IP"
$CURL -X PUT "$API_URL/zones/$ZONE_ID/dns_records/$REC_ID" --data '{"type":"A","name":"'"$SUBDOMAIN"'","content":"'"$IP"'","proxied":false}' 1>/dev/null
exit 0
@MachineITSvcs
Copy link

For anyone interested, I wrote a similar script in bash for use with multiple Cloudflare accounts, zones, records, and a proxy option: here

@LetMeDecay
Copy link

reaplace L42
REC_ID="$($CURL "$API_URL/zones/$ZONE_ID/dns_records?name=$SUBDOMAIN" | awk '{t=$0;gsub(/.*"id":"|".*/,"",t);print t}')"

@waiyanwh
Copy link

replace l37

ZONE_ID="$($CURL "$API_URL/zones?name=$DOMAIN" | jq '.result[0].id'

@lyoshenka
Copy link
Author

@waiyanwh that works but i'd like to avoid dependence on extra things like jq

@frankang
Copy link

frankang commented May 2, 2020

cloudflare has changed its response format, so this line
| sed -e 's/[{}]/\n/g' | grep '"name":"'"$SUBDOMAIN"'.'"$DOMAIN"'"' | sed -e 's/,/\n/g' | grep '"id": "' | cut -d'"' -f4 doesn't work now.
REC_ID needs to be manually set. (you can get your id by uncomment the -x in the ddns.sh script, then manually curl request the dns_records path as given by the debug info )

@lyoshenka
Copy link
Author

Thanks @frankang. I updated the grep commands so it finds the correct record. Should work now

@ImmortalScientist
Copy link

@lyoshenka

Wondering the same as Agazed did years ago. Is it at all possible to set the root domain using this script?

@lyoshenka
Copy link
Author

lyoshenka commented May 25, 2020

@ImmortalScientist @Agazed - I just updated the script to support root domains. Set SUBDOMAIN to @ and give it a try. Let me know if that doesn't work for you.

@CyberPoison
Copy link

Hi, i'm trying to get it working with monit but unfortunatelly looks like monit doesn't run the script btw when i run it myself everything works.
Do you have any idea ?

Perhaps there is my command on monit

check host xxxxxx with address xxxxxx
  if failed ping4 then exec "/bin/bash -c /etc/monit/SecondaryIP.sh"
  else if succeeded then exec "/bin/bash -c /etc/monit/NativeIP.sh"

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