328 lines
8.2 KiB
Bash
Executable File
328 lines
8.2 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
# Cloudflare API credentials
|
|
CF_API_TOKEN="${CLOUDFLARE_API_TOKEN:-}"
|
|
CF_ZONE_ID="${CLOUDFLARE_ZONE_ID:-}"
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
usage() {
|
|
echo "Usage: $0 --hostname <hostname>"
|
|
echo " $0 --record-id <record_id>"
|
|
echo " $0 --all-matching <pattern>"
|
|
echo ""
|
|
echo "Options:"
|
|
echo " --hostname Remove DNS record by hostname (e.g., test.example.com)"
|
|
echo " --record-id Remove DNS record by Cloudflare record ID"
|
|
echo " --all-matching Remove all DNS records matching pattern (e.g., '*.example.com')"
|
|
echo ""
|
|
echo "Environment variables required:"
|
|
echo " CLOUDFLARE_API_TOKEN"
|
|
echo " CLOUDFLARE_ZONE_ID"
|
|
exit 1
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1" >&2
|
|
}
|
|
|
|
log_success() {
|
|
echo -e "${GREEN}[SUCCESS]${NC} $1" >&2
|
|
}
|
|
|
|
log_info() {
|
|
echo -e "${YELLOW}[INFO]${NC} $1" >&2
|
|
}
|
|
|
|
check_requirements() {
|
|
if [[ -z "$CF_API_TOKEN" ]]; then
|
|
log_error "CLOUDFLARE_API_TOKEN environment variable not set"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -z "$CF_ZONE_ID" ]]; then
|
|
log_error "CLOUDFLARE_ZONE_ID environment variable not set"
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v curl &> /dev/null; then
|
|
log_error "curl is required but not installed"
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v jq &> /dev/null; then
|
|
log_error "jq is required but not installed"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
get_dns_records_by_hostname() {
|
|
local hostname=$1
|
|
|
|
log_info "Looking up DNS records for: $hostname"
|
|
|
|
local response=$(curl -s -X GET \
|
|
"https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records?name=${hostname}" \
|
|
-H "Authorization: Bearer ${CF_API_TOKEN}" \
|
|
-H "Content-Type: application/json")
|
|
|
|
local success=$(echo "$response" | jq -r '.success')
|
|
|
|
if [[ "$success" != "true" ]]; then
|
|
log_error "Cloudflare API request failed"
|
|
echo "$response" | jq '.'
|
|
exit 1
|
|
fi
|
|
|
|
echo "$response"
|
|
}
|
|
|
|
get_all_dns_records() {
|
|
log_info "Fetching all DNS records in zone"
|
|
|
|
local response=$(curl -s -X GET \
|
|
"https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records?per_page=1000" \
|
|
-H "Authorization: Bearer ${CF_API_TOKEN}" \
|
|
-H "Content-Type: application/json")
|
|
|
|
local success=$(echo "$response" | jq -r '.success')
|
|
|
|
if [[ "$success" != "true" ]]; then
|
|
log_error "Cloudflare API request failed"
|
|
echo "$response" | jq '.'
|
|
exit 1
|
|
fi
|
|
|
|
echo "$response"
|
|
}
|
|
|
|
delete_dns_record() {
|
|
local record_id=$1
|
|
local hostname=$2
|
|
|
|
log_info "Deleting DNS record: $hostname (ID: $record_id)"
|
|
|
|
local response=$(curl -s -X DELETE \
|
|
"https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records/${record_id}" \
|
|
-H "Authorization: Bearer ${CF_API_TOKEN}" \
|
|
-H "Content-Type: application/json")
|
|
|
|
local success=$(echo "$response" | jq -r '.success')
|
|
|
|
if [[ "$success" == "true" ]]; then
|
|
log_success "DNS record deleted successfully: $hostname (ID: $record_id)"
|
|
return 0
|
|
else
|
|
log_error "Failed to delete DNS record: $hostname (ID: $record_id)"
|
|
echo "$response" | jq '.'
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
delete_by_hostname() {
|
|
local hostname=$1
|
|
|
|
local response=$(get_dns_records_by_hostname "$hostname")
|
|
local count=$(echo "$response" | jq -r '.result | length')
|
|
|
|
if [[ "$count" -eq 0 ]]; then
|
|
log_error "No DNS records found for: $hostname"
|
|
exit 1
|
|
fi
|
|
|
|
log_info "Found $count record(s) for: $hostname"
|
|
|
|
local deleted=0
|
|
local failed=0
|
|
|
|
while IFS= read -r record; do
|
|
local record_id=$(echo "$record" | jq -r '.id')
|
|
local record_name=$(echo "$record" | jq -r '.name')
|
|
local record_type=$(echo "$record" | jq -r '.type')
|
|
local record_content=$(echo "$record" | jq -r '.content')
|
|
|
|
log_info "Found: $record_name ($record_type) -> $record_content"
|
|
|
|
if delete_dns_record "$record_id" "$record_name"; then
|
|
deleted=$((deleted + 1))
|
|
else
|
|
failed=$((failed + 1))
|
|
fi
|
|
done < <(echo "$response" | jq -c '.result[]')
|
|
|
|
log_info "Summary: $deleted deleted, $failed failed"
|
|
|
|
if [[ $failed -gt 0 ]]; then
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
delete_by_record_id() {
|
|
local record_id=$1
|
|
|
|
# First, get the record details
|
|
log_info "Fetching record details for ID: $record_id"
|
|
|
|
local response=$(curl -s -X GET \
|
|
"https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records/${record_id}" \
|
|
-H "Authorization: Bearer ${CF_API_TOKEN}" \
|
|
-H "Content-Type: application/json")
|
|
|
|
local success=$(echo "$response" | jq -r '.success')
|
|
|
|
if [[ "$success" != "true" ]]; then
|
|
log_error "Record not found or API request failed"
|
|
echo "$response" | jq '.'
|
|
exit 1
|
|
fi
|
|
|
|
local hostname=$(echo "$response" | jq -r '.result.name')
|
|
local record_type=$(echo "$response" | jq -r '.result.type')
|
|
local content=$(echo "$response" | jq -r '.result.content')
|
|
|
|
log_info "Record found: $hostname ($record_type) -> $content"
|
|
|
|
delete_dns_record "$record_id" "$hostname"
|
|
}
|
|
|
|
delete_all_matching() {
|
|
local pattern=$1
|
|
|
|
log_info "Searching for records matching pattern: $pattern"
|
|
|
|
local response=$(get_all_dns_records)
|
|
local all_records=$(echo "$response" | jq -c '.result[]')
|
|
|
|
local matching_records=()
|
|
|
|
while IFS= read -r record; do
|
|
local record_name=$(echo "$record" | jq -r '.name')
|
|
|
|
# Simple pattern matching (supports * wildcard)
|
|
if [[ "$pattern" == *"*"* ]]; then
|
|
# Convert pattern to regex
|
|
local regex="${pattern//\*/.*}"
|
|
if [[ "$record_name" =~ ^${regex}$ ]]; then
|
|
matching_records+=("$record")
|
|
fi
|
|
else
|
|
# Exact match
|
|
if [[ "$record_name" == "$pattern" ]]; then
|
|
matching_records+=("$record")
|
|
fi
|
|
fi
|
|
done < <(echo "$all_records")
|
|
|
|
local count=${#matching_records[@]}
|
|
|
|
if [[ $count -eq 0 ]]; then
|
|
log_error "No DNS records found matching pattern: $pattern"
|
|
exit 1
|
|
fi
|
|
|
|
log_info "Found $count record(s) matching pattern: $pattern"
|
|
|
|
# List matching records
|
|
for record in "${matching_records[@]}"; do
|
|
local record_name=$(echo "$record" | jq -r '.name')
|
|
local record_type=$(echo "$record" | jq -r '.type')
|
|
local content=$(echo "$record" | jq -r '.content')
|
|
log_info " - $record_name ($record_type) -> $content"
|
|
done
|
|
|
|
# Confirm deletion
|
|
echo ""
|
|
read -p "Delete all $count record(s)? [y/N] " -n 1 -r
|
|
echo ""
|
|
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
log_info "Deletion cancelled"
|
|
exit 0
|
|
fi
|
|
|
|
local deleted=0
|
|
local failed=0
|
|
|
|
for record in "${matching_records[@]}"; do
|
|
local record_id=$(echo "$record" | jq -r '.id')
|
|
local record_name=$(echo "$record" | jq -r '.name')
|
|
|
|
if delete_dns_record "$record_id" "$record_name"; then
|
|
deleted=$((deleted + 1))
|
|
else
|
|
failed=$((failed + 1))
|
|
fi
|
|
done
|
|
|
|
log_info "Summary: $deleted deleted, $failed failed"
|
|
|
|
if [[ $failed -gt 0 ]]; then
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Parse arguments
|
|
HOSTNAME=""
|
|
RECORD_ID=""
|
|
PATTERN=""
|
|
MODE=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--hostname)
|
|
HOSTNAME="$2"
|
|
MODE="hostname"
|
|
shift 2
|
|
;;
|
|
--record-id)
|
|
RECORD_ID="$2"
|
|
MODE="record-id"
|
|
shift 2
|
|
;;
|
|
--all-matching)
|
|
PATTERN="$2"
|
|
MODE="pattern"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
;;
|
|
*)
|
|
log_error "Unknown option: $1"
|
|
usage
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Validate arguments
|
|
if [[ -z "$MODE" ]]; then
|
|
log_error "No deletion mode specified"
|
|
usage
|
|
fi
|
|
|
|
# Check requirements
|
|
check_requirements
|
|
|
|
# Execute based on mode
|
|
case $MODE in
|
|
hostname)
|
|
delete_by_hostname "$HOSTNAME"
|
|
;;
|
|
record-id)
|
|
delete_by_record_id "$RECORD_ID"
|
|
;;
|
|
pattern)
|
|
delete_all_matching "$PATTERN"
|
|
;;
|
|
*)
|
|
log_error "Invalid mode: $MODE"
|
|
exit 1
|
|
;;
|
|
esac
|