#!/usr/bin/env bash
# Copyright (c) 2026 HostFog Ltd. All rights reserved.
# Proprietary and confidential. Use is subject to the HostFog licence (LICENSE).
# Unauthorised copying, modification or distribution is prohibited.
# HostFog: the DNS records the panel's own domain needs (panel., webmail., mx., ftp., ns1./ns2., SPF) plus the glue
# records and reverse DNS — a plain list to add or update at the domain's DNS provider.
#   hf-admin dns-setup [--check]           (after install: reads /etc/hostfog/config.php)
#   hf-dns-setup --host panel.example.com --ip 203.0.113.5 [--mail-host mx.example.com] [--ns1 N --ns2 N]
#                [--no-mail] [--no-dns] [--zone-file FILE] [--plain] [--check]
# --check also looks each record up in public DNS (✔/✘, ADD/UPDATE) and exits 1 while anything is missing.
set -uo pipefail
HOST="" IP="" MAILH="" NS1="" NS2="" MAIL_ON=1 DNS_ON=1 ZONE_FILE="" COLOR=1 CHECK=0 TUNDOM="" PADM="" PCLI="" PRES=""
while [[ $# -gt 0 ]]; do
  case "$1" in
    --host) HOST="$2"; shift 2;;
    --ip) IP="$2"; shift 2;;
    --mail-host) MAILH="$2"; shift 2;;
    --ns1) NS1="$2"; shift 2;;
    --ns2) NS2="$2"; shift 2;;
    --no-mail) MAIL_ON=0; shift;;
    --no-dns) DNS_ON=0; shift;;
    --zone-file) ZONE_FILE="$2"; shift 2;;
    --plain) COLOR=0; shift;;
    --check) CHECK=1; shift;;
    --portals) read -r PADM PCLI PRES <<<"$2"; shift 2;;   # "server.x panel.x reseller.x" (separate panels)
    *) echo "Unknown option $1" >&2; exit 2;;
  esac
done
cfg() { php -r '$c = @include "/etc/hostfog/config.php"; $v = is_array($c) ? ($c[$argv[1]] ?? "") : ""; echo is_bool($v) ? ($v ? "1" : "0") : (is_array($v) ? implode(" ", $v) : $v);' "$1" 2>/dev/null; }
if [[ -z "$HOST" && -r /etc/hostfog/config.php ]] && command -v php >/dev/null; then
  HOST="$(cfg panel_host)"; IP="$(cfg server_ip)"
  MAILH="$(cfg mail_name)"; [[ -n "$MAILH" ]] || MAILH="$(cfg mail_host)"
  read -r NS1 NS2 <<<"$(cfg nameservers)"
  [[ "$(cfg mail_enabled)" == 0 ]] && MAIL_ON=0
  [[ "$(cfg dns_enabled)" == 0 ]] && DNS_ON=0
  TUNDOM="$(cfg tunnel_domain)"
  PADM="$(cfg portal_admin)"; PCLI="$(cfg portal_client)"; PRES="$(cfg portal_reseller)"
  [[ -n "$ZONE_FILE" ]] || ZONE_FILE=/etc/hostfog/dns-setup.zone
fi
[[ "$HOST" =~ ^([a-z0-9-]+\.){2,}[a-z]{2,}$ ]] || { echo "Give --host panel.example.com (or run this after HostFog is installed)." >&2; exit 2; }
[[ -n "$IP" ]] || IP="$(curl -4 -fsS --max-time 8 https://api.ipify.org 2>/dev/null || hostname -I | awk '{print $1}')"
BASE="${HOST#*.}"
MAILH="${MAILH:-mx.$BASE}"; NS1="${NS1:-ns1.$BASE}"; NS2="${NS2:-ns2.$BASE}"
if [[ $COLOR -eq 1 && -t 1 ]]; then G=$'\e[32m' R=$'\e[31m' Y=$'\e[33m' D=$'\e[2m' B=$'\e[1m' N=$'\e[0m'; else G="" R="" Y="" D="" B="" N=""; fi
# separate panels: every panel name points here too; the panel name itself is the customers' Control Panel
HOSTNOTE="control panel"; [[ -n "$PADM" ]] && HOSTNOTE="control panel (classic address, port 8443)"
[[ -n "$PCLI" && "$PCLI" == "$HOST" ]] && HOSTNOTE="Control Panel for customers (also the classic address)"
PANELS=()
[[ -n "$PADM" ]] && PANELS+=("$PADM|Server Admin (administrators)")
[[ -n "$PCLI" && "$PCLI" != "$HOST" ]] && PANELS+=("$PCLI|Control Panel (customers)")
[[ -n "$PRES" ]] && PANELS+=("$PRES|Reseller Panel (resellers)")
rel() { [[ "$1" == *".$BASE" ]] && echo "${1%."$BASE"}" || echo "$1."; }   # zone-file name relative to the domain

# ---------------------------------------------------------------- the list (default: nothing is looked up)
if [[ $CHECK -eq 0 ]]; then
  recs=("A|$HOST|$IP|$HOSTNOTE")
  for e in "${PANELS[@]}"; do recs+=("A|${e%%|*}|$IP|${e#*|}"); done
  [[ $MAIL_ON -eq 1 ]] && recs+=("A|webmail.$BASE|$IP|webmail" "A|$MAILH|$IP|mail server: MX, IMAP and SMTP")
  recs+=("A|ftp.$BASE|$IP|FTP")
  glue=()
  if [[ $DNS_ON -eq 1 ]]; then
    for n in "$NS1" "$NS2"; do [[ "$n" == *".$BASE" ]] && { recs+=("A|$n|$IP|nameserver"); glue+=("$n"); }; done
  fi
  [[ $MAIL_ON -eq 1 ]] && recs+=("TXT|$MAILH|\"v=spf1 a -all\"|lets other mail servers trust mail from this server")
  [[ -n "$TUNDOM" ]] && recs+=("A|*.$TUNDOM|$IP|HostFog Tunnel links (one record for every tunnel)")
  w1=4; w2=4; w3=5
  for r in "${recs[@]}"; do IFS='|' read -r t n v _ <<<"$r"; (( ${#t} > w1 )) && w1=${#t}; (( ${#n} > w2 )) && w2=${#n}; (( ${#v} > w3 )) && w3=${#v}; done
  echo
  printf "   ${B}DNS for %s${N}\n" "$BASE"
  printf "   Add these records in ${B}%s${N}'s DNS — or update them if they already exist with another value:\n\n" "$BASE"
  printf "     ${D}%-${w1}s  %-${w2}s  %-${w3}s  %s${N}\n" "Type" "Name" "Value" "For"
  for r in "${recs[@]}"; do
    IFS='|' read -r t n v note <<<"$r"
    printf "     %-${w1}s  %-${w2}s  %-${w3}s  ${D}%s${N}\n" "$t" "$n" "$v" "$note"
  done
  echo
  n=1
  if [[ ${#glue[@]} -gt 0 ]]; then
    printf "   ${B}%d.${N} At your domain registrar: register ${B}%s${N} → %s as ${B}glue records${N} (child nameservers).\n" "$n" "${glue[*]}" "$IP"; n=$((n+1))
  fi
  if [[ $MAIL_ON -eq 1 ]]; then
    printf "   ${B}%d.${N} At your server provider: set the ${B}reverse DNS (PTR)${N} of %s to ${B}%s${N}.\n" "$n" "$IP" "$MAILH"; n=$((n+1))
  fi
  if [[ $DNS_ON -eq 1 ]]; then
    printf "   ${D}Customers' domains use the nameservers %s and %s.${N}\n" "$NS1" "$NS2"
  fi
  if [[ -n "$ZONE_FILE" ]] && mkdir -p "$(dirname "$ZONE_FILE")" 2>/dev/null; then
    { echo "; HostFog — DNS records for $BASE (import this at your DNS provider, or add them by hand)"
      echo "\$ORIGIN $BASE."
      for r in "${recs[@]}"; do IFS='|' read -r t n v _ <<<"$r"; echo "$(rel "$n") 3600 IN $t $v"; done
    } > "$ZONE_FILE" 2>/dev/null && printf "   ${D}Zone file to import: %s${N}\n" "$ZONE_FILE"
  fi
  echo
  exit 0
fi

# public DNS, never this server's own /etc/hosts or BIND (they would always say "yes")
q() { # type name -> answers, one per line
  local t="$1" n="$2" s out
  if command -v dig >/dev/null; then   # dig exits 0 on a real answer (even an empty one), 9 when the resolver can't be reached
    for s in 1.1.1.1 8.8.8.8; do out="$(dig +short +time=3 +tries=1 "$t" "$n" "@$s" 2>/dev/null)" && { [[ -n "$out" ]] && printf '%s\n' "$out" | sed 's/\.$//'; return 0; }; done
  fi
  # no dig yet (first moments of the install) or port 53 blocked: Google's public DNS over HTTPS
  curl -fsS --max-time 6 "https://dns.google/resolve?name=$n&type=$t" 2>/dev/null | grep -o '"data": *"\([^"\\]\|\\.\)*"' \
    | sed -E 's/^"data": *"//; s/"$//; s/\\"//g; s/\.$//'
  return 0
}
ALL_OK=1
# can public DNS be reached at all? If not, say "couldn't check" instead of marking everything as missing
CAN_CHECK=1; [[ -n "$(q A one.one.one.one)" ]] || CAN_CHECK=0
rows=(); zone=()
NADD=0; NUPD=0
add() { # ok type name value note [what public DNS has now]
  local mark act
  if [[ $CAN_CHECK -eq 0 ]]; then mark="${Y}?${N}"; act="${Y}check later${N}"; ALL_OK=0
  elif [[ "$1" == 1 ]]; then mark="${G}✔${N}"; act="${G}ok${N}"
  elif [[ -n "${6:-}" ]]; then mark="${R}✘${N}"; act="${Y}UPDATE${N} ${D}(now: ${6})${N}"; ALL_OK=0; NUPD=$((NUPD+1))
  else mark="${R}✘${N}"; act="${R}ADD${N}"; ALL_OK=0; NADD=$((NADD+1)); fi
  rows+=("$mark|$2|$3|$4|$act|$5")
}

# 1. names that must point at the server
names=("$HOST|$HOSTNOTE")
for e in "${PANELS[@]}"; do names+=("$e"); done
[[ $MAIL_ON -eq 1 ]] && names+=("webmail.$BASE|webmail" "$MAILH|mail server: MX, IMAP and SMTP")
names+=("ftp.$BASE|FTP")
if [[ $DNS_ON -eq 1 ]]; then
  for n in "$NS1" "$NS2"; do [[ "$n" == *".$BASE" ]] && names+=("$n|nameserver — also add it as a glue record (child nameserver) at your registrar"); done
fi
for e in "${names[@]}"; do
  n="${e%%|*}"; note="${e#*|}"
  cur="$(q A "$n" | grep -E '^[0-9.]+$' | sort -u | tr '\n' ' ')"; cur="${cur% }"
  if grep -qwF "$IP" <<<"$cur"; then ok=1; else ok=0; fi
  add "$ok" A "$n" "$IP" "$note" "$cur"
  zone+=("$(rel "$n") 3600 IN A $IP")
done
# 2. mail: other servers check the server's name with SPF and its address with reverse DNS
if [[ $MAIL_ON -eq 1 ]]; then
  if q TXT "$MAILH" | grep -q 'v=spf1'; then ok=1; else ok=0; fi
  add "$ok" TXT "$MAILH" '"v=spf1 a -all"' "lets other mail servers trust mail sent by this server's name"
  zone+=("$(rel "$MAILH") 3600 IN TXT \"v=spf1 a -all\"")
  cur="$(q PTR "$(awk -F. '{print $4"."$3"."$2"."$1".in-addr.arpa"}' <<<"$IP")" | head -n 1)"
  if [[ "$cur" == "$MAILH" ]]; then ok=1; else ok=0; fi
  add "$ok" PTR "$IP" "$MAILH" "reverse DNS — set this at your server provider (VPS control panel), not in DNS" "$cur"
fi

# who runs the domain's DNS now
ns_now="$(q NS "$BASE" | sort | tr '\n' ' ')"; ns_now="${ns_now% }"
case "$ns_now" in
  *domaincontrol.com*) prov="GoDaddy";; *cloudflare.com*) prov="Cloudflare";; *dns-parking.com*|*hostinger*) prov="Hostinger";;
  *registrar-servers.com*) prov="Namecheap";; *awsdns*) prov="Amazon Route 53";; *googledomains.com*|*google.com*) prov="Google";;
  *ovh.net*|*ovh.ca*) prov="OVH";; *digitalocean.com*) prov="DigitalOcean";; *hetzner.com*|*hetzner.de*) prov="Hetzner";; *ionos*|*ui-dns*) prov="IONOS";;
  *) prov="";;
esac

echo
printf "   ${B}DNS setup for %s${N}\n" "$BASE"
if [[ -n "$ns_now" ]]; then
  printf "   ${D}%s's DNS is run by%s: %s — add the records there.${N}\n" "$BASE" "${prov:+ $prov}" "$ns_now"
else
  printf "   ${D}Add these records at the company that runs %s's DNS.${N}\n" "$BASE"
fi
echo
w2=4; w3=4; w4=5
for r in "${rows[@]}"; do IFS='|' read -r _ t n v _ _ <<<"$r"; (( ${#t} > w2 )) && w2=${#t}; (( ${#n} > w3 )) && w3=${#n}; (( ${#v} > w4 )) && w4=${#v}; done
printf "     ${D}%-${w2}s  %-${w3}s  %-${w4}s  %s${N}\n" "Type" "Name" "Value" "To do"
for r in "${rows[@]}"; do
  IFS='|' read -r m t n v act note <<<"$r"
  printf "   %s %-${w2}s  %-${w3}s  %-${w4}s  %s\n" "$m" "$t" "$n" "$v" "$act"
  printf "     %-${w2}s  ${D}↳ %s${N}\n" "" "$note"
done
echo
if [[ $CAN_CHECK -eq 0 ]]; then
  printf "   ${Y}? = couldn't check:${N} ${D}this server can't reach public DNS right now. Add the records, then check again: hf-admin dns-setup${N}\n"
elif [[ $ALL_OK -eq 1 ]]; then
  printf "   ${G}All set.${N}\n"
else
  printf "   ${B}To do:${N} %s\n" "$( [[ $NADD -gt 0 ]] && printf 'add %d record(s)' "$NADD")$( [[ $NADD -gt 0 && $NUPD -gt 0 ]] && printf ', ')$( [[ $NUPD -gt 0 ]] && printf 'update %d record(s) that point somewhere else' "$NUPD")"
  printf "   ${Y}✘ = not visible in public DNS yet.${N} ${D}New records usually show up within 10–30 minutes; the panel's SSL\n   certificate picks the names up by itself within 5 minutes of them pointing here. Check again: hf-admin dns-setup${N}\n"
fi
if [[ $DNS_ON -eq 1 ]]; then
  printf "   ${D}Customers' domains: point them at %s and %s (or add their records wherever their DNS is).${N}\n" "$NS1" "$NS2"
fi
if [[ -n "$ZONE_FILE" ]] && mkdir -p "$(dirname "$ZONE_FILE")" 2>/dev/null; then
  { echo "; HostFog — DNS records for $BASE (import this at your DNS provider, or add them by hand)"
    echo "\$ORIGIN $BASE."
    printf '%s\n' "${zone[@]}"
  } > "$ZONE_FILE" 2>/dev/null && printf "   ${D}Zone file to import at your DNS provider: %s${N}\n" "$ZONE_FILE"
fi
echo
[[ $ALL_OK -eq 1 ]]
