#!/bin/bash
# h42 dns – Einträge auf ns1.aurisp.com per nsupdate (TSIG) pflegen, kein Zonefile-Edit nötig.
#   h42 dns add  <fqdn> [ziel]       Ziel = IPv4 (A, Default: IP von $PUBLIC_HOST), IPv6 (AAAA) oder Hostname (CNAME)
#   h42 dns del  <fqdn>              alle A/AAAA/CNAME des Namens löschen
#   h42 dns exists <fqdn>            Exit 0: A/AAAA/CNAME vorhanden, 3: nicht vorhanden, sonst: nicht prüfbar
#   h42 dns show <fqdn>              Antwort aller zuständigen Nameserver zeigen
#   h42 dns wait <fqdn> [txt-wert]   warten, bis alle Nameserver den Namen (bzw. diesen TXT-Wert) ausliefern
#   h42 dns txt-add <name> <wert>    TXT-Eintrag hinzufügen (z. B. _acme-challenge.<fqdn> für DNS-01)
#   h42 dns txt-del <name> [wert]    diesen TXT-Wert (ohne wert: alle TXT des Namens) löschen
# add/del/txt-add/txt-del prüfen vorher den Stand auf $NS_SERVER: Was schon so vorhanden bzw. schon weg ist,
# wird nur gemeldet (Exit 0), ein abweichender Eintrag bei add wird ersetzt und das gemeldet.
# ZONE=... überschreibt die Zone (Default: $DEFAULT_ZONE, wenn der Name darauf endet, sonst letzte zwei Labels)
set -euo pipefail
. "${H42_LIB:-/usr/lib/h42}/common.sh"

[ $# -ge 2 ] || usage
cmd=$1; fqdn=${2%.}; arg=${3:-}

if [ -n "${ZONE:-}" ]; then zone=$ZONE
elif [[ $fqdn == *."$DEFAULT_ZONE" ]]; then zone=$DEFAULT_ZONE
else zone=$(echo "$fqdn" | awk -F. '{print $(NF-1)"."$NF}'); fi

nameservers() { dig +short NS "$zone" @"$NS_SERVER" | sed 's/\.$//'; }

nsu() {  # nsupdate-Befehle von stdin an den Primary schicken
  { echo "server $NS_SERVER"; echo "zone $zone"; cat; echo "send"; } | nsupdate -k "$TSIG_KEY"
}

visible() {  # visible <nameserver>: Name (bzw. TXT-Wert $arg) dort sichtbar?
  if [ -n "$arg" ]; then dig +short TXT "$fqdn" @"$1" | grep -qxF "\"$arg\""
  else [ -n "$(dig +short "$fqdn" @"$1")" ]; fi
}

records() {  # records <typ>...: Einträge von $fqdn auf dem Primary als "<typ> <wert>"; bricht ab, wenn er nicht sauber antwortet
  local type out
  for type in "$@"; do
    # dig +short liefert auch bei SERVFAIL/REFUSED leer und Exit 0 – das darf nicht als "nicht vorhanden" gelten
    out=$(dig +noall +comments +answer "$fqdn" "$type" @"$NS_SERVER") || die "keine Antwort von $NS_SERVER zu $fqdn $type"
    grep -qE 'status: (NOERROR|NXDOMAIN)' <<<"$out" || die "$NS_SERVER antwortet nicht sauber zu $fqdn $type"
    awk -v name="$fqdn." -v type="$type" '
      !/^;/ && tolower($1) == tolower(name) && $4 == type {
        sub(/^[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+/, ""); print type, $0
      }' <<<"$out"
  done
}

case $cmd in
  add)
    target=$arg
    [ -n "$target" ] || target=$(dig +short A "$PUBLIC_HOST" @"$NS_SERVER" | tail -1)
    [ -n "$target" ] || die "keine IPv4 für $PUBLIC_HOST von $NS_SERVER – Ziel angeben oder ns1 prüfen"
    if [[ $target =~ ^[0-9.]+$ ]]; then type=A
    elif [[ $target == *:* ]]; then type=AAAA
    else type=CNAME; target=${target%.}.; fi
    existing=$(records A AAAA CNAME)
    if [ "$existing" = "$type $target" ]; then
      echo "$fqdn $type $target ist auf $NS_SERVER schon vorhanden"
      exit 0
    fi
    if [ -n "$existing" ]; then echo "$fqdn ist schon vorhanden und wird ersetzt: $(paste -sd, <<<"$existing")"; fi
    nsu <<NSU
update delete $fqdn. A
update delete $fqdn. AAAA
update delete $fqdn. CNAME
update add $fqdn. $DEFAULT_TTL $type $target
NSU
    echo "$fqdn $type $target -> $NS_SERVER"
    ;;
  del)
    existing=$(records A AAAA CNAME)
    if [ -z "$existing" ]; then
      echo "$fqdn ist auf $NS_SERVER nicht vorhanden"
      exit 0
    fi
    nsu <<NSU
update delete $fqdn. A
update delete $fqdn. AAAA
update delete $fqdn. CNAME
NSU
    echo "$fqdn gelöscht (war: $(paste -sd, <<<"$existing"))"
    ;;
  exists)
    existing=$(records A AAAA CNAME)
    [ -n "$existing" ] || exit 3
    ;;
  show)
    for ns in $(nameservers); do printf '%-20s %s\n' "$ns" "$(dig +short "$fqdn" @"$ns" | tr '\n' ' ')"; done
    ;;
  wait)
    for _ in $(seq 1 60); do
      missing=0
      for ns in $(nameservers); do visible "$ns" || missing=1; done
      [ $missing = 0 ] && { echo "$fqdn${arg:+ (TXT)} auf allen Nameservern sichtbar"; exit 0; }
      sleep 5
    done
    die "Timeout: $fqdn noch nicht auf allen Nameservern ($(nameservers | xargs))"
    ;;
  txt-add)
    [ -n "$arg" ] || usage
    existing=$(records TXT)   # nicht direkt im if: dort würde ein Abbruch von records als "nicht vorhanden" gelten
    if grep -qxF "TXT \"$arg\"" <<<"$existing"; then
      echo "$fqdn TXT \"$arg\" ist auf $NS_SERVER schon vorhanden"
      exit 0
    fi
    echo "update add $fqdn. ${TXT_TTL:-60} TXT \"$arg\"" | nsu
    echo "$fqdn TXT \"$arg\" -> $NS_SERVER"
    ;;
  txt-del)
    existing=$(records TXT)
    if [ -z "$existing" ] || { [ -n "$arg" ] && ! grep -qxF "TXT \"$arg\"" <<<"$existing"; }; then
      echo "$fqdn TXT${arg:+ \"$arg\"} ist auf $NS_SERVER nicht vorhanden"
      exit 0
    fi
    echo "update delete $fqdn. TXT${arg:+ \"$arg\"}" | nsu
    echo "$fqdn TXT${arg:+ \"$arg\"} gelöscht"
    ;;
  *) usage ;;
esac
