#!/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 "$@"