#!/bin/bash set -euo pipefail # --- Configuration --- : "${ENV_DIR:="./zones"}" : "${LOG_DIR:="./logs"}" DATE=$(date +%F) LOG_FILE="$LOG_DIR/update_${DATE}.log" # --- Ensure log directory exists --- mkdir -p "$LOG_DIR" # --- Logging function --- log() { local message="$1" echo "$message" | tee -a "$LOG_FILE" } # --- Check required tools --- for cmd in curl jq; do if ! command -v "$cmd" >/dev/null 2>&1; then echo "[!] Required tool '$cmd' not found. Please install it." >&2 exit 1 fi done # --- Fetch current IP from Mullvad --- log "[*] Fetching IP from Mullvad..." IP_INFO=$(curl -sf https://ipv4.am.i.mullvad.net/json) || { log "[!] Failed to fetch IP from Mullvad" exit 1 } if ! echo "$IP_INFO" | jq -e '.ip' >/dev/null; then log "[!] Invalid Mullvad response or missing IP." log "$IP_INFO" exit 1 fi CURRENT_IP=$(echo "$IP_INFO" | jq -r '.ip') log "[*] Current public IP is: $CURRENT_IP" log "" # --- Get list of .env files --- shopt -s nullglob ENV_FILES=("$ENV_DIR"/*.env) if [[ ${#ENV_FILES[@]} -eq 0 ]]; then log "[!] No .env files found in $ENV_DIR" exit 0 fi # --- Process each env file --- for ENV_FILE in "${ENV_FILES[@]}"; do log "[*] Processing config: $ENV_FILE" # Load environment variables set -a source "$ENV_FILE" set +a # Validate required vars missing_vars=() [[ -z "${ZONE_ID:-}" ]] && missing_vars+=("ZONE_ID") [[ -z "${DNS_NAME:-}" ]] && missing_vars+=("DNS_NAME") [[ -z "${CLOUDFLARE_API_KEY:-}" ]] && missing_vars+=("CLOUDFLARE_API_KEY") if (( ${#missing_vars[@]} )); then log "[!] Missing variables in $ENV_FILE: ${missing_vars[*]}" log "" continue fi # --- Check if DNS record exists --- log "[*] Checking DNS record for $DNS_NAME..." DNS_LOOKUP=$(curl -sf -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=A&name=$DNS_NAME" \ -H "Authorization: Bearer $CLOUDFLARE_API_KEY" \ -H "Content-Type: application/json") CURL_EXIT_CODE=$? if [[ $CURL_EXIT_CODE -ne 0 ]]; then log "[!] Failed to query DNS record for $DNS_NAME (curl exit code $CURL_EXIT_CODE)" log "$DNS_LOOKUP" log "" continue fi RECORD_ID=$(echo "$DNS_LOOKUP" | jq -r '.result[0].id // empty') EXISTING_IP=$(echo "$DNS_LOOKUP" | jq -r '.result[0].content // empty') if [[ -z "$RECORD_ID" ]]; then log "[!] No existing record found. Creating new A record for $DNS_NAME..." CREATE_RESPONSE=$(curl -sf -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \ -H "Content-Type: application/json" \ -H "X-Auth-Email: $CLOUDFLARE_EMAIL" \ -H "Authorization: Bearer $CLOUDFLARE_API_KEY" \ -d "{ \"name\": \"$DNS_NAME\", \"ttl\": 3600, \"type\": \"A\", \"comment\": \"Domain verification record\", \"content\": \"$CURRENT_IP\", \"proxied\": true }") CURL_EXIT_CODE=$? if [[ $CURL_EXIT_CODE -ne 0 ]]; then log "[!] curl failed creating DNS record (exit code $CURL_EXIT_CODE)" log "$CREATE_RESPONSE" log "" continue fi if [[ $(echo "$CREATE_RESPONSE" | jq -r '.success') == "true" ]]; then log "[+] Successfully created DNS record for $DNS_NAME → $CURRENT_IP" else log "[!] Failed to create DNS record for $DNS_NAME" echo "$CREATE_RESPONSE" | tee -a "$LOG_FILE" fi log "" continue fi # --- If record exists, check if update is needed --- if [[ "$EXISTING_IP" == "$CURRENT_IP" ]]; then log "[=] No update needed. $DNS_NAME already points to $CURRENT_IP" log "" continue fi # --- Patch the existing record --- log "[*] IP has changed: $EXISTING_IP → $CURRENT_IP" log "[*] Updating existing DNS record via PATCH..." UPDATE_RESPONSE=$(curl -sf -X PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \ -H "Content-Type: application/json" \ -H "X-Auth-Email: $CLOUDFLARE_EMAIL" \ -H "Authorization: Bearer $CLOUDFLARE_API_KEY" \ -d "{ \"name\": \"$DNS_NAME\", \"ttl\": 3600, \"type\": \"A\", \"comment\": \"Domain verification record\", \"content\": \"$CURRENT_IP\", \"proxied\": true }") CURL_EXIT_CODE=$? if [[ $CURL_EXIT_CODE -ne 0 ]]; then log "[!] curl failed updating DNS record (exit code $CURL_EXIT_CODE)" log "$UPDATE_RESPONSE" log "" continue fi if [[ $(echo "$UPDATE_RESPONSE" | jq -r '.success') == "true" ]]; then log "[+] Successfully updated $DNS_NAME to $CURRENT_IP" else log "[!] Failed to update $DNS_NAME" echo "$UPDATE_RESPONSE" | tee -a "$LOG_FILE" fi log "" done