Files
boc/build/c3-apply-dns.sh
T

558 lines
23 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# =============================================================================
# BUILD-C3 — DNS Apply Script
# Wavult Group — Partner-/Geo-modell DNS-implementation
# =============================================================================
# Förberett av: OpenClaw BUILD-C2 subagent | 2026-06-06
# Status: FÖRBERETT — EJ KÖRT
#
# SÄKERHETSPOLICY:
# Detta skript KÖR INGA DNS-ändringar automatiskt.
# Varje sektion är en funktion som måste anropas explicit.
# Kör: bash c3-apply-dns.sh --help för instruktioner.
#
# KÖRMILJÖ:
# - Kräver AWS CLI med profil 'platform-ai-agent' (eller default-profil)
# - Kräver IAM-behörighet: route53:ChangeResourceRecordSets, route53:ListResourceRecordSets
# - Kräver: jq (för JSON-parsning)
# - Region: eu-north-1 (Route53 är globalt, men default-profil används)
#
# GODKÄNNANDESTRUKTUR:
# ✅ SÄKER — Kan köras utan Eriks godkännande (inga MX-ändringar, inga deletions)
# ⚠️ ERIK-OK — Kräver explicit godkännande av Erik innan körning
# 🔴 DESTRUKTIV — Tar bort data, kräver verifiering + Erik-godkännande
# =============================================================================
set -euo pipefail
# ---------------------------------------------------------------------------
# Konfiguration
# ---------------------------------------------------------------------------
AWS_REGION="eu-north-1"
DMARC_RUA="dmarc@aamos.systems"
MAILU_MX="mail.wavult.com"
MAILU_MX_PRIORITY=10
# DKIM-selektor och pubkey måste hämtas från Mailu-admin och fyllas i nedan
DKIM_SELECTOR="mail"
# OBS: Ersätt DKIM_PUBKEY_PLACEHOLDER med faktisk pubkey från Mailu-admin
# Hämta via: Mailu Admin → Domain → <domain> → DKIM → kopiera pubkey-strängen
DKIM_PUBKEY="HÄMTA-FRÅN-MAILU-ADMIN-FÖRE-KÖRNING"
# ---------------------------------------------------------------------------
# Hjälpfunktioner
# ---------------------------------------------------------------------------
log() { echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] $*"; }
warn() { echo "[WARN] $*" >&2; }
die() { echo "[ERROR] $*" >&2; exit 1; }
check_deps() {
command -v aws >/dev/null || die "aws CLI saknas"
command -v jq >/dev/null || die "jq saknas"
[[ "$DKIM_PUBKEY" == "HÄMTA-FRÅN-MAILU-ADMIN-FÖRE-KÖRNING" ]] && \
die "DKIM_PUBKEY är inte ifyllt — hämta från Mailu-admin och sätt variabeln"
}
# Skicka en Route53 change batch (JSON-sträng) och vänta tills den är INSYNC
apply_change_batch() {
local zone_id="$1"
local batch_json="$2"
local description="$3"
log "Applicerar ändring på zone $zone_id: $description"
local change_id
change_id=$(aws route53 change-resource-record-sets \
--hosted-zone-id "$zone_id" \
--change-batch "$batch_json" \
--query 'ChangeInfo.Id' --output text)
log "Change ID: $change_id — väntar på INSYNC..."
aws route53 wait resource-record-sets-changed --id "$change_id"
log "$description — INSYNC"
}
# Bygg ett UPSERT-batch för SPF + DMARC (ingen MX = partnerdomän/avveckling)
build_no_mx_spf_dmarc_batch() {
local domain="$1"
jq -n \
--arg domain "$domain" \
--arg rua "$DMARC_RUA" \
'{
"Comment": ("SPF -all + DMARC reject for " + $domain + " (no MX)"),
"Changes": [
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": ($domain + "."),
"Type": "TXT",
"TTL": 300,
"ResourceRecords": [{"Value": "\"v=spf1 -all\""}]
}
},
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": ("_dmarc." + $domain + "."),
"Type": "TXT",
"TTL": 300,
"ResourceRecords": [{"Value": ("\"v=DMARC1; p=reject; rua=mailto:" + $rua + "\"")}]
}
}
]
}'
}
# Bygg ett UPSERT-batch för MX + SPF + DKIM + DMARC p=none (Nivå 2 EU-landsdomän)
build_eu_level2_batch() {
local domain="$1"
local dkim_selector="$2"
local dkim_pubkey="$3"
jq -n \
--arg domain "$domain" \
--arg mx "$MAILU_MX" \
--argjson prio "$MAILU_MX_PRIORITY" \
--arg sel "$dkim_selector" \
--arg key "$dkim_pubkey" \
--arg rua "$DMARC_RUA" \
'{
"Comment": ("EU Level-2 mail setup for " + $domain),
"Changes": [
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": ($domain + "."),
"Type": "MX",
"TTL": 300,
"ResourceRecords": [{"Value": (($prio | tostring) + " " + $mx + ".")}]
}
},
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": ($domain + "."),
"Type": "TXT",
"TTL": 300,
"ResourceRecords": [{"Value": ("\"v=spf1 mx a:" + $mx + " ~all\"")}]
}
},
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": ($sel + "._domainkey." + $domain + "."),
"Type": "TXT",
"TTL": 300,
"ResourceRecords": [{"Value": ("\"v=DKIM1; k=rsa; p=" + $key + "\"")}]
}
},
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": ("_dmarc." + $domain + "."),
"Type": "TXT",
"TTL": 300,
"ResourceRecords": [{"Value": ("\"v=DMARC1; p=none; rua=mailto:" + $rua + "; fo=1\"")}]
}
}
]
}'
}
# Bygg DELETE-batch för MX-post (kräver att befintlig MX-post anges exakt)
build_delete_mx_batch() {
local domain="$1"
local existing_mx_value="$2" # t.ex. "10 mail.wavult.com."
jq -n \
--arg domain "$domain" \
--arg mxval "$existing_mx_value" \
--arg rua "$DMARC_RUA" \
'{
"Comment": ("REMOVE MX + add SPF -all + DMARC reject for " + $domain),
"Changes": [
{
"Action": "DELETE",
"ResourceRecordSet": {
"Name": ($domain + "."),
"Type": "MX",
"TTL": 300,
"ResourceRecords": [{"Value": $mxval}]
}
},
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": ($domain + "."),
"Type": "TXT",
"TTL": 300,
"ResourceRecords": [{"Value": "\"v=spf1 -all\""}]
}
},
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": ("_dmarc." + $domain + "."),
"Type": "TXT",
"TTL": 300,
"ResourceRecords": [{"Value": ("\"v=DMARC1; p=reject; rua=mailto:" + $rua + "\"")}]
}
}
]
}'
}
# ---------------------------------------------------------------------------
# FAS 1 — SÄKER: Partnerdomäner (Nivå 3) — ingen MX + DMARC reject
# ✅ SÄKER — Inga MX-ändringar, inga deletions. Lägger till SPF -all och DMARC reject.
# Dessa domäner har ALDRIG haft aktiv MX (bekräftat i C1).
# ---------------------------------------------------------------------------
fas1_partner_domains() {
log "=== FAS 1: Partnerdomäner — SPF -all + DMARC reject ==="
log "✅ SÄKER: Inga destruktiva ändringar, inga MX-poster påverkas"
# Landvex partner-domäner
declare -A PARTNER_ZONES=(
["landvex.mx"]="Z035940510YOVC15ORL0P"
["landvex.lat"]="Z0452682DQUO68GWV1LF"
["landvex.asia"]="Z07961892X5K5ZAGU9P7A"
["landvex.in"]="Z0114999EEC7DABEJF1R"
["landvex.jp"]="Z06417423FRM1NZ24HQIG"
# landvex.co och .tech — se FLAGGA nedan
# ["landvex.co"]="Z06390531NTPHQPHH556O" # FLAGGA: Klassificering oklar — Erik-beslut
# ["landvex.tech"]="Z03595122SFEDANKC6HLC" # FLAGGA: Klassificering oklar — Erik-beslut
# Quixzoom partner-domäner
["quixzoom.mx"]="Z03592031KP18830ISSFO"
["quixzoom.lat"]="Z07960653PSQVVJH6MWKQ"
["quixzoom.in"]="Z0116012C853QJX10S7F"
["quixzoom.jp"]="Z0691646HX1TESS78DHG"
["quixzoom.asia"]="Z03303363QVPGTSXJ4CBP"
# quixzoom.co och .tech — se FLAGGA nedan
# ["quixzoom.co"]="Z0640526G3V30S6YP4KW" # FLAGGA: Klassificering oklar — Erik-beslut
# ["quixzoom.tech"]="Z06392373COSIWKDCKU02" # FLAGGA: Klassificering oklar — Erik-beslut
)
for domain in "${!PARTNER_ZONES[@]}"; do
local zone_id="${PARTNER_ZONES[$domain]}"
local batch
batch=$(build_no_mx_spf_dmarc_batch "$domain")
apply_change_batch "$zone_id" "$batch" "Partnerdomän: $domain"
done
log "=== FAS 1 klar ==="
}
# ---------------------------------------------------------------------------
# FAS 2 — SÄKER: EU Nivå 2 (landsdomäner) — MX + SPF + DKIM + DMARC p=none
# ✅ SÄKER: Aktiverar mailmottagning (redirect → primär via Mailu catch-all).
# DMARC p=none = neutral, samlar rapporter utan att blockera.
# FÖRUTSÄTTNING: DKIM_PUBKEY måste vara ifyllt med rätt pubkey från Mailu.
# ---------------------------------------------------------------------------
fas2_eu_level2_domains() {
log "=== FAS 2: EU Nivå 2 (landsdomäner) — MX + SPF + DKIM + DMARC p=none ==="
log "✅ SÄKER: Aktiverar mailmottagning, DMARC p=none (neutral)"
log "⚠️ KRAV: DKIM_PUBKEY måste vara ifyllt — kontrollera variabeln"
# Landvex EU Nivå 2 (har idag ingen MX)
declare -A EU2_ZONES=(
["landvex.se"]="Z094509730UHHMSPHTT2N"
["landvex.eu"]="Z03535482N3R7HVG35RLC"
["landvex.de"]="Z09315373152U0OFU6S2J"
["landvex.fr"]="Z05975142FLYRUIUNWORN"
["landvex.nl"]="Z05939403BEIHDPX4CFG1"
["landvex.es"]="Z03544502KQL4NRCHY4AU"
["landvex.it"]="Z06922101ZAJMZJR3ZLJI"
["landvex.fi"]="Z0646854PR1U78P1AJS0"
["landvex.co.uk"]="Z06400622V84QB2SX67DT"
["landvex.be"]="Z0642291ACB2WGN8KIPV"
["landvex.dk"]="Z03432136USE09VHQGF9"
["landvex.net"]="Z06400772MZF13N0GK4HC"
["landvex.org"]="Z03600372562O0AYE74AX"
["landvex-gov.eu"]="Z03523293HYR2LY9FB8V3"
["landvex-intelligence.eu"]="Z04528812DA06BOTM71QN"
["landvex-infrastructure.eu"]="Z06915882XI394D5IHZZ0"
["landvex-systems.com"]="Z03530401ABRF61KQ4UGD"
["landvex-secure.com"]="Z06922211OLB8FWUK47U5"
# Quixzoom EU Nivå 2
["quixzoom.eu"]="Z0352300H6B69TZB3FB"
["quixzoom.nl"]="Z08393705I60UK5C6T3D"
["quixzoom.de"]="Z02042236EVWD34RIL8"
["quixzoom.fr"]="Z0590787VZDW5D5XHQAV"
["quixzoom.es"]="Z03537332N1170YZA6Y96"
["quixzoom.it"]="Z01178703OEP5JBWVMCVN"
["quixzoom.fi"]="Z01156752B3NC8OLB0VQ3"
["quixzoom.ch"]="Z05948653CJP2I13KROV9"
["quixzoom.be"]="Z02403741O3VID61R4CWK"
["quixzoom.dk"]="Z03418436Y57RW7ET3I2"
["quixzoom.net"]="Z064013718W8XEVMMH8I3"
["quixzoom.org"]="Z0646024GDLOX0L6C9L0"
# Wavult
["wavult.se"]="Z09416523AFDKP6WRCHRL"
# landvex.ch — använd en av de två zones (se duplikat-hantering fas5)
# FLAGGA: Verifiera vilken zone som är aktiv (NS-delegation) INNAN körning
# ["landvex.ch"]="Z060292760R1ZY0PQH3V" # Duplikat — verifiera FÖR körning
# quixzoom.co.uk — samma
# ["quixzoom.co.uk"]="Z035263432T28MD24XUIG" # Duplikat — verifiera FÖR körning
)
for domain in "${!EU2_ZONES[@]}"; do
local zone_id="${EU2_ZONES[$domain]}"
local batch
batch=$(build_eu_level2_batch "$domain" "$DKIM_SELECTOR" "$DKIM_PUBKEY")
apply_change_batch "$zone_id" "$batch" "EU Nivå 2: $domain"
done
log "=== FAS 2 klar ==="
}
# ---------------------------------------------------------------------------
# FAS 3 — SÄKER: pixdrift.com — SPF -all + DMARC reject (har ingen MX)
# ✅ SÄKER: Pixdrift är dödd, har aldrig haft MX (bekräftat C1). Inga deletions.
# ---------------------------------------------------------------------------
fas3_pixdrift() {
log "=== FAS 3: pixdrift.com — SPF -all + DMARC reject ==="
log "✅ SÄKER: Pixdrift har ingen MX — lägger bara till SPF och DMARC"
local zone_id="Z06885272CR0QX3ZHVMTK"
local batch
batch=$(build_no_mx_spf_dmarc_batch "pixdrift.com")
apply_change_batch "$zone_id" "$batch" "Avveckling: pixdrift.com"
log "=== FAS 3 klar ==="
}
# ---------------------------------------------------------------------------
# FAS 4A — ⚠️ ERIK-OK: Verifiera Mailu-loggar FÖR körning
# Kontrollera att ingen aktiv korrespondens finns på corpfitt/hypbit
# ---------------------------------------------------------------------------
fas4a_verify_dead_domains_mail() {
log "=== FAS 4A: Verifiering av aktiv korrespondens (READ-ONLY) ==="
log "⚠️ KRÄVER MANUELL KONTROLL: Logga in i Mailu-admin (mail.wavult.com)"
log ""
log "Kontrollera följande INNAN fas4b körs:"
log " 1. Gå till Mailu-admin → Domains → corpfitt.com"
log " - Finns det konton/alias? → Ta bort dem i Mailu FÖR att ta bort MX i DNS"
log " - Kolla mailloggar (Mailu → Logs) efter inkommande mail senaste 90 dagar"
log " 2. Upprepa för hypbit.com"
log " 3. Om inga konton/alias + ingen trafik → godkänn fas4b med Erik"
log ""
log "Alternativt via AWS SSM (root-access):"
log " sudo grep -E '(corpfitt|hypbit)' /var/log/mail.log | tail -50"
log " (Anpassa loggväg efter Mailu-installationens konfiguration)"
log ""
log "=== FAS 4A klar — KÖR INTE FAS 4B UTAN ERIKS GODKÄNNANDE ==="
}
# ---------------------------------------------------------------------------
# FAS 4B — ⚠️ ERIK-OK: corpfitt.com + hypbit.com — Ta bort MX + DMARC reject
# KRÄVER: Fas 4A genomförd + Eriks godkännande + verifiering att inga konton finns
# ---------------------------------------------------------------------------
fas4b_decommission_dead_mx() {
log "=== FAS 4B: Avveckling MX för döda domäner ==="
log "⚠️ KRÄVER ERIKS GODKÄNNANDE"
log "⚠️ KRÄVER: Fas 4A genomförd (Mailu-loggar kontrollerade)"
log ""
# Bekräftelseprompt — kräver explicit input
read -r -p "Har Erik godkänt denna åtgärd? Har fas4a genomförts? (skriv 'JA ERIK HAR GODKÄNT' för att fortsätta): " confirm
if [[ "$confirm" != "JA ERIK HAR GODKÄNT" ]]; then
log "Avbröt — inget ändrat."
exit 0
fi
# corpfitt.com
log "Tar bort MX på corpfitt.com (Zone: Z03586003P55GRO87T6M3)"
local corpfitt_batch
corpfitt_batch=$(build_delete_mx_batch "corpfitt.com" "10 mail.wavult.com.")
apply_change_batch "Z03586003P55GRO87T6M3" "$corpfitt_batch" "Avveckling MX: corpfitt.com"
# hypbit.com
log "Tar bort MX på hypbit.com (Zone: Z06944491GZGBRKHYNYSK)"
local hypbit_batch
hypbit_batch=$(build_delete_mx_batch "hypbit.com" "10 mail.wavult.com.")
apply_change_batch "Z06944491GZGBRKHYNYSK" "$hypbit_batch" "Avveckling MX: hypbit.com"
log "=== FAS 4B klar ==="
}
# ---------------------------------------------------------------------------
# FAS 5 — ⚠️ ERIK-OK: Duplikatzon-konsolidering
# KRÄVER: NS-verifiering mot registraren, Eriks godkännande, manuell record-jämförelse
# ---------------------------------------------------------------------------
fas5_inspect_duplicate_zones() {
log "=== FAS 5: Duplikatzoner — Inspektion (READ-ONLY) ==="
log "✅ SÄKER: Denna funktion listar records och NS — inga ändringar"
declare -A DUP_ZONES=(
["landvex.ch"]="Z060292760R1ZY0PQH3V Z00716462L6XS9QZX1HHS"
["quixzoom.co.uk"]="Z035263432T28MD24XUIG Z06391212CQRAVQ1FMNHV"
["aamos.ai"]="Z0992155TZTAVTB9DKU Z10021043NUR85M9WUOQM"
["wavult.com"]="Z03332778KCOYPM9OBKP Z02946821NE63OOAKDTKA"
)
for domain in "${!DUP_ZONES[@]}"; do
IFS=' ' read -r zone_a zone_b <<< "${DUP_ZONES[$domain]}"
log ""
log "--- $domain ---"
log "Zone A ($zone_a) records:"
aws route53 list-resource-record-sets --hosted-zone-id "$zone_a" \
--query 'ResourceRecordSets[*].{Type:Type,Name:Name,Records:ResourceRecords}' \
--output table 2>/dev/null || warn "Kunde inte lista records för $zone_a"
log "Zone B ($zone_b) records:"
aws route53 list-resource-record-sets --hosted-zone-id "$zone_b" \
--query 'ResourceRecordSets[*].{Type:Type,Name:Name,Records:ResourceRecords}' \
--output table 2>/dev/null || warn "Kunde inte lista records för $zone_b"
log "Extern NS-kontroll för $domain:"
dig NS "$domain" +short || warn "dig-fel för $domain"
done
log ""
log "=== FAS 5 INSPEKTION KLAR ==="
log "Jämför NS-poster ovan mot registrarens delegering."
log "Zone vars NS matchar registraren = aktiv. Den andra = phantom (kan raderas)."
log "RADERA INTE zones via detta skript — gör det via AWS-konsolen efter manuell verifiering."
}
# ---------------------------------------------------------------------------
# FAS 6 — ⚠️ DMARC-skärpning (aktiva maildomäner)
# KRÄVER: 2 veckor DMARC-rapportanalys + DKIM-verifiering
# ---------------------------------------------------------------------------
fas6_dmarc_tighten_active_domains() {
log "=== FAS 6: DMARC-skärpning på aktiva maildomäner ==="
log "⚠️ KRÄVER: DKIM verifierat i praktiken (mail-tester.com)"
log "⚠️ KRÄVER: 2 veckors DMARC-rapportanalys utan avvikelser"
log "⚠️ KRÄVER: Eriks godkännande"
log ""
# Dessa domäner har AKTIV MX och roll-routing — skärpning kräver extra försiktighet
local ACTIVE_MAIL_DOMAINS=(
"aamos.systems:Z03908683A2AIMRZR85BQ"
"landvex.com:Z05966261S4KJRAGIJ6FQ"
"quixzoom.com:Z04915922EJ53UAUIOPFG"
# wavult.com och aamos.ai har duplikatzoner — lös fas5 först
# "wavult.com:Z03332778KCOYPM9OBKP"
# "aamos.ai:Z0992155TZTAVTB9DKU"
)
read -r -p "Har DKIM verifierats? Har DMARC-rapporter analyserats 2 veckor? Eriks godkännande? (skriv 'JA VERIFIERAT' för att fortsätta): " confirm
if [[ "$confirm" != "JA VERIFIERAT" ]]; then
log "Avbröt — inget ändrat."
exit 0
fi
read -r -p "Välj DMARC-policy [quarantine/reject]: " policy
if [[ "$policy" != "quarantine" && "$policy" != "reject" ]]; then
die "Ogiltig policy: $policy. Välj 'quarantine' eller 'reject'."
fi
read -r -p "Välj pct (1-100, t.ex. 10 för gradvis utrullning): " pct
if ! [[ "$pct" =~ ^[0-9]+$ ]] || (( pct < 1 || pct > 100 )); then
die "Ogiltigt pct-värde: $pct"
fi
for entry in "${ACTIVE_MAIL_DOMAINS[@]}"; do
IFS=':' read -r domain zone_id <<< "$entry"
log "Skärper DMARC på $domain → p=$policy; pct=$pct"
local batch
batch=$(jq -n \
--arg domain "$domain" \
--arg policy "$policy" \
--argjson pct "$pct" \
--arg rua "$DMARC_RUA" \
'{
"Comment": ("DMARC tighten to " + $policy + " pct=" + ($pct|tostring) + " for " + $domain),
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": ("_dmarc." + $domain + "."),
"Type": "TXT",
"TTL": 300,
"ResourceRecords": [{"Value": ("\"v=DMARC1; p=" + $policy + "; pct=" + ($pct|tostring) + "; rua=mailto:" + $rua + "; fo=1\"")}]
}
}]
}')
apply_change_batch "$zone_id" "$batch" "DMARC skärpning: $domain → p=$policy pct=$pct"
done
log "=== FAS 6 klar ==="
}
# ---------------------------------------------------------------------------
# Hjälptext och dispatcher
# ---------------------------------------------------------------------------
usage() {
cat <<EOF
Wavult Group DNS Implementation Script (BUILD-C3)
Förberett: 2026-06-06 | Status: FÖRBERET — EJ KÖRT
SÄKERHETSKLASSER:
✅ SÄKER fas1, fas2, fas3, fas5 (inspect) — inga destruktiva ändringar
⚠️ ERIK-OK fas4b, fas6 — kräver Eriks godkännande + bekräftelseprompt
🔴 DESTRUKTIV Zonradering görs INTE via detta skript — manuellt via AWS-konsolen
ANVÄNDNING:
bash c3-apply-dns.sh <fas> [--dry-run]
FASER:
fas1 ✅ Partnerdomäner: SPF -all + DMARC reject (10 domäner)
fas2 ✅ EU Nivå 2: MX + SPF + DKIM + DMARC p=none (30 domäner)
OBS: Fyll i DKIM_PUBKEY-variabeln före körning!
fas3 ✅ pixdrift.com: SPF -all + DMARC reject
fas4a ✅ Inspektion/verifiering av döda domäner (read-only)
fas4b ⚠️ corpfitt+hypbit MX-borttagning (kräver Erik-OK + prompt)
fas5 ✅ Duplikatzon-inspektion (read-only)
fas6 ⚠️ DMARC-skärpning aktiva maildomäner (kräver Erik-OK + prompt)
FÖRE KÖRNING:
1. Sätt DKIM_PUBKEY i skript-toppen (hämta från Mailu Admin → Domains → DKIM)
2. Bekräfta att AWS-profil är korrekt: aws sts get-caller-identity
3. Fas2 och fas4b: Verifiera att catch-all konfigurerats i Mailu FÖR DNS-ändring
EXEMPEL:
# Kontrollera AWS-åtkomst
aws sts get-caller-identity
# Kör fas1 (partnerdomäner, säker)
bash c3-apply-dns.sh fas1
# Inspektera duplikatzoner (read-only)
bash c3-apply-dns.sh fas5
# Kör fas4a (verifiera döda domäner, read-only)
bash c3-apply-dns.sh fas4a
EOF
}
main() {
local cmd="${1:-help}"
shift || true
case "$cmd" in
fas1) check_deps; fas1_partner_domains ;;
fas2) check_deps; fas2_eu_level2_domains ;;
fas3) check_deps; fas3_pixdrift ;;
fas4a) fas4a_verify_dead_domains_mail ;;
fas4b) check_deps; fas4b_decommission_dead_mx ;;
fas5) fas5_inspect_duplicate_zones ;;
fas6) check_deps; fas6_dmarc_tighten_active_domains ;;
help|--help|-h) usage ;;
*)
echo "Okänt kommando: $cmd"
usage
exit 1
;;
esac
}
main "$@"