From ea0fb8af02fa8eee1b3eca06482d7fba974aba44 Mon Sep 17 00:00:00 2001 From: FranSisco Date: Mon, 18 Nov 2024 22:38:28 +0100 Subject: [PATCH] restore repo --- .gitignore | 2 + README.md | 142 ++++++++++++++++++++++++++++++- cloudflare-dns-update.conf | 16 ++++ cloudflare-dns-update.sh | 169 +++++++++++++++++++++++++++++++++++++ 4 files changed, 327 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 cloudflare-dns-update.conf create mode 100755 cloudflare-dns-update.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..192566a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +cloudflare-dns-update_test.conf +cloudflare-dns-update.log diff --git a/README.md b/README.md index f76ec5f..8ba792b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,141 @@ -# Cloudflare-DDNS-Script +# cloudflare DDNS Script -Cloudflare-DDNS-script is a Bash script that automatically updates Cloudflare DNS records with your current external IP address. It supports both IPv4 and IPv6, and can update multiple domains simultaneously. This script is particularly useful for any user who has a dynamic IP address and wants to keep their Cloudflare DNS records up to date without having an additional DDNS provider. \ No newline at end of file +cloudflare-DDNS-script is a Bash script that automatically updates Cloudflare DNS records with your current external IP address. It supports both IPv4 and IPv6, and can update multiple domains simultaneously. This script is particularly useful for any user who has a dynamic IP address and wants to keep their Cloudflare DNS records up to date without having an additional DDNS provider. + +## Features + +- Updates both A (IPv4) and AAAA (IPv6) records +- Supports multiple domains +- Uses multiple sources to reliably fetch public IP addresses +- Configurable Time To Live (TTL) and proxy settings +- Optional Telegram notifications for successful updates +- Detailed logging for easy troubleshooting (including API responses) + +## Prerequisites + +- Bash shell +- `curl` command-line tool +- A Cloudflare account with the domain(s) you want to update +- Cloudflare API token with the necessary permissions + +## Installation + +1. Clone this repository: + ```bash + git clone https://github.com/Ate329/cloudflare-DDNS-script.git + cd cloudflare-DDNS-script + ``` + +2. Make the script executable: + ```bash + chmod +x cloudflare-dns-update.sh + ``` + +3. Change the configurations in the configuration file named `cloudflare-dns-update.conf` in the same directory as the script based on your needs (see Configuration section below). + +## Configuration + +The configuration is in a file named `cloudflare-dns-update.conf` with the following content: + +```bash +zoneid="your_cloudflare_zone_id" +cloudflare_zone_api_token="your_cloudflare_api_token" + +# "DOMAIN,TTL,PROXY" ==> "google.com,3600,true" +dns_record=( + "example.com,3600,true" + "sub1.example.com,1,true" + "sub2.example.com,1,false" + "sub3.example.com,120,true" + "sub4.example.com,," +) # Automatic TTL = 1 , TTL and Proxy dont set ==> TTL = 1 and PROXY=true +ttl=1 # Or any value between 120 and 7200 (1 for automatic) +proxied=false # Or true +notify_me_telegram="no" # Or "yes" +telegram_bot_API_Token="your_telegram_bot_token" # If using Telegram notifications +telegram_chat_id="your_telegram_chat_id" # If using Telegram notifications +``` + +Replace the placeholder values with your actual Cloudflare and Telegram (if used) credentials. + +This is where you can get your API Tokens: https://dash.cloudflare.com/profile/api-tokens +***You should get the API Tokens by clicking the "Create Token" button instead of the API Keys. +(API Tokens and keys are DIFFERENT)*** + +## Usage + +Run the script manually: + +```bash +./cloudflare-dns-update.sh +``` + +For automatic updates, you can set up a cron job. For example, to run the script every 5 minutes: + +Run the command: +```bash +crontab -e +``` + +And add the following (it means running the config once per 5 minutes): +```bash +*/5 * * * * /path/to/cloudflare-dns-update.sh +``` + +### For NixOS + +It's a better idea to do this declaratively in NixOS. + +In your configuration.nix (the default configuration file for NixOS): +```nix + services.cron = { + enable = true; + systemCronJobs = [ + + "* * * * * [username] /your/path/to/cloudflare-dns-update.sh" + ]; + }; +``` + +Example: +```nix + services.cron = { + enable = true; + systemCronJobs = [ + + "* * * * * guest /home/guest/cloudflare-DDNS-script/cloudflare-dns-update.sh" + ]; + }; +``` + +And you might need to install cron as well: +```nix + environment.systemPackages = [ + pkgs.cron + ]; +``` + +Tested on my raspberrypi 4 with NixOS installed and it was working perfectly. + +## Logging + +The script creates a log file named `cloudflare-dns-update.log` in the same directory. This log file contains information about each run of the script, including any errors encountered. + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Disclaimer + +This script is provided as-is, without any warranties. Always test thoroughly before using in a production environment. + +## Acknowledgments + +- Thanks to Cloudflare for providing a robust API. +- Thanks to various public IP services for enabling reliable IP address retrieval. +- Thanks to ChatGPT for generating this README as well. +- Motivated by [DDNS-Cloudflare-Bash](https://github.com/fire1ce/DDNS-Cloudflare-Bash) diff --git a/cloudflare-dns-update.conf b/cloudflare-dns-update.conf new file mode 100644 index 0000000..26c8b8a --- /dev/null +++ b/cloudflare-dns-update.conf @@ -0,0 +1,16 @@ +zoneid="your_cloudflare_zone_id" +cloudflare_zone_api_token="your_cloudflare_api_token" + +# "DOMAIN,TTL,PROXY" ==> "google.com,3600,true" +dns_record=( + "example.com,3600,true" + "sub1.example.com,1,true" + "sub2.example.com,1,false" + "sub3.example.com,120,true" + "sub4.example.com,," +) # Automatic TTL = 1 , TTL and Proxy dont set ==> TTL = 1 and PROXY=true +ttl=1 # Or any value between 120 and 7200 (1 for automatic) +proxied=false # Or true +notify_me_telegram="no" # Or "yes" +telegram_bot_API_Token="your_telegram_bot_token" # If using Telegram notifications +telegram_chat_id="your_telegram_chat_id" # If using Telegram notifications diff --git a/cloudflare-dns-update.sh b/cloudflare-dns-update.sh new file mode 100755 index 0000000..a8d929d --- /dev/null +++ b/cloudflare-dns-update.sh @@ -0,0 +1,169 @@ +#!/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" +