Files
Mohamed Youssef bf49ccf53c first commit
2026-04-17 18:56:21 +02:00

299 lines
9.4 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!bin/bash
#=================#
# params
#=================#
TOKEN="cfut_wgrWeIbAuyqekbEd1EqCQFSR6NNmE0gzJdReSZoce3352365"
AUTO_DELETE_ORPHANS=true # Set to true to auto-delete orphaned records
DRY_RUN=false # Set to false to actually create records
#################
# Domain list
#################
declare -a domains # declare main domain associative array to include all domains in one array
declare -A d0 d1 # declare new array for each domain
d0=(
["domain"]=""
["zone_id"]=""
["subdomains"]="@,git"
["protected_records"]=""
)
domains=( d0 d1 )
#*****************#
# DNS FUNCTIONS
#*****************#
get_current_ip() {
curl -s https://ifconfig.me/ip
}
list_dns_records() {
local zone_id="$1"
curl -s "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records" \
-H "Authorization: Bearer $TOKEN"
}
create_dns_record() {
local zone_id="$1"
local data="$2"
if [[ "$DRY_RUN" == true ]]; then
echo " [DRY RUN] Would create record with: $data"
echo '{"success":true,"dry_run":true}'
return 0
fi
curl -s "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records" \
-X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d "$data"
}
delete_dns_record() {
local zone_id="$1"
local dns_record_id="$2"
if [[ "$DRY_RUN" == true ]]; then
echo " [DRY RUN] Would delete record ID: $dns_record_id"
echo '{"success":true,"dry_run":true}'
return 0
fi
curl -s "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$dns_record_id" \
-X DELETE \
-H "Authorization: Bearer $TOKEN"
}
update_dns_record() {
local zone_id="$1"
local dns_record_id="$2"
local body="$3"
if [[ "$DRY_RUN" == true ]]; then
echo " [DRY RUN] Would update record ID: $dns_record_id with: $body"
echo '{"success":true,"dry_run":true}'
return 0
fi
curl -s "https://api.cloudflare.com/client/v4/zones/$zone_id/dns_records/$dns_record_id" \
-X PATCH \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d "$body"
}
#*****************#
# HELPER FUNCTIONS
#*****************#
compare_ips() {
local ip1="$1"
local ip2="$2"
if [[ "$ip1" == "$ip2" ]]; then
return 0 # IPs are the same
else
return 1 # IPs are different
fi
}
#**************#
# MAIN OPERATIONS
#**************#
# Display mode
if [[ "$DRY_RUN" == true ]]; then
echo "⚠️ DRY RUN MODE ENABLED - No actual changes will be made"
echo "========================================="
fi
# Get current public IP
current_ip=$(get_current_ip)
if [ -z "$current_ip" ]; then
echo "Error: Could not retrieve current IP"
exit 1
fi
echo "Current Public IP: $current_ip"
echo "========================================="
# Process each domain
for item in "${domains[@]}"; do
unset domain
declare -n domain="$item"
echo "Processing domain: ${domain[domain]}"
echo "Zone ID: ${domain[zone_id]}"
# Parse protected records
IFS=',' read -ra protected_records <<< "${domain[protected_records]}"
# Parse managed subdomains
IFS=',' read -ra managed_subdomains <<< "${domain[subdomains]}"
echo "Managed subdomains: ${managed_subdomains[@]}"
# Get existing DNS records
response=$(list_dns_records "${domain[zone_id]}")
# Check for API errors
if echo "$response" | jq -e '.success == false' > /dev/null 2>&1; then
echo "Error: Failed to get DNS records"
echo "$response" | jq '.errors'
continue
fi
echo "----------------------------------------"
# Create associative array for existing A records
declare -A existing_a_records
# Build map of existing A records
echo "Existing A records:"
while IFS= read -r record; do
if [[ -n "$record" ]]; then
record_id=$(echo "$record" | jq -r '.id')
record_name=$(echo "$record" | jq -r '.name')
record_content=$(echo "$record" | jq -r '.content')
existing_a_records["$record_name"]="$record_id|$record_content"
echo " - $record_name$record_content (ID: $record_id)"
fi
done < <(echo "$response" | jq -c '.result[] | select(.type == "A")')
echo "----------------------------------------"
# Statistics counters
records_created=0
records_updated=0
records_skipped=0
records_deleted=0
# Process each managed subdomain (create/update)
for subdomain in "${managed_subdomains[@]}"; do
# Build full domain name
if [[ "$subdomain" == "@" ]] || [[ -z "$subdomain" ]]; then
full_name="${domain[domain]}"
else
full_name="${subdomain}.${domain[domain]}"
fi
echo "Checking: $full_name"
# Check if record exists
if [[ -n "${existing_a_records[$full_name]}" ]]; then
IFS='|' read -r record_id existing_ip <<< "${existing_a_records[$full_name]}"
# Compare IPs
if compare_ips "$existing_ip" "$current_ip"; then
echo " ✓ SKIPPED: IP unchanged ($existing_ip = $current_ip)"
((records_skipped++))
else
echo " ⚠ NEEDS UPDATE: IP changed ($existing_ip$current_ip)"
# Update existing record with new IP
update_body=$(jq -n --arg ip "$current_ip" '{"content": $ip}')
update_response=$(update_dns_record "${domain[zone_id]}" "$record_id" "$update_body")
if echo "$update_response" | jq -e '.success == true' > /dev/null 2>&1; then
echo " ✓ SUCCESS: Updated $full_name to $current_ip"
((records_updated++))
else
echo " ✗ FAILED: Could not update $full_name"
echo "$update_response" | jq '.errors'
fi
fi
# Mark as processed (remove from map for deletion check)
unset existing_a_records["$full_name"]
else
echo " CREATING: Record does not exist"
# Create new record
create_data=$(jq -n \
--arg name "$full_name" \
--arg ip "$current_ip" \
'{
"type": "A",
"name": $name,
"content": $ip,
"ttl": 1,
"proxied": true
}')
create_response=$(create_dns_record "${domain[zone_id]}" "$create_data")
if echo "$create_response" | jq -e '.success == true' > /dev/null 2>&1; then
echo " ✓ SUCCESS: Created $full_name -> $current_ip"
((records_created++))
else
echo " ✗ FAILED: Could not create $full_name"
echo "$create_response" | jq '.errors'
fi
fi
echo ""
done
# Delete orphaned records if auto-delete is enabled
if [[ "$AUTO_DELETE_ORPHANS" == true ]] && [[ ${#existing_a_records[@]} -gt 0 ]]; then
echo "Checking for orphaned records to delete..."
for record_name in "${!existing_a_records[@]}"; do
# Check if record is protected
is_protected=false
for protected in "${protected_records[@]}"; do
if [[ "$record_name" == "$protected" ]]; then
is_protected=true
break
fi
done
if [[ "$is_protected" == true ]]; then
echo " ⚠ PROTECTED: $record_name (skipped from deletion)"
continue
fi
IFS='|' read -r record_id existing_ip <<< "${existing_a_records[$record_name]}"
echo " 🗑 ORPHANED: $record_name -> $existing_ip"
delete_response=$(delete_dns_record "${domain[zone_id]}" "$record_id")
if echo "$delete_response" | jq -e '.success == true' > /dev/null 2>&1; then
echo " ✓ DELETED: $record_name"
((records_deleted++))
else
echo " ✗ FAILED: Could not delete $record_name"
echo "$delete_response" | jq '.errors'
fi
done
echo ""
elif [[ ${#existing_a_records[@]} -gt 0 ]]; then
echo "Orphaned records found (auto-delete disabled):"
for record_name in "${!existing_a_records[@]}"; do
IFS='|' read -r record_id existing_ip <<< "${existing_a_records[$record_name]}"
echo " - $record_name -> $existing_ip (ID: $record_id)"
done
echo ""
fi
# Summary
echo "========================================="
echo "SUMMARY for ${domain[domain]}:"
echo " ✓ Created: $records_created"
echo " ✓ Updated: $records_updated"
echo " ✓ Skipped (IP unchanged): $records_skipped"
echo " ✓ Deleted: $records_deleted"
echo "========================================="
echo ""
# Clean up associative array
unset existing_a_records
done
if [[ "$DRY_RUN" == true ]]; then
echo "⚠️ DRY RUN COMPLETED - No actual changes were made"
echo "Set DRY_RUN=false to apply changes"
fi