What's new

Solved Need help setting up DDNS for cloudflare

  • SNBForums Code of Conduct

    SNBForums is a community for everyone, no matter what their level of experience.

    Please be tolerant and patient of others, especially newcomers. We are all here to share and learn!

    The rules are simple: Be patient, be nice, be helpful or be gone!

an303042

Occasional Visitor
EDIT: nevermind, figured it out with chatgpt :)

Hello,
Running asus-merlin on RT-AX86U.
Recently switched ISPs and my new one isn't able to provide me with a static IP so I need to set up DDNS.
I'm in no way an IT/network professional, but more of a hobbyist.
I have a few self hosted services and a domain, and I use cloudflare's dns.
I've figured out that I need to use a custom script, and am trying to follow this script example - https://github.com/RMerl/asuswrt-merlin.ng/wiki/DDNS-Sample-Scripts#cloudflare

I would appreciate any and all help.
Thanks in advance!
 
Last edited:
Well, since OP didn't share his solution. I will post mine here for those struggling to get this to work.

Solution 1: https://github.com/RMerl/asuswrt-merlin.ng/wiki/DDNS-Sample-Scripts#cloudflare

This one requires the record ID and doesn't seem to update the AAAA record for your ipv6 address. It's also a little bit more complicated, though the record ID can be obtained through the following steps:-

a. Log in to your Cloudflare account.
b. Go to the "My Profile" section.
c. Under the "API Tokens" tab, create a new token with the necessary permissions to access DNS records.
d. Open your terminal in any linux system and use curl to make a GET request to list DNS records:

Code:
curl -X GET "https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records" \
-H "Authorization: Bearer {API_TOKEN}" \
-H "Content-Type: application/json"

Note: Replace {zone_id} with the ID of your zone and {API_TOKEN} with your Cloudflare API token.

f. After executing the command, the response will contain the DNS records for your zone, including their record IDs. Locate the specific DNS record you are interested in, and note down its record ID.


Solution 2: https://github.com/alphabt/asuswrt-merlin-ddns-cloudflare/blob/master/ddns-start

Solution 3: - A simplified script from solution 2, which is what I'm using:

Code:
#!/bin/sh

API_TOKEN=""      # Your API Token
ZONE_ID=""        # Your zone id, hex16 string
RECORD_NAME=""    # Your DNS record name, e.g. sub.example.com
RECORD_TTL="1"    # TTL in seconds (1=auto)
UPDATE_IPv6=true # Set to true if you want to also update IPv6

get_dns_record_ids() {
  local record_name=$1
  local type=$2
  local api_token=$3
  local zone_id=$4

  RESPONSE="$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records?type=${type}&name=${record_name}" \
    -H "Authorization: Bearer ${api_token}" \
    -H "Content-Type:application/json")"

  echo $RESPONSE | python -c "
import sys, json

data = json.load(sys.stdin)
for record in data['result']:
    print (record['id'])"
}

update_dns_record() {
  local record_name=$1
  local record_id=$2
  local type=$3
  local ip=$4
  local record_ttl=$5
  local api_token=$6
  local zone_id=$7

  curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records/${record_id}" \
    -H "Authorization: Bearer ${api_token}" \
    -H "Content-Type: application/json" \
    --data "{\"type\":\"${type}\",\"name\":\"${record_name}\",\"content\":\"${ip}\",\"ttl\":${record_ttl},\"proxied\":false}"
}

RESULT=true

# Update IPv4
IPv4=${1}
A_RECORD_IDS=$(get_dns_record_ids $RECORD_NAME A $API_TOKEN $ZONE_ID)

for A_RECORD_ID in $A_RECORD_IDS; do
  RESPONSE="$(update_dns_record $RECORD_NAME $A_RECORD_ID A $IPv4 $RECORD_TTL $API_TOKEN $ZONE_ID)"
  echo $RESPONSE | grep '"success":\ *true' >/dev/null

  if [ $? -eq 0 ]; then
    logger "Updated A record for ${RECORD_NAME} to ${IPv4}"
  else
    logger "Unable to update A record for ${RECORD_NAME}"
    RESULT=false
  fi
done

if [ "$UPDATE_IPv6" == true ]; then
  # Update IPv6
  IPv6=$(ip -6 addr list scope global $device | grep -v " fd" | sed -n 's/.*inet6 \([0-9a-f:]\+\).*/\1/p' | head -n 1)
  AAAA_RECORD_IDS=$(get_dns_record_ids $RECORD_NAME AAAA $API_TOKEN $ZONE_ID)

  for AAAA_RECORD_ID in $AAAA_RECORD_IDS; do
    RESPONSE="$(update_dns_record $RECORD_NAME $AAAA_RECORD_ID AAAA $IPv6 $RECORD_TTL $API_TOKEN $ZONE_ID)"
    echo $RESPONSE | grep '"success":\ *true' >/dev/null

    if [ $? -eq 0 ]; then
      logger "Updated AAAA record for ${RECORD_NAME} to ${IPv6}"
    else
      logger "Unable to update AAAA record for ${RECORD_NAME}"
      RESULT=false
    fi
  done
fi

if [ "$RESULT" == true ]; then
  /sbin/ddns_custom_updated 1
else
  /sbin/ddns_custom_updated 0
fi


For both solution 2 and 3, you just have to copy the above script to the ddns-start script with the following command

Code:
nano /jffs/scripts/ddns-start

And fill in the details of

API_TOKEN="" # Your API Token
ZONE_ID="" # Your zone id, hex16 string
RECORD_NAME="" # Your DNS record name, e.g. sub.example.com

that can be found from cloudflare account.
 
Sorry for not posting my solution. Should have thought of it.

My script is very similar, with some added retries in case of failure, and discord notifications.

Thanks for cleaning up after me!


Edit:
Figured I'd add my (and ChatGPTs) script, in case someone finds it useful. I've added checks comparing the IPs because I kept getting notification of IP change even when there wasnt actually a change, due to the watchdog. I'm sure there are much more elegant ways to carry this stuff out, but since I've never written any code in my life, I'm pretty happy with it.
Configuration variables need changing at the top of the script -


Code:
#!/bin/sh

# Configuration variables
LAST_IP_FILE="/jffs/scripts/last_ip.txt"
LOG_FILE="/jffs/scripts/ddns-update.log"
ZONEID="YOUR_ZONE_ID"
RECORDID="YOUR_RECORD_ID"
RECORDNAME="YOUR_RECORD_NAME"
API="YOUR_API_KEY"
WEBHOOK_URL="DISCORD_HOOK_URL"
IP=${1}  # The new IP address
MAX_RETRIES=5
RETRY_DELAY=30  # Delay in seconds
MAX_LOG_SIZE=102400

# Check and truncate the log file if it exceeds the maximum size
if [ -f "$LOG_FILE" ]; then
    log_size=$(ls -l "$LOG_FILE" | awk '{print $5}')
    if [ $log_size -gt $MAX_LOG_SIZE ]; then
        > "$LOG_FILE"  # Truncate the log file
    fi
fi

# Function to log messages with a timestamp
log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" 2>&1
}

# Function to send a notification to Discord with the update status
send_discord_notification() {
    local status="$1"  # The status message to be sent (Success or Fail)
    local message="Cloudflare DDNS update for $RECORDNAME: $status."
 
  # Use curl to send a POST request to Discord Webhook
  curl -s -H "Content-Type: application/json" \
       -d "{\"content\": \"$message\"}" \
       $WEBHOOK_URL >> "$LOG_FILE" 2>&1
}

# Start of the script logging
log_message "DDNS update script started."

# Read the last known IP address from the file, if it exists
if [ -f "$LAST_IP_FILE" ]; then
    LAST_IP=$(cat "$LAST_IP_FILE")
else
    LAST_IP=""
fi

# Log the IP comparison
log_message "Comparing LAST_IP: $LAST_IP with NEW IP: $IP"

# Proceed with the update if the last_ip.txt is empty or the IP has changed
if [ -z "$LAST_IP" ] || [ "$IP" != "$LAST_IP" ]; then
    echo "$IP" > "$LAST_IP_FILE" # Update the last known IP
    log_message "Updating IP address to: $IP"

    # Attempt to update the DNS record with a retry loop
    retry_count=0
    while [ $retry_count -lt $MAX_RETRIES ]; do
        # Update the A record on Cloudflare
        update_response=$(curl -fsS -o /dev/null -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONEID/dns_records/$RECORDID" \
          -H "Authorization: Bearer $API" \
          -H "Content-Type: application/json" \
          --data "{\"type\":\"A\",\"name\":\"$RECORDNAME\",\"content\":\"$IP\",\"ttl\":120,\"proxied\":true}" \
          --write-out "%{http_code}" 2>/dev/null)
       
        # Check the response status from Cloudflare's API
        if [ "$update_response" -eq 200 ]; then
            log_message "Cloudflare DNS record updated successfully."
            send_discord_notification "Success: New IP is $IP"  # Notify Discord about the successful update
            /sbin/ddns_custom_updated 1 # Notify the router's DDNS service of the successful update
            exit 0 # Exit the script successfully
        else
            log_message "Failed to update Cloudflare DNS record. Response code: $update_response. Attempt $((retry_count + 1)) of $MAX_RETRIES."
            retry_count=$((retry_count + 1)) # Increment the retry counter
            if [ $retry_count -lt $MAX_RETRIES ]; then
                log_message "Retrying in $RETRY_DELAY seconds..."
                sleep $RETRY_DELAY # Wait before retrying
            else
                log_message "Maximum retry attempts reached. Giving up."
                send_discord_notification "Fail: Could not update after $MAX_RETRIES attempts."  # Notify Discord about the failure after all retries
                /sbin/ddns_custom_updated 0 # Notify the router's DDNS service of the failed update
                exit 1 # Exit the script with an error
            fi
        fi
    done
else
    log_message "IP address has not changed. No update needed." # Log if the IP address remains the same
fi

# Log the end of the script
log_message "DDNS update script finished."
 
Last edited:

Similar threads

Latest threads

Sign Up For SNBForums Daily Digest

Get an update of what's new every day delivered to your mailbox. Sign up here!
Top