#!/bin/bash # cloudflareMigrationDns.sh - DNS Setup für sanfte E-Mail-Migration # # Dieses Script ist speziell für die Migration von einem alten Provider # zu Bay Area Email (Amazon SES + DMS). Es: # - Erkennt automatisch die MAIL FROM Subdomain aus SES # - Prüft auf CNAME-Konflikte bevor Records gesetzt werden # - Enthält den alten Provider SPF während der Migration # - Setzt KEINE Autodiscover/SRV Records (das kommt erst in Phase 3) # # Voraussetzungen: # - awsses.sh wurde bereits ausgeführt (SES Identity existiert) # - Domain ist bereits in Cloudflare # # Verwendung: # export DOMAIN_NAME="buddelectric.net" # export CF_API_TOKEN="xxx" # export OLD_PROVIDER_SPF="include:_spf.hostedemail.com" # SPF des alten Providers # ./cloudflareMigrationDns.sh # # Optionale Parameter: # export OLD_PROVIDER_SPF="" # Kein alter SPF nötig # export DRY_RUN="true" # Nur anzeigen, nichts ändern # export SKIP_MX="true" # MX nicht setzen (für Tests) set -e # ========================================== # KONFIGURATION & CHECKS # ========================================== AWS_REGION=${AWS_REGION:-"us-east-2"} DRY_RUN=${DRY_RUN:-"false"} SKIP_MX=${SKIP_MX:-"false"} if [ -z "$DOMAIN_NAME" ]; then echo "❌ Fehler: DOMAIN_NAME ist nicht gesetzt." echo " export DOMAIN_NAME='buddelectric.net'" exit 1 fi if [ -z "$CF_API_TOKEN" ]; then echo "❌ Fehler: CF_API_TOKEN fehlt." exit 1 fi if ! command -v jq &> /dev/null; then echo "❌ Fehler: 'jq' fehlt." exit 1 fi if ! command -v aws &> /dev/null; then echo "❌ Fehler: 'aws' CLI fehlt." exit 1 fi echo "============================================================" echo " 📧 Migration DNS Setup für: $DOMAIN_NAME" echo "============================================================" if [ "$DRY_RUN" = "true" ]; then echo " ⚠️ DRY RUN - Es werden KEINE Änderungen vorgenommen!" fi echo "" # ========================================== # 1. MAIL FROM Subdomain aus SES ermitteln # ========================================== echo "--- [1/7] MAIL FROM Subdomain aus SES ermitteln ---" SES_IDENTITY=$(aws sesv2 get-email-identity \ --email-identity "${DOMAIN_NAME}" \ --region "${AWS_REGION}" \ --output json 2>/dev/null) if [ $? -ne 0 ] || [ -z "$SES_IDENTITY" ]; then echo "❌ SES Identity für ${DOMAIN_NAME} nicht gefunden!" echo " Bitte zuerst ./awsses.sh ausführen." exit 1 fi MAIL_FROM_DOMAIN=$(echo "$SES_IDENTITY" | jq -r '.MailFromAttributes.MailFromDomain // empty') if [ -z "$MAIL_FROM_DOMAIN" ]; then echo "⚠️ Keine MAIL FROM Domain in SES konfiguriert. Verwende mail.${DOMAIN_NAME}" MAIL_FROM_DOMAIN="mail.${DOMAIN_NAME}" fi # Extrahiere den Subdomain-Prefix (z.B. "mailfrom" aus "mailfrom.buddelectric.net") MAIL_FROM_PREFIX=$(echo "$MAIL_FROM_DOMAIN" | sed "s/\.${DOMAIN_NAME}$//") echo " ✓ MAIL FROM Domain: ${MAIL_FROM_DOMAIN} (Prefix: ${MAIL_FROM_PREFIX})" # ========================================== # 2. Cloudflare Zone ID ermitteln # ========================================== echo "" echo "--- [2/7] Cloudflare Zone ID ermitteln ---" ZONE_RESPONSE=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$DOMAIN_NAME" \ -H "Authorization: Bearer $CF_API_TOKEN" \ -H "Content-Type: application/json") if [ "$(echo $ZONE_RESPONSE | jq -r '.success')" != "true" ]; then echo "❌ Fehler beim Abrufen der Zone ID:" echo $ZONE_RESPONSE | jq . exit 1 fi CF_ZONE_ID=$(echo $ZONE_RESPONSE | jq -r '.result[0].id') if [ "$CF_ZONE_ID" = "null" ] || [ -z "$CF_ZONE_ID" ]; then echo "❌ Zone für $DOMAIN_NAME nicht in Cloudflare gefunden!" exit 1 fi echo " ✓ Zone ID: $CF_ZONE_ID" # ========================================== # 3. Bestehende Records prüfen (Konflikte erkennen) # ========================================== echo "" echo "--- [3/7] Bestehende DNS Records prüfen ---" # Alle Records der Zone abrufen EXISTING_RECORDS=$(curl -s -X GET \ "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records?per_page=100" \ -H "Authorization: Bearer $CF_API_TOKEN" \ -H "Content-Type: application/json") # Prüfe auf CNAME-Konflikt mit MAIL FROM Subdomain MAIL_FROM_CNAME=$(echo "$EXISTING_RECORDS" | jq -r \ --arg name "${MAIL_FROM_DOMAIN}" \ '.result[] | select(.type == "CNAME" and .name == $name) | .content') if [ -n "$MAIL_FROM_CNAME" ]; then echo "" echo " ⚠️ KONFLIKT ERKANNT!" echo " ${MAIL_FROM_DOMAIN} hat einen CNAME → ${MAIL_FROM_CNAME}" echo " Ein CNAME erlaubt keine weiteren Records (MX, TXT)." echo "" echo " Optionen:" echo " 1) CNAME löschen lassen (falls kein Client diesen Hostnamen nutzt)" echo " 2) awsses.sh mit MAIL_FROM_SUBDOMAIN='mailfrom' nochmal ausführen" echo "" # Prüfe ob es ein alternativer MAIL FROM wäre if [ "$MAIL_FROM_PREFIX" = "mail" ]; then echo " 💡 Empfehlung: Führe folgendes aus und starte dann dieses Script neu:" echo "" echo " export MAIL_FROM_SUBDOMAIN=\"mailfrom\"" echo " export DOMAIN_NAME=\"${DOMAIN_NAME}\"" echo " ./awsses.sh" echo "" echo " Dann dieses Script erneut starten." exit 1 fi fi # Prüfe auf bestehenden MX Record EXISTING_MX=$(echo "$EXISTING_RECORDS" | jq -r \ --arg name "${DOMAIN_NAME}" \ '.result[] | select(.type == "MX" and .name == $name) | "\(.priority) \(.content)"') if [ -n "$EXISTING_MX" ]; then echo " ℹ️ Bestehende MX Records:" echo "$EXISTING_MX" | while read line; do echo " $line"; done echo " → Diese müssen manuell gelöscht werden bevor der neue MX gesetzt wird!" echo " (Script erstellt nur neue Records, löscht keine alten)" fi # Prüfe auf bestehenden SPF EXISTING_SPF=$(echo "$EXISTING_RECORDS" | jq -r \ --arg name "${DOMAIN_NAME}" \ '.result[] | select(.type == "TXT" and .name == $name and (.content | contains("v=spf1"))) | .content') if [ -n "$EXISTING_SPF" ]; then echo " ℹ️ Bestehender SPF: $EXISTING_SPF" echo " → Muss manuell gelöscht/ersetzt werden!" fi echo "" # ========================================== # HILFSFUNKTIONEN # ========================================== create_dns_record() { local TYPE=$1 local NAME=$2 local CONTENT=$3 local PROXIED=${4:-"false"} local TTL=${5:-3600} local PRIORITY=$6 if [ "$DRY_RUN" = "true" ]; then echo " [DRY RUN] Würde erstellen: $TYPE $NAME → $CONTENT" [ -n "$PRIORITY" ] && echo " Priorität: $PRIORITY" return fi local JSON_DATA="" if [ "$TYPE" = "MX" ]; then if [ -z "$PRIORITY" ]; then PRIORITY=10; fi JSON_DATA="{ \"type\": \"$TYPE\", \"name\": \"$NAME\", \"content\": \"$CONTENT\", \"ttl\": $TTL, \"priority\": $PRIORITY, \"proxied\": $PROXIED }" elif [ "$TYPE" = "TXT" ]; then CONTENT=$(echo "$CONTENT" | sed 's/"//g') JSON_DATA="{ \"type\": \"$TYPE\", \"name\": \"$NAME\", \"content\": \"\\\"$CONTENT\\\"\", \"ttl\": $TTL, \"proxied\": $PROXIED }" else JSON_DATA="{ \"type\": \"$TYPE\", \"name\": \"$NAME\", \"content\": \"$CONTENT\", \"ttl\": $TTL, \"proxied\": $PROXIED }" fi RESULT=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \ -H "Authorization: Bearer $CF_API_TOKEN" \ -H "Content-Type: application/json" \ --data "$JSON_DATA") SUCCESS=$(echo "$RESULT" | jq -r '.success') if [ "$SUCCESS" = "true" ]; then echo " ✓ $TYPE $NAME → $CONTENT" else ERROR_MSG=$(echo "$RESULT" | jq -r '.errors[0].message // "Unbekannt"') ERROR_CODE=$(echo "$RESULT" | jq -r '.errors[0].code // 0') if [ "$ERROR_CODE" = "81057" ]; then echo " ⏭ $TYPE $NAME existiert bereits (übersprungen)" else echo " ✗ $TYPE $NAME FEHLER: $ERROR_MSG" fi fi } # ========================================== # 4. DKIM Records (aus SES API) # ========================================== echo "--- [4/7] SES DKIM Records ---" DKIM_TOKENS=$(aws ses get-identity-dkim-attributes \ --identities ${DOMAIN_NAME} --region ${AWS_REGION} \ --query "DkimAttributes.\"${DOMAIN_NAME}\".DkimTokens" --output text 2>/dev/null) if [ -n "$DKIM_TOKENS" ] && [ "$DKIM_TOKENS" != "None" ]; then for TOKEN in ${DKIM_TOKENS}; do create_dns_record "CNAME" "${TOKEN}._domainkey.${DOMAIN_NAME}" "${TOKEN}.dkim.amazonses.com" "false" done else echo " ⚠️ Keine DKIM Tokens gefunden. SES Identity evtl. noch nicht verifiziert?" fi # SES Verification Token VERIFICATION_TOKEN=$(aws ses get-identity-verification-attributes \ --identities ${DOMAIN_NAME} --region ${AWS_REGION} \ --query "VerificationAttributes.\"${DOMAIN_NAME}\".VerificationToken" --output text 2>/dev/null) if [ -n "$VERIFICATION_TOKEN" ] && [ "$VERIFICATION_TOKEN" != "None" ]; then create_dns_record "TXT" "_amazonses.${DOMAIN_NAME}" "${VERIFICATION_TOKEN}" "false" fi # ========================================== # 5. MX Record (SES Inbound) # ========================================== echo "" echo "--- [5/7] MX Record ---" if [ "$SKIP_MX" = "true" ]; then echo " ⏭ MX übersprungen (SKIP_MX=true)" echo " → Setze MX manuell wenn du bereit bist:" echo " ${DOMAIN_NAME} MX 10 inbound-smtp.${AWS_REGION}.amazonaws.com" else echo " ⚠️ ACHTUNG: Alte MX Records müssen VORHER manuell gelöscht werden!" echo " Alter MX vorhanden? Siehe Prüfung oben." echo "" create_dns_record "MX" "${DOMAIN_NAME}" "inbound-smtp.${AWS_REGION}.amazonaws.com" "false" 3600 10 fi # ========================================== # 6. MAIL FROM Subdomain (MX + SPF) # ========================================== echo "" echo "--- [6/7] MAIL FROM Subdomain: ${MAIL_FROM_DOMAIN} ---" create_dns_record "MX" "${MAIL_FROM_DOMAIN}" "feedback-smtp.${AWS_REGION}.amazonses.com" "false" 3600 10 create_dns_record "TXT" "${MAIL_FROM_DOMAIN}" "v=spf1 include:amazonses.com ~all" "false" # ========================================== # 7. SPF & DMARC (mit altem Provider!) # ========================================== echo "" echo "--- [7/7] SPF & DMARC ---" # SPF mit beiden Providern (Migration!) if [ -n "$OLD_PROVIDER_SPF" ]; then SPF_RECORD="v=spf1 include:amazonses.com ${OLD_PROVIDER_SPF} ~all" echo " ℹ️ Migrations-SPF (enthält alten Provider):" else SPF_RECORD="v=spf1 include:amazonses.com ~all" echo " ℹ️ Standard-SPF (kein alter Provider angegeben):" fi echo " ${SPF_RECORD}" create_dns_record "TXT" "${DOMAIN_NAME}" "${SPF_RECORD}" "false" # DMARC create_dns_record "TXT" "_dmarc.${DOMAIN_NAME}" "v=DMARC1; p=none; pct=100; rua=mailto:postmaster@${DOMAIN_NAME}" "false" # ========================================== # ZUSAMMENFASSUNG # ========================================== echo "" echo "============================================================" if [ "$DRY_RUN" = "true" ]; then echo " ⚠️ DRY RUN abgeschlossen - keine Änderungen gemacht" else echo " ✅ Migration DNS Setup abgeschlossen" fi echo "============================================================" echo "" echo " Domain: $DOMAIN_NAME" echo " MAIL FROM: $MAIL_FROM_DOMAIN" echo " SPF: $SPF_RECORD" if [ "$SKIP_MX" = "true" ]; then echo " MX: ⏭ ÜBERSPRUNGEN (manuelle Aktivierung nötig)" else echo " MX: inbound-smtp.${AWS_REGION}.amazonaws.com" fi echo "" echo " 📋 MANUELLE SCHRITTE (falls noch nicht erledigt):" echo " ─────────────────────────────────────────────────" echo " 1. Alte MX Records für ${DOMAIN_NAME} löschen" echo " 2. Alten SPF Record für ${DOMAIN_NAME} löschen" echo " 3. Alten DKIM Record löschen (falls vorhanden)" if [ -n "$MAIL_FROM_CNAME" ]; then echo " 4. CNAME ${MAIL_FROM_DOMAIN} → ${MAIL_FROM_CNAME} löschen" fi echo "" echo " ⚠️ NICHT LÖSCHEN während Migration:" echo " ─────────────────────────────────────" echo " - imap.${DOMAIN_NAME} (Clients holen noch dort ab)" echo " - pop.${DOMAIN_NAME} (Clients holen noch dort ab)" echo " - smtp.${DOMAIN_NAME} (Clients senden noch dort)" echo " - webmail.${DOMAIN_NAME} (falls vorhanden)" echo "" echo " Diese Records werden erst in Phase 3 (nach vollständiger" echo " Client-Umstellung) gelöscht mit cloudflareDns.sh" echo "============================================================"