Created
July 23, 2025 22:22
-
-
Save AssortedFantasy/a60aa79b6060358ff742d45eef0dd84b to your computer and use it in GitHub Desktop.
Cloudflare DDNS CRON Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ============================================================================== | |
# Example Configuration for Cloudflare DDNS Script (v3) | |
# | |
# Instructions: | |
# To add a DNS record to update, you must add a new entry to EACH of the | |
# five arrays below, making sure they have the same index. | |
# The first record is at index 0, the second at index 1, and so on. | |
# ============================================================================== | |
# --- Record 1 --- | |
dns_records[0]="YOUR_RECORD_NAME_HERE" # e.g., "home.yourdomain.com" | |
zone_ids[0]="YOUR_ZONE_ID_HERE" | |
api_tokens[0]="YOUR_CLOUDFLARE_API_TOKEN_HERE" | |
ttls[0]=1 # Use 1 for "Auto", or a value between 120-7200 | |
proxied_statuses[0]="true" # Must be "true" or "false" | |
# --- To add a second record, uncomment and fill out the lines below --- | |
# dns_records[1]="ANOTHER_RECORD_NAME" | |
# zone_ids[1]="ANOTHER_ZONE_ID" | |
# api_tokens[1]="ANOTHER_API_TOKEN" | |
# ttls[1]=1 | |
# proxied_statuses[1]="true" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# ============================================================================== | |
# Cloudflare Dynamic DNS Update Script | |
# | |
# Description: | |
# This script updates Cloudflare DNS 'A' records with the machine's current | |
# external IP address. It supports per-record configuration for zone ID, | |
# API token, TTL, and proxy status. | |
# | |
# Dependencies: | |
# - curl: For making API requests. | |
# - jq: For robustly parsing JSON responses from the Cloudflare API. | |
# | |
# Author: Gemini | |
# Version: 3.1 | |
# ============================================================================== | |
set -eo pipefail # Exit on error and on pipe failure | |
# --- Helper Functions --- | |
# Centralized logging functions. | |
log_error() { | |
echo "[$(date "+%Y-%m-%d %H:%M:%S")] ERROR: $1" | |
} | |
log_success() { | |
echo "[$(date "+%Y-%m-%d %H:%M:%S")] SUCCESS: $1" | |
} | |
log_info() { | |
echo "[$(date "+%Y-%m-%d %H:%M:%S")] INFO: $1" | |
} | |
# --- Script Setup --- | |
# Get the directory where the script is located | |
parent_path="$(dirname "${BASH_SOURCE[0]}")" | |
LOG_FILE="${parent_path}/update-cloudflare-dns.log" | |
# Redirect all stdout and stderr to a log file and also print to the console | |
exec > >(tee -a "$LOG_FILE") 2>&1 | |
# --- Dependency Check --- | |
if ! command -v jq &> /dev/null; then | |
log_error "'jq' is not installed. Please install it to continue. (e.g., 'sudo apt-get install jq' or 'brew install jq')" | |
exit 1 | |
fi | |
if ! command -v curl &> /dev/null; then | |
log_error "'curl' is not installed. Please install it to continue." | |
exit 1 | |
fi | |
# --- Configuration Loading --- | |
# Load config file passed as an argument, or default to update-cloudflare-dns.conf | |
config_file="${1:-${parent_path}/update-cloudflare-dns.conf}" | |
if [[ ! -f "$config_file" ]]; then | |
log_error "Configuration file not found at ${config_file}" | |
exit 1 | |
fi | |
source "$config_file" | |
# --- Get External IP Address --- | |
external_ip=$(curl -4 -s -X GET https://checkip.amazonaws.com --max-time 15) | |
ipv4_regex='^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])\.){3}(25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])$' | |
if ! [[ "$external_ip" =~ $ipv4_regex ]]; then | |
log_error "Failed to get a valid external IP. Received: '${external_ip}'" | |
exit 1 | |
fi | |
# --- Main DNS Update Loop --- | |
# Loop through the indices of the dns_records array | |
for i in "${!dns_records[@]}"; do | |
# Extract per-record settings | |
record_name="${dns_records[$i]}" | |
zoneid="${zone_ids[$i]}" | |
api_token="${api_tokens[$i]}" | |
ttl="${ttls[$i]}" | |
proxied="${proxied_statuses[$i]}" | |
# --- Configuration Validation --- | |
if [[ -z "$record_name" || -z "$zoneid" || -z "$api_token" || -z "$ttl" || -z "$proxied" ]]; then | |
log_error "Incomplete configuration for record index ${i}. Please check all parameters." | |
continue # Skip to the next record | |
fi | |
# --- Get current DNS record details from Cloudflare API --- | |
api_url="https://api.cloudflare.com/client/v4/zones/${zoneid}/dns_records?type=A&name=${record_name}" | |
api_response=$(curl -s -X GET "$api_url" \ | |
-H "Authorization: Bearer ${api_token}" \ | |
-H "Content-Type: application/json") | |
if ! echo "$api_response" | jq -e '.success' > /dev/null; then | |
log_error "API call to get info for ${record_name} failed. Response: $(echo "$api_response" | jq .)" | |
continue | |
fi | |
# Extract current details using jq | |
current_ip=$(echo "$api_response" | jq -r '.result[0].content') | |
current_proxied_status=$(echo "$api_response" | jq -r '.result[0].proxied') | |
record_id=$(echo "$api_response" | jq -r '.result[0].id') | |
if [[ "$current_ip" == "null" ]]; then | |
log_error "DNS record '${record_name}' does not exist or could not be found." | |
continue | |
fi | |
# --- Check if an update is needed --- | |
if [[ "$current_ip" == "$external_ip" && "$current_proxied_status" == "$proxied" ]]; then | |
log_info "No change needed for ${record_name}: IP (${current_ip}) and proxy status match." | |
continue | |
fi | |
# --- Update the DNS Record --- | |
update_api_url="https://api.cloudflare.com/client/v4/zones/${zoneid}/dns_records/${record_id}" | |
update_data=$(jq -n \ | |
--arg name "$record_name" \ | |
--arg content "$external_ip" \ | |
--argjson ttl "$ttl" \ | |
--argjson proxied "$proxied" \ | |
'{type: "A", name: $name, content: $content, ttl: $ttl, proxied: $proxied}') | |
update_response=$(curl -s -X PUT "$update_api_url" \ | |
-H "Authorization: Bearer ${api_token}" \ | |
-H "Content-Type: application/json" \ | |
--data "$update_data") | |
if echo "$update_response" | jq -e '.success' > /dev/null; then | |
log_success "Updated ${record_name} to ${external_ip} (proxied: ${proxied}, ttl: ${ttl})" | |
else | |
log_error "Failed to update ${record_name}. Response: $(echo "$update_response" | jq .)" | |
fi | |
done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment