Cloudflare-DDNS-Script/cloudflare-dns-update.sh
2024-11-18 22:38:28 +01:00

169 lines
5.4 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
### Function to log messages
log() {
echo "$(date "+%Y-%m-%d %H:%M:%S") $1" | tee -a "$LOG_FILE" >&2
}
### Function to log messages only to the log file
log_to_file() {
echo "$(date "+%Y-%m-%d %H:%M:%S") $1" >> "$LOG_FILE"
}
### Create log file
parent_path="$(dirname "${BASH_SOURCE[0]}")"
LOG_FILE="${parent_path}/cloudflare-dns-update.log"
touch "$LOG_FILE"
log "==> Script started"
### Validate config file
config_file="${1:-${parent_path}/cloudflare-dns-update.conf}"
if ! source "$config_file"; then
log "Error! Missing configuration file $config_file or invalid syntax!"
exit 1
fi
### Check validity of parameters
if ! [[ "$ttl" =~ ^[0-9]+$ ]] || { [ "$ttl" -lt 120 ] || [ "$ttl" -gt 7200 ]; } && [ "$ttl" -ne 1 ]; then
log "Error! ttl must be 1 or between 120 and 7200"
exit 1
fi
### Valid IPv4 and IPv6 Regex
readonly IPV4_REGEX='^([0-9]{1,3}\.){3}[0-9]{1,3}$'
readonly IPV6_REGEX='^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$'
### Function to get external IP (IPv4 or IPv6)
get_external_ip() {
local ip_type=$1
local sources=()
local regex
case "$ip_type" in
ipv4)
sources=("https://api.ipify.org" "https://checkip.amazonaws.com" "https://ifconfig.me/ip")
regex="$IPV4_REGEX"
;;
ipv6)
sources=("https://api64.ipify.org" "https://ifconfig.co/ip")
regex="$IPV6_REGEX"
;;
*)
log "Error! Invalid IP type specified: $ip_type"
return 1
;;
esac
for source in "${sources[@]}"; do
if ip=$(curl -"${ip_type:3:1}" -s "$source" --max-time 10) && [[ "$ip" =~ $regex ]]; then
echo "$ip"
return 0
fi
done
log "Error! Unable to retrieve $ip_type address from any source."
return 1
}
### Get external IPs
ipv4=$(get_external_ip "ipv4") || ipv4=""
ipv6=$(get_external_ip "ipv6") || ipv6=""
[ -n "$ipv4" ] && log "==> External IPv4 is: $ipv4"
[ -n "$ipv6" ] && log "==> External IPv6 is: $ipv6"
### Function to extract value from JSON
json_extract() {
local key=$1
sed -n 's/.*"'"$key"'":"\?\([^,"]*\)"\?.*/\1/p'
}
### Function to update DNS record
update_dns_record() {
local record=$1
local ip=$2
local type=$3
local ttl=$4
local proxied=$5
# Get the DNS record information from Cloudflare API
local cloudflare_record_info
cloudflare_record_info=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zoneid/dns_records?type=$type&name=$record" \
-H "Authorization: Bearer $cloudflare_zone_api_token" \
-H "Content-Type: application/json")
log_to_file "Cloudflare API response: $cloudflare_record_info" # Log the API response to file for debugging
if [[ $cloudflare_record_info == *"\"success\":false"* ]]; then
log "Error! Can't get $record ($type) record information from Cloudflare API"
return 1
fi
# Get the current IP and proxy status from the API response
local current_ip
current_ip=$(echo "$cloudflare_record_info" | json_extract "content")
local current_proxied
current_proxied=$(echo "$cloudflare_record_info" | json_extract "proxied")
# Check if IP or proxy have changed
if [ "$current_ip" == "$ip" ] && [ "$current_proxied" == "$proxied" ]; then
log "==> DNS $type record of $record is $current_ip, no changes needed."
return 0
fi
log "==> DNS $type record of $record is: $current_ip. Trying to update..."
# Get the DNS record ID from response
local cloudflare_dns_record_id
cloudflare_dns_record_id=$(echo "$cloudflare_record_info" | json_extract "id")
# Push new DNS record information to Cloudflare API
if ! curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zoneid/dns_records/$cloudflare_dns_record_id" \
-H "Authorization: Bearer $cloudflare_zone_api_token" \
-H "Content-Type: application/json" \
--data "{\"type\":\"$type\",\"name\":\"$record\",\"content\":\"$ip\",\"ttl\":$ttl,\"proxied\":$proxied}" | grep -q '"success":true'; then
log "Error! Update failed for $record ($type)"
return 1
fi
log "==> Success!"
log "==> $record DNS $type Record updated to: $ip, ttl: $ttl, proxied: $proxied"
# Telegram notification
if [ "${notify_me_telegram:-no}" == "yes" ]; then
send_telegram_notification "$record" "$type" "$ip" "$ttl" "$proxied"
fi
}
### Function to send Telegram notification
send_telegram_notification() {
local record=$1
local type=$2
local ip=$3
local ttl=$4
local proxied=$5
if ! curl -s -X POST "https://api.telegram.org/bot${telegram_bot_API_Token}/sendMessage" \
-H "Content-Type: application/json" \
--data "{\"chat_id\":\"${telegram_chat_id}\",\"text\":\"DNS Record Update\nDomain: ${record}\nType: ${type}\nIP: ${ip}\nTTL: ${ttl}\nProxied: ${proxied}\"}" | grep -q '"ok":true'; then
log "Error! Telegram notification failed for $record ($type)"
fi
}
# Update DNS records
for dns_record in "${dns_record[@]}"; do
IFS=',' read -r record ttl proxied <<< "$dns_record"
ttl="${ttl:-1}" # if TTL is null, set to automatic 1
proxied="${proxied:-true}" # if proxied is null, set true
[ -n "$ipv4" ] && update_dns_record "$record" "$ipv4" "A" "$ttl" "$proxied"
[ -n "$ipv6" ] && update_dns_record "$record" "$ipv6" "AAAA" "$ttl" "$proxied"
done
log "==> Script finished"