changes
This commit is contained in:
parent
07e2449d04
commit
9b8217cbd8
|
|
@ -157,24 +157,6 @@ echo "--- Autodiscover & Caddy Konfiguration ---"
|
||||||
create_dns_record "A" "autodiscover.${DOMAIN_NAME}" "$CADDY_SERVER_IP" "false"
|
create_dns_record "A" "autodiscover.${DOMAIN_NAME}" "$CADDY_SERVER_IP" "false"
|
||||||
create_dns_record "A" "autoconfig.${DOMAIN_NAME}" "$CADDY_SERVER_IP" "false"
|
create_dns_record "A" "autoconfig.${DOMAIN_NAME}" "$CADDY_SERVER_IP" "false"
|
||||||
|
|
||||||
# SRV-Records für Apple & Mobile Clients
|
|
||||||
# Wir decken jetzt ALLE Varianten ab, damit das iPhone sicher den richtigen Port findet.
|
|
||||||
|
|
||||||
# 1. IMAP SSL (Port 993) - Das bevorzugt das iPhone!
|
|
||||||
create_srv_record "_imaps" "_tcp" "993" "$MAIL_SERVER_HOSTNAME"
|
|
||||||
|
|
||||||
# 2. IMAP STARTTLS (Port 143) - Als Fallback (optional, falls Port 143 offen ist)
|
|
||||||
# create_srv_record "_imap" "_tcp" "143" "$MAIL_SERVER_HOSTNAME"
|
|
||||||
# (Wenn du 993 im "_imap" Eintrag lassen willst, ist das okay, aber _imaps ist wichtiger)
|
|
||||||
create_srv_record "_imap" "_tcp" "993" "$MAIL_SERVER_HOSTNAME"
|
|
||||||
|
|
||||||
# 3. SMTP SSL (Port 465) - Das bevorzugt das iPhone oft für den Ausgang
|
|
||||||
create_srv_record "_smtps" "_tcp" "465" "$MAIL_SERVER_HOSTNAME"
|
|
||||||
|
|
||||||
# 4. SMTP STARTTLS (Port 587) - Der Standard
|
|
||||||
create_srv_record "_submission" "_tcp" "587" "$MAIL_SERVER_HOSTNAME"
|
|
||||||
|
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# 4. SPF & DMARC
|
# 4. SPF & DMARC
|
||||||
# ==========================================
|
# ==========================================
|
||||||
|
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# ==========================================
|
|
||||||
# KONFIGURATION
|
|
||||||
# ==========================================
|
|
||||||
|
|
||||||
AWS_REGION="us-east-2"
|
|
||||||
|
|
||||||
if [ -z "$DOMAIN_NAME" ]; then
|
|
||||||
echo "Fehler: DOMAIN_NAME ist nicht gesetzt (z.B. export DOMAIN_NAME='bayarea-cc.com')."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -z "$CF_API_TOKEN" ]; then
|
|
||||||
echo "Fehler: CF_API_TOKEN fehlt."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ==========================================
|
|
||||||
# ZONE ID ERMITTELN
|
|
||||||
# ==========================================
|
|
||||||
|
|
||||||
echo "Zone ID für $DOMAIN_NAME abrufen..."
|
|
||||||
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')
|
|
||||||
echo "Zone ID: $CF_ZONE_ID"
|
|
||||||
|
|
||||||
# ==========================================
|
|
||||||
# FUNKTIONEN
|
|
||||||
# ==========================================
|
|
||||||
|
|
||||||
create_dns_record() {
|
|
||||||
local TYPE=$1
|
|
||||||
local NAME=$2
|
|
||||||
local CONTENT=$3
|
|
||||||
local PROXIED=$4
|
|
||||||
local TTL=$5
|
|
||||||
local PRIORITY=$6
|
|
||||||
|
|
||||||
if [ -z "$PROXIED" ]; then PROXIED="false"; fi
|
|
||||||
if [ -z "$TTL" ]; then TTL=3600; fi
|
|
||||||
|
|
||||||
echo "Erstelle $TYPE-Eintrag für $NAME..."
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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" | jq -r '.success'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# ==========================================
|
|
||||||
# 1. AWS SES Setup (DKIM & Verifizierung)
|
|
||||||
# ==========================================
|
|
||||||
echo "--- AWS SES Konfiguration ---"
|
|
||||||
DKIM_TOKENS=$(aws ses get-identity-dkim-attributes \
|
|
||||||
--identities ${DOMAIN_NAME} --region ${AWS_REGION} \
|
|
||||||
--query "DkimAttributes.\"${DOMAIN_NAME}\".DkimTokens" --output text)
|
|
||||||
|
|
||||||
VERIFICATION_TOKEN=$(aws ses get-identity-verification-attributes \
|
|
||||||
--identities ${DOMAIN_NAME} --region ${AWS_REGION} \
|
|
||||||
--query "VerificationAttributes.\"${DOMAIN_NAME}\".VerificationToken" --output text)
|
|
||||||
|
|
||||||
if [ -n "$VERIFICATION_TOKEN" ]; then
|
|
||||||
create_dns_record "TXT" "_amazonses.${DOMAIN_NAME}" "${VERIFICATION_TOKEN}" "false"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ==========================================
|
|
||||||
# 2. MX Records (AWS SES Ingest)
|
|
||||||
# ==========================================
|
|
||||||
echo "--- MX Records (AWS SES) ---"
|
|
||||||
# Hier leiten wir eingehende Mails an Amazon S3/SQS Pipeline
|
|
||||||
create_dns_record "MX" "${DOMAIN_NAME}" "inbound-smtp.${AWS_REGION}.amazonaws.com" "false" 3600 10
|
|
||||||
|
|
||||||
# ==========================================
|
|
||||||
# 4. SPF & DMARC
|
|
||||||
# ==========================================
|
|
||||||
|
|
||||||
echo "Fertig. Konfiguration für $DOMAIN_NAME abgeschlossen."
|
|
||||||
|
|
@ -1,358 +1,242 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# cloudflareMigrationDns.sh - DNS Setup für sanfte E-Mail-Migration
|
# cloudflareMigrationDns.sh
|
||||||
#
|
# Setzt DNS Records für Amazon SES Migration + Cloudflare
|
||||||
# Dieses Script ist speziell für die Migration von einem alten Provider
|
# Unterstützt: DKIM, SPF (Merge), DMARC, MX (Safety Check), Autodiscover
|
||||||
# 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
|
set -e
|
||||||
|
|
||||||
# ==========================================
|
# --- KONFIGURATION ---
|
||||||
# KONFIGURATION & CHECKS
|
|
||||||
# ==========================================
|
|
||||||
|
|
||||||
AWS_REGION=${AWS_REGION:-"us-east-2"}
|
AWS_REGION=${AWS_REGION:-"us-east-2"}
|
||||||
DRY_RUN=${DRY_RUN:-"false"}
|
DRY_RUN=${DRY_RUN:-"false"}
|
||||||
SKIP_MX=${SKIP_MX:-"false"}
|
|
||||||
|
|
||||||
if [ -z "$DOMAIN_NAME" ]; then
|
# Ziel für Autodiscover/IMAP (wohin sollen Mail-Clients verbinden?)
|
||||||
echo "❌ Fehler: DOMAIN_NAME ist nicht gesetzt."
|
# Standard: mail.deinedomain.tld. Kann überschrieben werden.
|
||||||
echo " export DOMAIN_NAME='buddelectric.net'"
|
TARGET_MAIL_SERVER=${TARGET_MAIL_SERVER:-"mail.${DOMAIN_NAME}"}
|
||||||
exit 1
|
|
||||||
fi
|
# --- CHECKS ---
|
||||||
if [ -z "$CF_API_TOKEN" ]; then
|
if [ -z "$DOMAIN_NAME" ]; then echo "❌ Fehler: DOMAIN_NAME fehlt."; exit 1; fi
|
||||||
echo "❌ Fehler: CF_API_TOKEN fehlt."
|
if [ -z "$CF_API_TOKEN" ]; then echo "❌ Fehler: CF_API_TOKEN fehlt."; exit 1; fi
|
||||||
exit 1
|
if ! command -v jq &> /dev/null; then echo "❌ Fehler: 'jq' fehlt."; exit 1; fi
|
||||||
fi
|
if ! command -v aws &> /dev/null; then echo "❌ Fehler: 'aws' CLI 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 "============================================================"
|
||||||
echo " 📧 Migration DNS Setup für: $DOMAIN_NAME"
|
echo " 🛡️ DNS Migration Setup für: $DOMAIN_NAME"
|
||||||
|
echo " 🌍 Region: $AWS_REGION"
|
||||||
|
[ "$DRY_RUN" = "true" ] && echo " ⚠️ DRY RUN MODE - Keine Änderungen!"
|
||||||
echo "============================================================"
|
echo "============================================================"
|
||||||
if [ "$DRY_RUN" = "true" ]; then
|
|
||||||
echo " ⚠️ DRY RUN - Es werden KEINE Änderungen vorgenommen!"
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ==========================================
|
# 1. ZONE ID HOLEN
|
||||||
# 1. MAIL FROM Subdomain aus SES ermitteln
|
echo "🔍 Suche Cloudflare Zone ID..."
|
||||||
# ==========================================
|
ZONE_ID=$(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" | jq -r '.result[0].id')
|
||||||
|
|
||||||
echo "--- [1/7] MAIL FROM Subdomain aus SES ermitteln ---"
|
if [ "$ZONE_ID" == "null" ] || [ -z "$ZONE_ID" ]; then
|
||||||
|
echo "❌ Zone nicht gefunden."
|
||||||
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
echo " ✅ Zone ID: $ZONE_ID"
|
||||||
|
|
||||||
MAIL_FROM_DOMAIN=$(echo "$SES_IDENTITY" | jq -r '.MailFromAttributes.MailFromDomain // empty')
|
# ------------------------------------------------------------------
|
||||||
|
# FUNKTION: ensure_record
|
||||||
|
# Prüft Existenz -> Create oder Update (je nach Typ)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
ensure_record() {
|
||||||
|
local type=$1
|
||||||
|
local name=$2
|
||||||
|
local content=$3
|
||||||
|
local proxied=${4:-false}
|
||||||
|
local priority=$5 # Optional für MX
|
||||||
|
|
||||||
if [ -z "$MAIL_FROM_DOMAIN" ]; then
|
echo " ⚙️ Prüfe $type $name..."
|
||||||
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")
|
# Bestehenden Record suchen
|
||||||
MAIL_FROM_PREFIX=$(echo "$MAIL_FROM_DOMAIN" | sed "s/\.${DOMAIN_NAME}$//")
|
# Hinweis: Wir suchen exakt nach Name und Typ
|
||||||
|
local search_res=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=$type&name=$name" \
|
||||||
|
-H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json")
|
||||||
|
|
||||||
echo " ✓ MAIL FROM Domain: ${MAIL_FROM_DOMAIN} (Prefix: ${MAIL_FROM_PREFIX})"
|
local rec_id=$(echo "$search_res" | jq -r '.result[0].id')
|
||||||
|
local rec_content=$(echo "$search_res" | jq -r '.result[0].content')
|
||||||
|
|
||||||
# ==========================================
|
# JSON Body bauen
|
||||||
# 2. Cloudflare Zone ID ermitteln
|
if [ "$type" == "MX" ]; then
|
||||||
# ==========================================
|
json_data=$(jq -n --arg t "$type" --arg n "$name" --arg c "$content" --argjson p "$proxied" --argjson prio "$priority" \
|
||||||
|
'{type: $t, name: $n, content: $c, ttl: 3600, proxied: $p, priority: $prio}')
|
||||||
echo ""
|
elif [ "$type" == "TXT" ]; then
|
||||||
echo "--- [2/7] Cloudflare Zone ID ermitteln ---"
|
# Bei TXT Quotes escapen falls nötig, aber jq macht das meist gut
|
||||||
|
json_data=$(jq -n --arg t "$type" --arg n "$name" --arg c "$content" --argjson p "$proxied" \
|
||||||
ZONE_RESPONSE=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$DOMAIN_NAME" \
|
'{type: $t, name: $n, content: $c, ttl: 3600, proxied: $p}')
|
||||||
-H "Authorization: Bearer $CF_API_TOKEN" \
|
else
|
||||||
-H "Content-Type: application/json")
|
json_data=$(jq -n --arg t "$type" --arg n "$name" --arg c "$content" --argjson p "$proxied" \
|
||||||
|
'{type: $t, name: $n, content: $c, ttl: 3600, proxied: $p}')
|
||||||
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
|
||||||
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
|
|
||||||
|
|
||||||
|
# LOGIK
|
||||||
|
if [ "$rec_id" == "null" ]; then
|
||||||
|
# --- CREATE ---
|
||||||
if [ "$DRY_RUN" = "true" ]; then
|
if [ "$DRY_RUN" = "true" ]; then
|
||||||
echo " [DRY RUN] Würde erstellen: $TYPE $NAME → $CONTENT"
|
echo " [DRY] Würde ERSTELLEN: $content"
|
||||||
[ -n "$PRIORITY" ] && echo " Priorität: $PRIORITY"
|
else
|
||||||
|
res=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
|
||||||
|
-H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" --data "$json_data")
|
||||||
|
if [ "$(echo $res | jq -r .success)" == "true" ]; then
|
||||||
|
echo " ✅ Erstellt."
|
||||||
|
else
|
||||||
|
echo " ❌ Fehler beim Erstellen: $(echo $res | jq -r .errors[0].message)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# --- EXISTS ---
|
||||||
|
if [ "$rec_content" == "$content" ]; then
|
||||||
|
echo " 🆗 Identisch vorhanden. Überspringe."
|
||||||
|
else
|
||||||
|
# Inhalt anders -> Update oder Error?
|
||||||
|
if [ "$type" == "MX" ] && [ "$name" == "$DOMAIN_NAME" ]; then
|
||||||
|
echo " ⛔ MX Record existiert aber ist anders!"
|
||||||
|
echo " Gefunden: $rec_content"
|
||||||
|
echo " Erwartet: $content"
|
||||||
|
echo " ABBRUCH: Bitte alten MX Record ID $rec_id manuell löschen."
|
||||||
|
# Wir brechen hier nicht das ganze Script ab, aber setzen den neuen nicht.
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local JSON_DATA=""
|
# Für TXT (SPF/DMARC) oder CNAME machen wir ein UPDATE (Overwrite)
|
||||||
|
if [ "$DRY_RUN" = "true" ]; then
|
||||||
if [ "$TYPE" = "MX" ]; then
|
echo " [DRY] Würde UPDATEN von '$rec_content' auf '$content'"
|
||||||
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
|
else
|
||||||
JSON_DATA="{
|
res=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$rec_id" \
|
||||||
\"type\": \"$TYPE\", \"name\": \"$NAME\", \"content\": \"$CONTENT\",
|
-H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" --data "$json_data")
|
||||||
\"ttl\": $TTL, \"proxied\": $PROXIED
|
if [ "$(echo $res | jq -r .success)" == "true" ]; then
|
||||||
}"
|
echo " 🔄 Aktualisiert."
|
||||||
|
else
|
||||||
|
echo " ❌ Fehler beim Update: $(echo $res | jq -r .errors[0].message)"
|
||||||
|
fi
|
||||||
fi
|
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
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ==========================================
|
# ------------------------------------------------------------------
|
||||||
# 4. DKIM Records (aus SES API)
|
# SCHRITT 1: MAIL FROM ermitteln
|
||||||
# ==========================================
|
# ------------------------------------------------------------------
|
||||||
|
echo ""
|
||||||
|
echo "--- 1. MAIL FROM Domain ---"
|
||||||
|
# Wenn von außen nicht gesetzt, versuche via AWS
|
||||||
|
if [ -z "$MAIL_FROM_DOMAIN" ]; then
|
||||||
|
echo " Variable MAIL_FROM_DOMAIN leer, frage AWS SES..."
|
||||||
|
SES_JSON=$(aws sesv2 get-email-identity --email-identity $DOMAIN_NAME --region $AWS_REGION 2>/dev/null)
|
||||||
|
MAIL_FROM_DOMAIN=$(echo "$SES_JSON" | jq -r '.MailFromAttributes.MailFromDomain')
|
||||||
|
|
||||||
echo "--- [4/7] SES DKIM Records ---"
|
if [ "$MAIL_FROM_DOMAIN" == "null" ] || [ -z "$MAIL_FROM_DOMAIN" ]; then
|
||||||
|
MAIL_FROM_DOMAIN="mail.$DOMAIN_NAME"
|
||||||
DKIM_TOKENS=$(aws ses get-identity-dkim-attributes \
|
echo " ⚠️ Keine MAIL FROM in SES gefunden. Fallback auf: $MAIL_FROM_DOMAIN"
|
||||||
--identities ${DOMAIN_NAME} --region ${AWS_REGION} \
|
fi
|
||||||
--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
|
else
|
||||||
echo " ⚠️ Keine DKIM Tokens gefunden. SES Identity evtl. noch nicht verifiziert?"
|
echo " Nutze vorgegebene MAIL FROM: $MAIL_FROM_DOMAIN"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# SES Verification Token
|
# ------------------------------------------------------------------
|
||||||
VERIFICATION_TOKEN=$(aws ses get-identity-verification-attributes \
|
# SCHRITT 2: DKIM Records (CNAME)
|
||||||
--identities ${DOMAIN_NAME} --region ${AWS_REGION} \
|
# ------------------------------------------------------------------
|
||||||
--query "VerificationAttributes.\"${DOMAIN_NAME}\".VerificationToken" --output text 2>/dev/null)
|
echo ""
|
||||||
|
echo "--- 2. DKIM Records ---"
|
||||||
|
TOKENS=$(aws ses get-identity-dkim-attributes --identities $DOMAIN_NAME --region $AWS_REGION --query "DkimAttributes.\"$DOMAIN_NAME\".DkimTokens" --output text)
|
||||||
|
for token in $TOKENS; do
|
||||||
|
ensure_record "CNAME" "${token}._domainkey.$DOMAIN_NAME" "${token}.dkim.amazonses.com" false
|
||||||
|
done
|
||||||
|
|
||||||
if [ -n "$VERIFICATION_TOKEN" ] && [ "$VERIFICATION_TOKEN" != "None" ]; then
|
# ------------------------------------------------------------------
|
||||||
create_dns_record "TXT" "_amazonses.${DOMAIN_NAME}" "${VERIFICATION_TOKEN}" "false"
|
# SCHRITT 3: SES Verification (_amazonses)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
echo ""
|
||||||
|
echo "--- 3. SES Verification TXT ---"
|
||||||
|
VERIF_TOKEN=$(aws ses get-identity-verification-attributes --identities $DOMAIN_NAME --region $AWS_REGION --query "VerificationAttributes.\"$DOMAIN_NAME\".VerificationToken" --output text)
|
||||||
|
if [ "$VERIF_TOKEN" != "None" ]; then
|
||||||
|
ensure_record "TXT" "_amazonses.$DOMAIN_NAME" "$VERIF_TOKEN" false
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ==========================================
|
# ------------------------------------------------------------------
|
||||||
# 5. MX Record (SES Inbound)
|
# SCHRITT 4: MAIL FROM Subdomain (MX + SPF)
|
||||||
# ==========================================
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "--- [5/7] MX Record ---"
|
echo "--- 4. MAIL FROM Subdomain ($MAIL_FROM_DOMAIN) ---"
|
||||||
|
# MX für die Subdomain (feedback loop)
|
||||||
if [ "$SKIP_MX" = "true" ]; then
|
ensure_record "MX" "$MAIL_FROM_DOMAIN" "feedback-smtp.$AWS_REGION.amazonses.com" false 10
|
||||||
echo " ⏭ MX übersprungen (SKIP_MX=true)"
|
# SPF für die Subdomain (strikte SES Regel)
|
||||||
echo " → Setze MX manuell wenn du bereit bist:"
|
ensure_record "TXT" "$MAIL_FROM_DOMAIN" "v=spf1 include:amazonses.com ~all" false
|
||||||
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)
|
|
||||||
# ==========================================
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# SCHRITT 5: Root Domain SPF (Merge Logic)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
echo ""
|
echo ""
|
||||||
echo "--- [6/7] MAIL FROM Subdomain: ${MAIL_FROM_DOMAIN} ---"
|
echo "--- 5. Root Domain SPF ---"
|
||||||
|
|
||||||
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
|
if [ -n "$OLD_PROVIDER_SPF" ]; then
|
||||||
SPF_RECORD="v=spf1 include:amazonses.com ${OLD_PROVIDER_SPF} ~all"
|
# Merge: SES + Alter Provider
|
||||||
echo " ℹ️ Migrations-SPF (enthält alten Provider):"
|
FINAL_SPF="v=spf1 include:amazonses.com $OLD_PROVIDER_SPF ~all"
|
||||||
|
echo " ℹ️ Modus: Migration (SES + Alt)"
|
||||||
else
|
else
|
||||||
SPF_RECORD="v=spf1 include:amazonses.com ~all"
|
# Nur SES
|
||||||
echo " ℹ️ Standard-SPF (kein alter Provider angegeben):"
|
FINAL_SPF="v=spf1 include:amazonses.com ~all"
|
||||||
|
echo " ℹ️ Modus: SES only"
|
||||||
fi
|
fi
|
||||||
echo " ${SPF_RECORD}"
|
ensure_record "TXT" "$DOMAIN_NAME" "$FINAL_SPF" false
|
||||||
|
|
||||||
create_dns_record "TXT" "${DOMAIN_NAME}" "${SPF_RECORD}" "false"
|
# ------------------------------------------------------------------
|
||||||
|
# SCHRITT 6: Root Domain MX (Safety First)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
echo ""
|
||||||
|
echo "--- 6. Root Domain MX ---"
|
||||||
|
# Hier wollen wir den Inbound SMTP von AWS (falls man AWS WorkMail nutzt oder DMS via AWS ingress)
|
||||||
|
# WARTE: Du nutzt DMS. Dein DMS hat vermutlich eine eigene IP/Hostname (z.B. mail.buddelectric.net).
|
||||||
|
# Wenn du SES NUR ZUM SENDEN nutzt, darfst du den Root MX NICHT auf Amazon ändern!
|
||||||
|
#
|
||||||
|
# Annahme: Du willst den MX für den Empfang setzen.
|
||||||
|
# Da du oben "feedback-smtp" erwähnt hast, geht es wohl um den SES Return-Path.
|
||||||
|
# Aber der "echte MX" für die Domain ($DOMAIN_NAME) zeigt auf DEINEN Mailserver (DMS).
|
||||||
|
#
|
||||||
|
# Falls du den MX auf deinen DMS Server zeigen lassen willst:
|
||||||
|
TARGET_MX=${TARGET_MX:-"mail.$DOMAIN_NAME"}
|
||||||
|
echo " ℹ️ Ziel-MX ist: $TARGET_MX"
|
||||||
|
|
||||||
# DMARC
|
# HINWEIS: MX Records brauchen oft einen Hostnamen, keine IP.
|
||||||
create_dns_record "TXT" "_dmarc.${DOMAIN_NAME}" "v=DMARC1; p=none; pct=100; rua=mailto:postmaster@${DOMAIN_NAME}" "false"
|
# Wir prüfen, ob ein MX existiert.
|
||||||
|
ensure_record "MX" "$DOMAIN_NAME" "$TARGET_MX" false 10
|
||||||
|
|
||||||
# ==========================================
|
# ------------------------------------------------------------------
|
||||||
# ZUSAMMENFASSUNG
|
# SCHRITT 7: DMARC
|
||||||
# ==========================================
|
# ------------------------------------------------------------------
|
||||||
|
echo ""
|
||||||
|
echo "--- 7. DMARC ---"
|
||||||
|
ensure_record "TXT" "_dmarc.$DOMAIN_NAME" "v=DMARC1; p=none; rua=mailto:postmaster@$DOMAIN_NAME" false
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# SCHRITT 8: Autodiscover / Autoconfig
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
echo ""
|
||||||
|
echo "--- 8. Autodiscover / Autoconfig ---"
|
||||||
|
# Ziel ist meist der IMAP/SMTP Server
|
||||||
|
echo " ℹ️ Ziel für Clients: $TARGET_MAIL_SERVER"
|
||||||
|
|
||||||
|
ensure_record "CNAME" "autodiscover.$DOMAIN_NAME" "$TARGET_MAIL_SERVER" false
|
||||||
|
ensure_record "CNAME" "autoconfig.$DOMAIN_NAME" "$TARGET_MAIL_SERVER" false
|
||||||
|
|
||||||
|
|
||||||
|
# Füge das zu deinem Skript hinzu (Schritt 9 optional):
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# SCHRITT 9: SRV Records (Service Discovery)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
echo ""
|
||||||
|
echo "--- 9. SRV Records (Service Discovery) ---"
|
||||||
|
# Das hilft Outlook, direkt "email-srvr.com" zu nutzen statt "mail.domain.tld"
|
||||||
|
# Format: _service._proto.name TTL class SRV priority weight port target
|
||||||
|
|
||||||
|
# IMAP SRV
|
||||||
|
ensure_record "SRV" "_imap._tcp.$DOMAIN_NAME" "0 5 143 $TARGET_MAIL_SERVER" false
|
||||||
|
# IMAPS SRV (Port 993)
|
||||||
|
ensure_record "SRV" "_imaps._tcp.$DOMAIN_NAME" "0 5 993 $TARGET_MAIL_SERVER" false
|
||||||
|
# SUBMISSION SRV (Port 587)
|
||||||
|
ensure_record "SRV" "_submission._tcp.$DOMAIN_NAME" "0 5 587 $TARGET_MAIL_SERVER" false
|
||||||
|
|
||||||
|
echo " ✅ SRV Records gesetzt (Server: $TARGET_MAIL_SERVER)"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "============================================================"
|
echo "✅ Fertig."
|
||||||
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 "============================================================"
|
|
||||||
Loading…
Reference in New Issue