Last active
September 12, 2024 17:30
-
-
Save craeckor/06dc9a40596b640ddd4ff4b5049c167d to your computer and use it in GitHub Desktop.
Script for updating your Cloudflare DNS-Records.
This file contains 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
#!/bin/bash | |
# | |
# Cloudflare DNS-Records updater script V1.4 | |
# Made by craeckor | |
# | |
# This script updates your Cloudflare DNS-Records. | |
# Requirements: bash, jq, curl and awk. | |
# API-Token (Not API-Key) is required. | |
# Only works for one specific Second Level Domain per script. (E.g example.com) Multiple subdomains are supported. | |
# Only works for A-records, AAAA-records are not supported yet. | |
# This script logs everything under /var/log/dns-updater, except if there where no changes made. | |
# This script respects privacy and hides the API-Token and your IP. | |
# API-Token permission: Zone.DNS.Write (Edit) | |
# | |
# Docs: Create API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/ | |
# Docs: Cloudflare API: https://developers.cloudflare.com/api/ | |
# Docs: API token permissions: https://developers.cloudflare.com/fundamentals/api/reference/permissions/ | |
# | |
# Get current date and time for naming logs | |
date=$(date +"%Y-%m-%d_%H-%M-%S") | |
# Get creation time of the log | |
create_log=$(date +"%Y-%m-%d %H:%M:%S") | |
# Function to get current date and time in log format | |
log_date() { | |
echo "$(date +"%Y-%m-%d %H:%M:%S")" | |
} | |
# Function to get current date and time for log messages | |
get_date() { | |
echo "$(date +"%Y-%m-%d_%H-%M-%S") - Cloudflare-DNS-Updater: " | |
} | |
# Check if log directory exists, if not, create it | |
if [ ! -d "/var/log/dns-updater" ]; then | |
mkdir -p /var/log/dns-updater | |
fi | |
# Define log file path | |
log="/var/log/dns-updater/dns-updater_$date.log" | |
# Cloudflare API token | |
api_token="CLOUDFLARE_API_KEY" | |
# List of subdomains to update | |
subdomains=("subdomain1.example.org" "subdomain2.example.org" "subdomain3.example.org" "...") | |
# Extract first subdomain and its top-level domain | |
first_domain="${subdomains[0]}" | |
tld=$(echo "$first_domain" | awk -F'.' '{print $(NF-1)"."$NF}') | |
# Counter for counting updated DNS records | |
counter=0 | |
# Get public IP address | |
ip=$(curl https://api.ipify.org -s) | |
# Mask API token for logging | |
first_chars="${api_token:0:5}" | |
last_chars="${api_token: -5}" | |
middle_chars="*****" | |
masked_token="${first_chars}${middle_chars}${last_chars}" | |
# Mask IP address for logging | |
IFS="." read -r -a ip_sections <<< "$ip" | |
ip_sections[1]="***" | |
ip_sections[2]="***" | |
masked_ip=$(IFS="."; echo "${ip_sections[*]}") | |
# Log start details | |
echo "$(get_date)Create Log" >> $log | |
echo "Start-Date: $create_log" >> $log | |
echo "Script-Path: $0" >> $log | |
echo -e "\n" | tee -a $log | |
echo "$(get_date)API-Token: $masked_token" >> $log | |
echo "$(get_date)Subdomains: ${domains[*]}" >> $log | |
echo "$(get_date)Domain: $tld" >> $log | |
echo "$(get_date)IP: $masked_ip" >> $log | |
echo -e "\n" | tee -a $log | |
# Validate API token | |
token_validation=$(curl -s --request GET \ | |
--url https://api.cloudflare.com/client/v4/user/tokens/verify \ | |
--header "Authorization: Bearer $api_token" \ | |
--header "Content-Type: application/json") | |
valid_token=$(echo $token_validation | jq -r '.success') | |
# Log token validation result | |
echo "$(get_date)Token Validation: $valid_token" >> $log | |
# If token is not valid, exit with error message | |
if [ "$valid_token" = true ]; then | |
echo "$(get_date)API-Token OK" | tee -a $log | |
else | |
echo "$(get_date)API-Token is not valid. Generate a valid one at https://dash.cloudflare.com/profile/api-tokens." | tee -a $log | |
echo "$(get_date)Docs: https://developers.cloudflare.com/fundamentals/api/get-started/create-token" | tee -a $log | |
echo "$(get_date)Exit with Error Code 1..." | tee -a $log | |
exit 1 | |
fi | |
# Get Zone ID | |
zone_data=$(curl -s --request GET \ | |
--url https://api.cloudflare.com/client/v4/zones?name=$tld \ | |
--header "Authorization: Bearer $api_token" \ | |
--header "Content-Type: application/json") | |
zone_id=$(echo $zone_data | jq -r '.result[].id') | |
# Log Zone ID | |
echo "$(get_date)Zone Data: $zone_id" >> $log | |
# If Zone ID is empty, exit with error message | |
if [ "$zone_id" = "" ];then | |
echo "$(get_date)No read and write permission for Zone $tld is granted or Zone $tld doesn't exist. Edit API-Token at https://dash.cloudflare.com/profile/api-tokens and give it Edit permission on Zone.DNS and specify the $tld Zone." | tee -a $log | |
echo "$(get_date)Docs: https://developers.cloudflare.com/fundamentals/api/reference/permissions/" | tee -a $log | |
echo "$(get_date)Exit with Error-Code 1..." | tee -a $log | |
exit 1 | |
fi | |
# Generate a temporary subdomain for permission validation | |
temp_subdomain=$(date +%s | sha256sum | base64 | head -c 33 ; echo) | |
# Validate permission for adding DNS record | |
permission_validation=$(curl -s --request POST \ | |
--url https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records \ | |
--header "Authorization: Bearer $api_token" \ | |
--header "Content-Type: application/json" \ | |
--data "{ | |
\"content\": \"254.254.254.254\", | |
\"name\": \"$temp_subdomain.$tld\", | |
\"proxied\": false, | |
\"type\": \"A\", | |
\"comment\": \"API-Token verification record\", | |
\"ttl\": 1 | |
}") | |
valid_permission=$(echo $permission_validation | jq -r '.success') | |
# Log permission validation result | |
echo "$(get_date)Permission Validation: $valid_permission" >> $log | |
# If permission is not valid, exit with error message | |
if [ "$valid_permission" = true ]; then | |
validation_record_id=$(echo $permission_validation | jq -r '.result.id') | |
delete_validation=$(curl -s --request DELETE \ | |
--url https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$validation_record_id \ | |
--header 'Content-Type: application/json' \ | |
--header "Authorization: Bearer $api_token") | |
delete_id=$(echo $delete_validation | jq -r '.result.id') | |
echo "$(get_date)Temporary Validation Record Name: $temp_subdomain.$tld" >> $log | |
echo "$(get_date)Temporary Validation Record ID: $validation_record_id" >> $log | |
echo "$(get_date)Temporary Validation Record Deletion: Done" >> $log | |
else | |
echo "$(get_date)No write permission for Zone $tld is granted. Edit API-Token at https://dash.cloudflare.com/profile/api-tokens and change Read to Edit at Zone.DNS." | tee -a $log | |
echo "$(get_date)Docs: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/" | tee -a $log | |
echo "$(get_date)Exit with Error-Code 1..." | tee -a $log | |
exit 1 | |
fi | |
# Loop through each subdomain to update DNS records | |
for subdomain in "${subdomains[@]}"; do | |
echo -e "\n" | tee -a $log | |
subdomain_data=$(curl -s --request GET \ | |
--url https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records?name=$subdomain \ | |
--header "Authorization: Bearer $api_token" \ | |
--header "Content-Type: application/json") | |
subdomain_id=$(echo $subdomain_data | jq -r '.result[].id') | |
subdomain_ip=$(echo $subdomain_data | jq -r '.result[].content') | |
# If subdomain record doesn't exist, log it | |
if [ "$subdomain_id" = "" ]; then | |
echo "$(get_date)Record for $subdomain doesn't exist." | tee -a $log | |
else | |
# If subdomain IP is the same as current IP, log no changes | |
if [ "$subdomain_ip" = "$ip" ]; then | |
echo "$(get_date)DNS-Record Name: $subdomain" | tee -a $log | |
echo "$(get_date)DNS-Record ID: $subdomain_id" >> $log | |
echo "$(get_date)DNS-Record: No content change detected" | tee -a $log | |
else | |
# If subdomain IP differs, update the DNS record | |
subdomain_proxy=$(echo $subdomain_data | jq -r '.result[].proxied') | |
subdomain_ttl=$(echo $subdomain_data | jq -r '.result[].ttl') | |
record_update=$(curl -s --request PUT \ | |
--url https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$subdomain_id \ | |
--header "Authorization: Bearer $api_token" \ | |
--header "Content-Type: application/json" \ | |
--data "{ | |
\"content\": \"$ip\", | |
\"name\": \"$subdomain\", | |
\"proxied\": $subdomain_proxy, | |
\"type\": \"A\", | |
\"ttl\": $subdomain_ttl | |
}") | |
echo "$(get_date)DNS-Record Name: $subdomain" | tee -a $log | |
echo "$(get_date)DNS-Record ID: $subdomain_id" >> $log | |
echo "$(get_date)DNS-Record Proxy: $subdomain_proxy" >> $log | |
echo "$(get_date)DNS-Record TTL: $subdomain_ttl" >> $log | |
echo "$(get_date)DNS-Record updated successfully" | tee -a $log | |
counter=$((counter + 1)) | |
fi | |
fi | |
done | |
# Log end details | |
echo -e "\n" | tee -a $log | |
echo "End-Date: $(log_date)" >> $log | |
echo "$(get_date)End Log" >> $log | |
# If no DNS records were updated, delete the log | |
if [ $counter = 0 ]; then | |
rm -f $log | |
echo "Log got deleted, since nothing changed." | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment