285 lines
8.6 KiB
Bash
285 lines
8.6 KiB
Bash
|
|
#!/bin/bash
|
||
|
|
# setup-dns-records.sh
|
||
|
|
# Creates DNS records for Proxmox instances using Cloudflare API
|
||
|
|
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
# Load environment variables from .env if it exists
|
||
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
|
if [ -f "${SCRIPT_DIR}/../.env" ]; then
|
||
|
|
source "${SCRIPT_DIR}/../.env"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Colors
|
||
|
|
RED='\033[0;31m'
|
||
|
|
GREEN='\033[0;32m'
|
||
|
|
YELLOW='\033[1;33m'
|
||
|
|
NC='\033[0m'
|
||
|
|
|
||
|
|
# Configuration
|
||
|
|
DOMAIN="${DOMAIN:-sankofa.nexus}"
|
||
|
|
ZONE_ID="${CLOUDFLARE_ZONE_ID:-}"
|
||
|
|
# Support both API Token and Global API Key + Email
|
||
|
|
API_TOKEN="${CLOUDFLARE_API_TOKEN:-}"
|
||
|
|
API_KEY="${CLOUDFLARE_API_KEY:-}"
|
||
|
|
API_EMAIL="${CLOUDFLARE_EMAIL:-}"
|
||
|
|
|
||
|
|
# Instance configurations
|
||
|
|
declare -A INSTANCES=(
|
||
|
|
["ml110-01"]="192.168.11.10"
|
||
|
|
["r630-01"]="192.168.11.11"
|
||
|
|
)
|
||
|
|
|
||
|
|
log() {
|
||
|
|
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
|
||
|
|
}
|
||
|
|
|
||
|
|
error() {
|
||
|
|
echo -e "${RED}[ERROR]${NC} $1" >&2
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
warn() {
|
||
|
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
||
|
|
}
|
||
|
|
|
||
|
|
check_requirements() {
|
||
|
|
# Check if we have either API Token or Global API Key + Email
|
||
|
|
if [ -z "$API_TOKEN" ] && [ -z "$API_KEY" ]; then
|
||
|
|
error "Either CLOUDFLARE_API_TOKEN or CLOUDFLARE_API_KEY must be set"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [ -z "$API_TOKEN" ] && [ -z "$API_EMAIL" ]; then
|
||
|
|
error "If using CLOUDFLARE_API_KEY, CLOUDFLARE_EMAIL must also be set"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if ! command -v curl &> /dev/null; then
|
||
|
|
error "curl is required but not installed"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if ! command -v jq &> /dev/null; then
|
||
|
|
error "jq is required but not installed"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Try to get zone ID if not provided
|
||
|
|
if [ -z "$ZONE_ID" ]; then
|
||
|
|
get_zone_id
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
get_zone_id() {
|
||
|
|
if [ -z "$ZONE_ID" ]; then
|
||
|
|
log "Getting zone ID for ${DOMAIN}..."
|
||
|
|
|
||
|
|
if [ -n "$API_TOKEN" ]; then
|
||
|
|
# Use API Token
|
||
|
|
ZONE_ID=$(curl -s -X GET \
|
||
|
|
-H "Authorization: Bearer ${API_TOKEN}" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
"https://api.cloudflare.com/client/v4/zones?name=${DOMAIN}" | \
|
||
|
|
jq -r '.result[0].id')
|
||
|
|
elif [ -n "$API_KEY" ] && [ -n "$API_EMAIL" ]; then
|
||
|
|
# Use Global API Key + Email
|
||
|
|
ZONE_ID=$(curl -s -X GET \
|
||
|
|
-H "X-Auth-Email: ${API_EMAIL}" \
|
||
|
|
-H "X-Auth-Key: ${API_KEY}" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
"https://api.cloudflare.com/client/v4/zones?name=${DOMAIN}" | \
|
||
|
|
jq -r '.result[0].id')
|
||
|
|
else
|
||
|
|
error "Cannot get Zone ID: No authentication method available"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [ "$ZONE_ID" == "null" ] || [ -z "$ZONE_ID" ]; then
|
||
|
|
error "Failed to get zone ID for ${DOMAIN}"
|
||
|
|
fi
|
||
|
|
|
||
|
|
log "Zone ID: ${ZONE_ID}"
|
||
|
|
export CLOUDFLARE_ZONE_ID="$ZONE_ID"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
create_a_record() {
|
||
|
|
local name=$1
|
||
|
|
local ip=$2
|
||
|
|
local comment=$3
|
||
|
|
|
||
|
|
log "Creating A record: ${name}.${DOMAIN} → ${ip}"
|
||
|
|
|
||
|
|
local response
|
||
|
|
if [ -n "$API_TOKEN" ]; then
|
||
|
|
response=$(curl -s -X POST \
|
||
|
|
-H "Authorization: Bearer ${API_TOKEN}" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
"https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
|
||
|
|
-d "{
|
||
|
|
\"type\": \"A\",
|
||
|
|
\"name\": \"${name}\",
|
||
|
|
\"content\": \"${ip}\",
|
||
|
|
\"ttl\": 300,
|
||
|
|
\"comment\": \"${comment}\",
|
||
|
|
\"proxied\": false
|
||
|
|
}")
|
||
|
|
else
|
||
|
|
response=$(curl -s -X POST \
|
||
|
|
-H "X-Auth-Email: ${API_EMAIL}" \
|
||
|
|
-H "X-Auth-Key: ${API_KEY}" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
"https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
|
||
|
|
-d "{
|
||
|
|
\"type\": \"A\",
|
||
|
|
\"name\": \"${name}\",
|
||
|
|
\"content\": \"${ip}\",
|
||
|
|
\"ttl\": 300,
|
||
|
|
\"comment\": \"${comment}\",
|
||
|
|
\"proxied\": false
|
||
|
|
}")
|
||
|
|
fi
|
||
|
|
|
||
|
|
local success=$(echo "$response" | jq -r '.success')
|
||
|
|
local record_id=$(echo "$response" | jq -r '.result.id // empty')
|
||
|
|
|
||
|
|
if [ "$success" == "true" ] && [ -n "$record_id" ]; then
|
||
|
|
log "✓ A record created: ${name}.${DOMAIN} (ID: ${record_id})"
|
||
|
|
return 0
|
||
|
|
else
|
||
|
|
local errors=$(echo "$response" | jq -r '.errors[].message // "Unknown error"' | head -1)
|
||
|
|
warn "Failed to create A record: ${errors}"
|
||
|
|
return 1
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
create_cname_record() {
|
||
|
|
local name=$1
|
||
|
|
local target=$2
|
||
|
|
local comment=$3
|
||
|
|
|
||
|
|
log "Creating CNAME record: ${name}.${DOMAIN} → ${target}"
|
||
|
|
|
||
|
|
local response
|
||
|
|
if [ -n "$API_TOKEN" ]; then
|
||
|
|
response=$(curl -s -X POST \
|
||
|
|
-H "Authorization: Bearer ${API_TOKEN}" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
"https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
|
||
|
|
-d "{
|
||
|
|
\"type\": \"CNAME\",
|
||
|
|
\"name\": \"${name}\",
|
||
|
|
\"content\": \"${target}\",
|
||
|
|
\"ttl\": 300,
|
||
|
|
\"comment\": \"${comment}\",
|
||
|
|
\"proxied\": false
|
||
|
|
}")
|
||
|
|
else
|
||
|
|
response=$(curl -s -X POST \
|
||
|
|
-H "X-Auth-Email: ${API_EMAIL}" \
|
||
|
|
-H "X-Auth-Key: ${API_KEY}" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
"https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
|
||
|
|
-d "{
|
||
|
|
\"type\": \"CNAME\",
|
||
|
|
\"name\": \"${name}\",
|
||
|
|
\"content\": \"${target}\",
|
||
|
|
\"ttl\": 300,
|
||
|
|
\"comment\": \"${comment}\",
|
||
|
|
\"proxied\": false
|
||
|
|
}")
|
||
|
|
fi
|
||
|
|
|
||
|
|
local success=$(echo "$response" | jq -r '.success')
|
||
|
|
local record_id=$(echo "$response" | jq -r '.result.id // empty')
|
||
|
|
|
||
|
|
if [ "$success" == "true" ] && [ -n "$record_id" ]; then
|
||
|
|
log "✓ CNAME record created: ${name}.${DOMAIN} (ID: ${record_id})"
|
||
|
|
return 0
|
||
|
|
else
|
||
|
|
local errors=$(echo "$response" | jq -r '.errors[].message // "Unknown error"' | head -1)
|
||
|
|
warn "Failed to create CNAME record: ${errors}"
|
||
|
|
return 1
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
check_record_exists() {
|
||
|
|
local name=$1
|
||
|
|
local type=$2
|
||
|
|
|
||
|
|
local response
|
||
|
|
if [ -n "$API_TOKEN" ]; then
|
||
|
|
response=$(curl -s -X GET \
|
||
|
|
-H "Authorization: Bearer ${API_TOKEN}" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
"https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records?name=${name}.${DOMAIN}&type=${type}")
|
||
|
|
else
|
||
|
|
response=$(curl -s -X GET \
|
||
|
|
-H "X-Auth-Email: ${API_EMAIL}" \
|
||
|
|
-H "X-Auth-Key: ${API_KEY}" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
"https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records?name=${name}.${DOMAIN}&type=${type}")
|
||
|
|
fi
|
||
|
|
|
||
|
|
local count=$(echo "$response" | jq -r '.result | length')
|
||
|
|
|
||
|
|
if [ "$count" -gt 0 ]; then
|
||
|
|
return 0 # Record exists
|
||
|
|
else
|
||
|
|
return 1 # Record does not exist
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
setup_instance_dns() {
|
||
|
|
local instance_name=$1
|
||
|
|
local ip=$2
|
||
|
|
|
||
|
|
local fqdn="${instance_name}.${DOMAIN}"
|
||
|
|
|
||
|
|
# Create A record
|
||
|
|
if check_record_exists "$instance_name" "A"; then
|
||
|
|
warn "A record for ${fqdn} already exists, skipping..."
|
||
|
|
else
|
||
|
|
create_a_record "$instance_name" "$ip" "Proxmox Instance - ${instance_name}"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Create API CNAME
|
||
|
|
if check_record_exists "${instance_name}-api" "CNAME"; then
|
||
|
|
warn "CNAME record for ${instance_name}-api.${DOMAIN} already exists, skipping..."
|
||
|
|
else
|
||
|
|
create_cname_record "${instance_name}-api" "$fqdn" "Proxmox ${instance_name} API endpoint"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Create metrics CNAME
|
||
|
|
if check_record_exists "${instance_name}-metrics" "CNAME"; then
|
||
|
|
warn "CNAME record for ${instance_name}-metrics.${DOMAIN} already exists, skipping..."
|
||
|
|
else
|
||
|
|
create_cname_record "${instance_name}-metrics" "$fqdn" "Proxmox ${instance_name} metrics endpoint"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
main() {
|
||
|
|
log "Setting up DNS records for Proxmox instances"
|
||
|
|
log "Domain: ${DOMAIN}"
|
||
|
|
|
||
|
|
check_requirements
|
||
|
|
get_zone_id
|
||
|
|
|
||
|
|
log ""
|
||
|
|
log "Creating DNS records for ${#INSTANCES[@]} instances..."
|
||
|
|
log ""
|
||
|
|
|
||
|
|
for instance_name in "${!INSTANCES[@]}"; do
|
||
|
|
setup_instance_dns "$instance_name" "${INSTANCES[$instance_name]}"
|
||
|
|
echo ""
|
||
|
|
done
|
||
|
|
|
||
|
|
log "DNS setup complete!"
|
||
|
|
log ""
|
||
|
|
log "Created records:"
|
||
|
|
for instance_name in "${!INSTANCES[@]}"; do
|
||
|
|
echo " • ${instance_name}.${DOMAIN} → ${INSTANCES[$instance_name]}"
|
||
|
|
echo " • ${instance_name}-api.${DOMAIN} → ${instance_name}.${DOMAIN}"
|
||
|
|
echo " • ${instance_name}-metrics.${DOMAIN} → ${instance_name}.${DOMAIN}"
|
||
|
|
done
|
||
|
|
}
|
||
|
|
|
||
|
|
main "$@"
|
||
|
|
|