Commit e7c5b5420d06

Vincent Demeester <vincent@sbr.pm>
2026-02-19 17:07:16
fix: handle stale DNS record removal in Gandi
Added removal of A records from Gandi that no longer exist in the Nix DNS configuration. Fixed FQDN-to-Gandi name conversion for apex and wildcard records which were previously conflated.
1 parent b7108c5
Changed files (1)
tools/update-gandi-dns.sh
@@ -85,21 +85,23 @@ fi
 echo -e "${GREEN}Found $(echo "$RECORDS" | wc -l) A records to process${RESET}"
 echo
 
-# Get current DNS records from Gandi
-if [[ "$DRY_RUN" == "false" ]]; then
-    echo -e "${CYAN}Fetching current DNS records from Gandi...${RESET}"
-    CURRENT_RECORDS=$(curl -s \
-      -H "Authorization: Bearer $GANDIV5_PERSONAL_TOKEN" \
-      "$API_URL" || echo "[]")
+# Get current DNS records from Gandi (needed for both update and removal detection)
+echo -e "${CYAN}Fetching current DNS records from Gandi...${RESET}"
+CURRENT_RECORDS=$(curl -s \
+  -H "Authorization: Bearer $GANDIV5_PERSONAL_TOKEN" \
+  "$API_URL" || echo "[]")
 
-    echo -e "${GREEN}Current records fetched${RESET}"
-    echo
-fi
+echo -e "${GREEN}Current records fetched${RESET}"
+echo
 
 # Process each record
 UPDATED=0
 SKIPPED=0
 FAILED=0
+DELETED=0
+
+# Track all desired A record names from Nix config
+declare -A DESIRED_NAMES
 
 while IFS= read -r line; do
     # Parse the line to extract: name, TTL, and value
@@ -121,15 +123,20 @@ while IFS= read -r line; do
         VALUE=$(echo "$line" | awk '{print $4}')
     fi
 
-    # Remove .sbr.pm. suffix and convert to Gandi format
-    NAME="${FULL_NAME%.sbr.pm.}"
-
-    # Convert wildcard format: *.name -> *.name (Gandi uses this format)
-    # Convert root wildcard: * -> @ (Gandi's root wildcard)
-    if [[ "$NAME" == "*" ]]; then
+    # Convert FQDN to Gandi record name
+    # - "sbr.pm." (apex) → "@"
+    # - "name.sbr.pm." → "name"
+    # - "*.sbr.pm." → "*"
+    # - "*.name.sbr.pm." → "*.name"
+    if [[ "$FULL_NAME" == "sbr.pm." ]]; then
         NAME="@"
+    else
+        NAME="${FULL_NAME%.sbr.pm.}"
     fi
 
+    # Track this name as desired
+    DESIRED_NAMES["$NAME"]=1
+
     echo -e "${BLUE}Processing: ${RESET}$NAME.$DOMAIN A $VALUE (TTL: $TTL)"
 
     if [[ "$DRY_RUN" == "true" ]]; then
@@ -178,11 +185,55 @@ while IFS= read -r line; do
     echo
 done <<< "$RECORDS"
 
+# Remove stale A records from Gandi that are no longer in Nix config
+echo -e "${BOLD}${BLUE}Checking for stale A records to remove...${RESET}"
+echo
+
+GANDI_A_NAMES=$(echo "$CURRENT_RECORDS" | jq -r '.[] | select(.rrset_type == "A") | .rrset_name' 2>/dev/null || true)
+
+while IFS= read -r gandi_name; do
+    [[ -z "$gandi_name" ]] && continue
+
+    if [[ -z "${DESIRED_NAMES[$gandi_name]+_}" ]]; then
+        GANDI_VALUE=$(echo "$CURRENT_RECORDS" | jq -r \
+          --arg name "$gandi_name" \
+          '.[] | select(.rrset_name == $name and .rrset_type == "A") | .rrset_values | join(", ")' \
+          2>/dev/null || echo "unknown")
+
+        echo -e "${RED}Stale record: ${RESET}$gandi_name.$DOMAIN A $GANDI_VALUE"
+
+        if [[ "$DRY_RUN" == "true" ]]; then
+            echo -e "  ${CYAN}[DRY RUN] Would delete record${RESET}"
+            DELETED=$((DELETED + 1))
+        else
+            RESPONSE=$(curl -s -w "\n%{http_code}" \
+              -X DELETE \
+              -H "Authorization: Bearer $GANDIV5_PERSONAL_TOKEN" \
+              "$API_URL/$gandi_name/A")
+
+            HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
+            BODY=$(echo "$RESPONSE" | sed '$d')
+
+            if [[ "$HTTP_CODE" == "204" ]] || [[ "$HTTP_CODE" == "200" ]]; then
+                echo -e "  ${GREEN}✓ Record deleted successfully${RESET}"
+                DELETED=$((DELETED + 1))
+            else
+                echo -e "  ${RED}✗ Failed to delete record (HTTP $HTTP_CODE)${RESET}"
+                echo -e "  ${RED}Response: $BODY${RESET}"
+                FAILED=$((FAILED + 1))
+            fi
+        fi
+
+        echo
+    fi
+done <<< "$GANDI_A_NAMES"
+
 echo -e "${BOLD}${GREEN}DNS update complete!${RESET}"
 echo
 echo -e "${CYAN}Summary:${RESET}"
 echo -e "  Updated: $UPDATED"
 echo -e "  Skipped (unchanged): $SKIPPED"
+echo -e "  Deleted: $DELETED"
 if [[ $FAILED -gt 0 ]]; then
     echo -e "  ${RED}Failed: $FAILED${RESET}"
 fi