first commit

This commit is contained in:
Mohamed Youssef
2026-04-17 18:56:21 +02:00
commit bf49ccf53c
2 changed files with 537 additions and 0 deletions
+299
View File
@@ -0,0 +1,299 @@
#!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