resolver-clean.sh 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. #!/usr/bin/env bash
  2. # net-ops :: macos/resolver-clean.sh
  3. # Safely remove orphaned /etc/resolver/* files left behind by disconnected VPNs.
  4. # NEVER removes Tailscale or current-VPN-tunnel entries.
  5. #
  6. # Defaults to DRY RUN — pass --apply to actually delete.
  7. # Requires sudo.
  8. set -eu
  9. # Shared terminal toolkit (skills/_lib/term.sh) — colorized, ASCII-aware section
  10. # headers. Dump/fixer output isn't a checklist, so it uses the bare-header style
  11. # (a deliberate exception per docs/TERMINAL-DESIGN.md), not the enclosing panel.
  12. __nlib="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../_lib" 2>/dev/null && pwd || true)"
  13. if [ -n "${__nlib:-}" ] && [ -f "$__nlib/term.sh" ]; then . "$__nlib/term.sh"; term_init
  14. else term_header() { printf '== %s ==\n' "${1:-}"; }; fi
  15. APPLY=0
  16. PROTECT_PATTERNS="${PROTECT_PATTERNS:-100\.100\.100\.100}"
  17. for arg in "$@"; do
  18. case "$arg" in
  19. --apply) APPLY=1 ;;
  20. --protect=*) PROTECT_PATTERNS="${arg#--protect=}" ;;
  21. --help|-h)
  22. cat <<EOF
  23. Usage: $0 [--apply] [--protect=REGEX]
  24. --apply Actually delete (default: dry-run only)
  25. --protect=REGEX Nameserver pattern to protect (default: Tailscale's 100.100.100.100)
  26. Examples:
  27. $0 # show what would be removed
  28. $0 --apply # remove orphan resolvers, protecting Tailscale
  29. $0 --apply --protect='100\\.\\.|192\\.168\\.1\\.' # also protect 192.168.1.x
  30. EOF
  31. exit 0 ;;
  32. esac
  33. done
  34. if [[ ! -d /etc/resolver ]] || [[ -z "$(ls -A /etc/resolver 2>/dev/null)" ]]; then
  35. echo "/etc/resolver/ is empty. Nothing to do."
  36. exit 0
  37. fi
  38. term_header "BEFORE"
  39. for f in /etc/resolver/*; do
  40. [[ -f "$f" ]] || continue
  41. ns=$(awk '/^nameserver/{print $2}' "$f" | tr '\n' ',')
  42. echo " $f -> ${ns%,}"
  43. done
  44. TARGETS=()
  45. for f in /etc/resolver/*; do
  46. [[ -f "$f" ]] || continue
  47. if awk '/^nameserver/{print $2}' "$f" | grep -qE "$PROTECT_PATTERNS"; then
  48. continue
  49. fi
  50. TARGETS+=("$f")
  51. done
  52. if [[ "${#TARGETS[@]}" -eq 0 ]]; then
  53. echo
  54. echo "No orphan resolver files (all match protected nameserver pattern). Nothing to clean."
  55. exit 0
  56. fi
  57. echo
  58. term_header "TARGETS FOR REMOVAL"
  59. for f in "${TARGETS[@]}"; do
  60. echo " $f"
  61. done
  62. if [[ "$APPLY" -eq 0 ]]; then
  63. echo
  64. echo "DRY RUN — pass --apply to actually remove the files above."
  65. exit 0
  66. fi
  67. # Apply
  68. if [[ "$EUID" -ne 0 ]]; then
  69. echo "Need root. Re-running with sudo..."
  70. exec sudo "$0" --apply --protect="$PROTECT_PATTERNS"
  71. fi
  72. echo
  73. term_header "REMOVING"
  74. for f in "${TARGETS[@]}"; do
  75. if rm -f "$f"; then
  76. echo "[OK] $f"
  77. else
  78. echo "[FAIL] $f"
  79. fi
  80. done
  81. echo
  82. term_header "FLUSHING DNS CACHE"
  83. dscacheutil -flushcache
  84. killall -HUP mDNSResponder 2>/dev/null || true
  85. echo " done."
  86. echo
  87. term_header "VERIFICATION"
  88. if out=$(dscacheutil -q host -a name google.com 2>&1) && echo "$out" | grep -q "ip_address:"; then
  89. addr=$(echo "$out" | awk '/ip_address:/{print $2; exit}')
  90. echo "[PASS] dscacheutil google.com -> $addr"
  91. else
  92. echo "[FAIL] dscacheutil still broken. Drill into scutil --dns and configuration profiles."
  93. fi
  94. if curl -sS -o /dev/null -w "[PASS] HTTPS google.com -> HTTP %{http_code}\n" --max-time 8 https://www.google.com 2>&1; then
  95. :
  96. else
  97. echo "[FAIL] HTTPS still broken."
  98. fi
  99. echo
  100. term_header "AFTER"
  101. if [[ -n "$(ls -A /etc/resolver 2>/dev/null)" ]]; then
  102. for f in /etc/resolver/*; do
  103. [[ -f "$f" ]] || continue
  104. ns=$(awk '/^nameserver/{print $2}' "$f" | tr '\n' ',')
  105. echo " $f -> ${ns%,}"
  106. done
  107. else
  108. echo " /etc/resolver/ is now empty."
  109. fi