#!/usr/bin/env bash
#
# SignalDig MCP DNS 诊断与修复（Linux）
#
# 用法：
#   sudo ./scripts/server-check-dns.sh --diagnose
#   sudo ./scripts/server-check-dns.sh --fix
#   sudo ./scripts/server-check-dns.sh --restore
#
# --diagnose 只读检查当前网络；--fix 把当前连接改为指定公共 DNS；
# --restore 清除静态 DNS，恢复 NetworkManager/systemd-resolved 的 DHCP DNS。
# 脚本不会写入 API Key，也不会调用业务 API。
#
# Linux 支持路径（按优先级）：NetworkManager、systemd-resolved、resolvconf、
# 最后的普通 /etc/resolv.conf。macOS 使用 networksetup，并恢复为 DHCP DNS。
#
set -Eeuo pipefail

DOMAIN="mcp.signaldig.com"
IPV4_PRIMARY="119.29.29.29"
IPV4_SECONDARY="223.5.5.5"
IPV6_PRIMARY="2402:4e00::"
IPV6_SECONDARY="2400:3200::1"
MODE="diagnose"
LANGUAGE="auto"
SCRIPT_LANGUAGE="zh"
STATE_DIR="/var/lib/signaldig-server-check"
RESOLV_BACKUP="${STATE_DIR}/resolv.conf.before-fix"
NM_STATE="${STATE_DIR}/networkmanager.env"

usage() {
  detect_language
  if [[ "${SCRIPT_LANGUAGE:-}" == "en" ]]; then
    cat <<'EOF'
SignalDig MCP DNS diagnosis and repair (Linux / macOS)

Usage:
  sudo ./server-check-dns.sh --diagnose    Check only; does not change settings (default)
  sudo ./server-check-dns.sh --fix         Set public IPv4/IPv6 DNS and verify
  sudo ./server-check-dns.sh --restore     Restore DHCP/router-provided DNS

Optional: --language zh|en, --domain DOMAIN, --ipv4-primary ADDRESS,
--ipv4-secondary ADDRESS, --ipv6-primary ADDRESS, --ipv6-secondary ADDRESS
EOF
    return
  fi
  cat <<'EOF'
SignalDig MCP DNS 诊断与修复（Linux / macOS）

用法：
  sudo ./server-check-dns.sh --diagnose    只诊断，不改配置（默认）
  sudo ./server-check-dns.sh --fix         设置公共 IPv4/IPv6 DNS 并验证
  sudo ./server-check-dns.sh --restore     恢复 DHCP/路由器下发的 DNS

可选参数：
  --domain DOMAIN
  --ipv4-primary ADDRESS
  --ipv4-secondary ADDRESS
  --ipv6-primary ADDRESS
  --ipv6-secondary ADDRESS
  --language zh|en
  --help
EOF
}

detect_language() {
  if [[ "$LANGUAGE" == "zh" || "$LANGUAGE" == "en" ]]; then SCRIPT_LANGUAGE="$LANGUAGE"; return; fi
  [[ "${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}" == zh* ]] && SCRIPT_LANGUAGE="zh" || SCRIPT_LANGUAGE="en"
}

translate() {
  [[ "$SCRIPT_LANGUAGE" == "zh" ]] && { printf '%s' "$1"; return; }
  case "$1" in
    "1/4 当前系统解析") printf '%s' "1/4 Local system resolution" ;;
    "2/4 公共 DNS 对照") printf '%s' "2/4 Public DNS comparison" ;;
    "3/4 当前网卡与 DNS 配置") printf '%s' "3/4 Active network adapter DNS" ;;
    "4/4 SignalDig HTTPS 443") printf '%s' "4/4 SignalDig HTTPS port 443" ;;
    "当前解析未发现回环地址") printf '%s' "No loopback address found in local resolution" ;;
    "当前解析失败，或系统解析工具不可用") printf '%s' "Local resolution failed or no system resolver tool is available" ;;
    "至少一个公共 DNS 返回了结果") printf '%s' "At least one public DNS resolver returned an answer" ;;
    "公共 DNS 查询没有返回结果") printf '%s' "Public DNS lookups returned no answer" ;;
    "无法识别默认路由网卡") printf '%s' "Could not identify the default-route network adapter" ;;
    "设置 macOS 公共 DNS") printf '%s' "Set public DNS on macOS" ;;
    "设置公共 DNS") printf '%s' "Set public DNS" ;;
    "修复后验证") printf '%s' "Verification after repair" ;;
    "恢复 macOS 默认 DNS") printf '%s' "Restore macOS default DNS" ;;
    "恢复默认 DNS") printf '%s' "Restore default DNS" ;;
    "恢复后验证") printf '%s' "Verification after restore" ;;
    "NetworkManager 已恢复 DHCP/路由器 DNS") printf '%s' "NetworkManager restored DHCP/router-provided DNS" ;;
    "systemd-resolved 已恢复默认 DNS") printf '%s' "systemd-resolved restored default DNS" ;;
    "resolvconf 已恢复默认 DNS") printf '%s' "resolvconf restored default DNS" ;;
    "已还原修改前的 /etc/resolv.conf") printf '%s' "Restored /etc/resolv.conf from the pre-repair backup" ;;
    *) printf '%s' "$1" ;;
  esac
}

log() { printf '\n===== %s =====\n' "$(translate "$1")"; }
ok() { printf '  [OK] %s\n' "$(translate "$1")"; }
bad() { printf '  [!!] %s\n' "$(translate "$1")" >&2; }
note() { printf '  [..] %s\n' "$(translate "$1")"; }

require_supported_os() {
  case "$(uname -s)" in
    Linux|Darwin) ;;
    *) bad "此脚本支持 Linux 和 macOS，当前系统为 $(uname -s)"; exit 2 ;;
  esac
}

require_root() {
  [[ "${EUID}" -eq 0 ]] || {
    bad "--fix 和 --restore 需要 root 权限，请使用 sudo 运行"
    exit 2
  }
}

has() { command -v "$1" >/dev/null 2>&1; }

active_interface() {
  if [[ "$(uname -s)" == "Darwin" ]]; then
    route -n get default 2>/dev/null | awk '/interface:/{ print $2; exit }'
  elif has ip; then
    ip -4 route show default 2>/dev/null | awk 'NR == 1 { print $5; exit }'
  fi
}

mac_network_service() {
  local iface="${1:-}"
  local service
  [[ "$(uname -s)" == "Darwin" ]] || return 1
  if [[ -n "$iface" ]]; then
    service="$(networksetup -listnetworkserviceorder 2>/dev/null | awk -v device="Device: ${iface}" '
      /^\([0-9]+\)/ { service=$0; sub(/^\([0-9]+\) /, "", service) }
      index($0, device) { print service; exit }
    ')"
    if [[ -n "$service" ]]; then
      printf '%s\n' "$service"
      return 0
    fi
  fi

  # Clash、VPN 等会把默认路由放到 utun*；它们不是可配置 DNS 的
  # networksetup 服务。此时选择已有 IPv4 地址的实际网络服务（通常是 Wi-Fi）。
  while IFS= read -r service; do
    [[ -n "$service" && "$service" != \** ]] || continue
    if networksetup -getinfo "$service" 2>/dev/null | grep -Eq '^IP address: [0-9]'; then
      printf '%s\n' "$service"
      return 0
    fi
  done < <(networksetup -listallnetworkservices 2>/dev/null | sed '1d')
  return 1
}

active_nm_connection() {
  local iface="${1:-}"
  if has nmcli && [[ -n "$iface" ]]; then
    nmcli -t -f NAME,DEVICE connection show --active 2>/dev/null |
      awk -F: -v device="$iface" '$2 == device { print substr($0, 1, length($0)-length($2)-1); exit }'
  fi
}

local_answers() {
  if [[ "$(uname -s)" == "Darwin" ]] && has dscacheutil; then
    dscacheutil -q host -a name "$DOMAIN" 2>/dev/null |
      awk -F': ' '$1 == "ip_address" { print $2 }' | sort -u |
      awk 'BEGIN { sep = "" } { printf "%s%s", sep, $0; sep = ", " } END { if (NR) print "" }'
  elif has getent; then
    getent ahosts "$DOMAIN" 2>/dev/null | awk '{ print $1 }' | sort -u |
      awk 'BEGIN { sep = "" } { printf "%s%s", sep, $0; sep = ", " } END { if (NR) print "" }'
  else
    printf '<system resolver lookup unavailable>'
  fi
}

public_answers() {
  local server="$1"
  if has dig; then
    {
      dig +time=3 +tries=1 +short A "$DOMAIN" "@$server" 2>/dev/null || true
      dig +time=3 +tries=1 +short AAAA "$DOMAIN" "@$server" 2>/dev/null || true
    } | awk '/^[0-9A-Fa-f:.]+$/ && /[0-9]/ { print }' | sort -u |
      awk 'BEGIN { sep = "" } { printf "%s%s", sep, $0; sep = ", " } END { if (NR) print "" }'
  else
    printf '<dig unavailable>'
  fi
}

configured_dns() {
  local iface="${1:-}"
  if [[ "$(uname -s)" == "Darwin" ]]; then
    local service="$(mac_network_service "$iface")"
    if [[ -n "$service" ]]; then
      networksetup -getdnsservers "$service" 2>/dev/null | sed 's/^/    /' || true
    else
      note "无法找到接口 ${iface} 对应的 macOS 网络服务"
    fi
  elif has resolvectl; then
    resolvectl dns "$iface" 2>/dev/null | sed 's/^/    /' || true
  elif has nmcli; then
    nmcli device show "$iface" 2>/dev/null | awk -F: '/IP4.DNS|IP6.DNS/ { gsub(/^ +| +$/, "", $2); print "    " $1 ": " $2 }' || true
  elif [[ -r /etc/resolv.conf ]]; then
    awk '/^nameserver[[:space:]]/ { print "    " $0 }' /etc/resolv.conf
  fi
}

has_loopback_answer() {
  local answers="$1"
  grep -Eq '(^|[,[:space:]])(127\.0\.0\.1|::1)([,[:space:]]|$)' <<<"$answers"
}

tcp_check() {
  if has curl; then
    curl -ksS -o /dev/null --connect-timeout 6 --max-time 10 "https://${DOMAIN}/" 2>/dev/null
  elif has nc; then
    nc -z -w 6 "$DOMAIN" 443 >/dev/null 2>&1
  else
    return 2
  fi
}

flush_dns_cache() {
  if [[ "$(uname -s)" == "Darwin" ]]; then
    dscacheutil -flushcache 2>/dev/null || true
    killall -HUP mDNSResponder 2>/dev/null || true
  elif has resolvectl; then
    resolvectl flush-caches 2>/dev/null || true
  fi
}

diagnose() {
  local iface="$1"
  local answers
  local cloudflare
  local google

  log "1/4 当前系统解析"
  answers="$(local_answers)"
  printf '  域名：%s\n' "$DOMAIN"
  printf '  结果：%s\n' "${answers:-<无结果>}"
  if has_loopback_answer "$answers"; then
    bad "当前解析包含 127.0.0.1 或 ::1，疑似 DNS 污染"
  elif [[ -n "$answers" && "$answers" != '<system resolver lookup unavailable>' ]]; then
    ok "当前解析未发现回环地址"
  else
    bad "当前解析失败，或系统解析工具不可用"
  fi

  log "2/4 公共 DNS 对照"
  if has dig; then
    cloudflare="$(public_answers "$IPV4_PRIMARY")"
    google="$(public_answers "$IPV4_SECONDARY")"
    printf '  %-18s %s\n' "$IPV4_PRIMARY" "${cloudflare:-<无结果>}"
    printf '  %-18s %s\n' "$IPV4_SECONDARY" "${google:-<无结果>}"
    if [[ -n "$cloudflare$google" ]]; then
      ok "至少一个公共 DNS 返回了结果"
    else
      bad "公共 DNS 查询没有返回结果"
    fi
  else
    note "未安装 dig，跳过指定 DNS 服务器对照（可安装 dnsutils 或 bind-utils）"
  fi

  log "3/4 当前网卡与 DNS 配置"
  printf '  默认路由网卡：%s\n' "${iface:-<未找到>}"
  if [[ -n "$iface" ]]; then configured_dns "$iface"; else note "无法识别默认路由网卡"; fi

  log "4/4 SignalDig HTTPS 443"
  if tcp_check; then
    ok "${DOMAIN}:443 可以连接"
  else
    case "$?" in
      2) note "缺少 curl 或 nc，跳过 TCP 检查" ;;
      *) bad "${DOMAIN}:443 无法连接" ;;
    esac
  fi
}

backup_resolv_conf() {
  mkdir -p "$STATE_DIR"
  if [[ ! -e "$RESOLV_BACKUP" ]]; then
    cp -L /etc/resolv.conf "$RESOLV_BACKUP" 2>/dev/null || true
  fi
}

set_with_nm() {
  local connection="$1"
  [[ -n "$connection" ]] || return 1
  mkdir -p "$STATE_DIR"
  if [[ ! -e "$NM_STATE" ]]; then
    {
      printf 'connection=%q\n' "$connection"
      printf 'ipv4_dns=%q\n' "$(nmcli -g ipv4.dns connection show "$connection" 2>/dev/null || true)"
      printf 'ipv4_ignore=%q\n' "$(nmcli -g ipv4.ignore-auto-dns connection show "$connection" 2>/dev/null || true)"
      printf 'ipv6_dns=%q\n' "$(nmcli -g ipv6.dns connection show "$connection" 2>/dev/null || true)"
      printf 'ipv6_ignore=%q\n' "$(nmcli -g ipv6.ignore-auto-dns connection show "$connection" 2>/dev/null || true)"
    } >"$NM_STATE"
  fi
  nmcli connection modify "$connection" \
    ipv4.ignore-auto-dns yes ipv4.dns "$IPV4_PRIMARY $IPV4_SECONDARY" \
    ipv6.ignore-auto-dns yes ipv6.dns "$IPV6_PRIMARY,$IPV6_SECONDARY"
  nmcli connection up "$connection" >/dev/null
}

set_with_resolved() {
  local iface="$1"
  [[ -n "$iface" && -x "$(command -v resolvectl)" ]] || return 1
  resolvectl dns "$iface" "$IPV4_PRIMARY" "$IPV4_SECONDARY" "$IPV6_PRIMARY" "$IPV6_SECONDARY"
  flush_dns_cache
}

set_with_macos() {
  local iface="$1"
  local service="$(mac_network_service "$iface")"
  [[ -n "$service" ]] || return 1
  networksetup -setdnsservers "$service" "$IPV4_PRIMARY" "$IPV4_SECONDARY" "$IPV6_PRIMARY" "$IPV6_SECONDARY"
  flush_dns_cache
}

set_with_resolvconf() {
  local iface="$1"
  [[ -n "$iface" && -x "$(command -v resolvconf)" ]] || return 1
  {
    printf 'nameserver %s\n' "$IPV4_PRIMARY"
    printf 'nameserver %s\n' "$IPV4_SECONDARY"
    printf 'nameserver %s\n' "$IPV6_PRIMARY"
    printf 'nameserver %s\n' "$IPV6_SECONDARY"
  } | resolvconf -a "$iface"
}

set_with_resolv_conf() {
  backup_resolv_conf
  cat > /etc/resolv.conf <<EOF
# Managed by SignalDig server-check-dns.sh; run --restore to recover the previous file.
nameserver ${IPV4_PRIMARY}
nameserver ${IPV4_SECONDARY}
nameserver ${IPV6_PRIMARY}
nameserver ${IPV6_SECONDARY}
EOF
}

fix_dns() {
  local iface="$1"
  if [[ "$(uname -s)" == "Darwin" ]]; then
    log "设置 macOS 公共 DNS"
    if set_with_macos "$iface"; then
      ok "已通过 networksetup 设置接口：$iface"
      log "修复后验证"
      sleep 1
      diagnose "$iface"
    else
      bad "未找到接口对应的 macOS 网络服务；未改动系统配置"
      return 1
    fi
    return
  fi
  local connection="$(active_nm_connection "$iface")"
  log "设置公共 DNS"
  if has nmcli && [[ -n "$connection" ]] && set_with_nm "$connection"; then
    ok "已通过 NetworkManager 设置：$connection"
  elif has resolvectl && set_with_resolved "$iface"; then
    ok "已通过 systemd-resolved 设置：$iface"
  elif has resolvconf && set_with_resolvconf "$iface"; then
    ok "已通过 resolvconf 设置：$iface"
  elif [[ -w /etc/resolv.conf || ! -e /etc/resolv.conf ]]; then
    set_with_resolv_conf
    ok "已更新 /etc/resolv.conf"
  else
    bad "未找到可安全修改的 DNS 管理器；未改动系统配置"
    return 1
  fi
  log "修复后验证"
  sleep 1
  diagnose "$iface"
}

restore_dns() {
  local iface="$1"
  if [[ "$(uname -s)" == "Darwin" ]]; then
    local service="$(mac_network_service "$iface")"
    log "恢复 macOS 默认 DNS"
    if [[ -n "$service" ]]; then
      networksetup -setdnsservers "$service" empty
      flush_dns_cache
      ok "已恢复 DHCP/路由器 DNS：$service"
      log "恢复后验证"
      sleep 1
      diagnose "$iface"
    else
      bad "未找到接口对应的 macOS 网络服务；未改动系统配置"
      return 1
    fi
    return
  fi
  local connection="$(active_nm_connection "$iface")"
  log "恢复默认 DNS"
  if has nmcli && [[ -n "$connection" ]] && [[ -f "$NM_STATE" ]]; then
    nmcli connection modify "$connection" ipv4.ignore-auto-dns no ipv4.dns '' ipv6.ignore-auto-dns no ipv6.dns ''
    nmcli connection up "$connection" >/dev/null
    rm -f "$NM_STATE"
    ok "NetworkManager 已恢复 DHCP/路由器 DNS"
  elif has resolvectl && [[ -n "$iface" ]]; then
    resolvectl revert "$iface"
    flush_dns_cache
    ok "systemd-resolved 已恢复默认 DNS"
  elif has resolvconf && [[ -n "$iface" ]]; then
    resolvconf -d "$iface"
    ok "resolvconf 已恢复默认 DNS"
  elif [[ -f "$RESOLV_BACKUP" ]]; then
    cp "$RESOLV_BACKUP" /etc/resolv.conf
    rm -f "$RESOLV_BACKUP"
    ok "已还原修改前的 /etc/resolv.conf"
  else
    bad "没有找到可恢复的备份；未改动系统配置"
    return 1
  fi
  log "恢复后验证"
  sleep 1
  diagnose "$iface"
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --diagnose) MODE="diagnose" ;;
    --fix) MODE="fix" ;;
    --restore) MODE="restore" ;;
    --language) LANGUAGE="${2:?--language requires zh or en}"; shift ;;
    --domain) DOMAIN="${2:?--domain requires a value}"; shift ;;
    --ipv4-primary) IPV4_PRIMARY="${2:?--ipv4-primary requires a value}"; shift ;;
    --ipv4-secondary) IPV4_SECONDARY="${2:?--ipv4-secondary requires a value}"; shift ;;
    --ipv6-primary) IPV6_PRIMARY="${2:?--ipv6-primary requires a value}"; shift ;;
    --ipv6-secondary) IPV6_SECONDARY="${2:?--ipv6-secondary requires a value}"; shift ;;
    --help|-h) usage; exit 0 ;;
    *) bad "未知参数：$1"; usage; exit 2 ;;
  esac
  shift
done

require_supported_os
detect_language
INTERFACE="$(active_interface || true)"

case "$MODE" in
  diagnose) diagnose "$INTERFACE" ;;
  fix) require_root; fix_dns "$INTERFACE" ;;
  restore) require_root; restore_dns "$INTERFACE" ;;
esac
