sysdiagnose-helper.sh 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. #!/usr/bin/env bash
  2. # mac-ops :: sysdiagnose-helper.sh
  3. # Run Apple's sysdiagnose tool, inspect its output, and prepare a sanitized
  4. # version for sharing.
  5. #
  6. # sysdiagnose captures the WORKS — unified log dumps, system_profiler, kext
  7. # inventory, process listings, network state, IOReg, accessibility config,
  8. # spindump, etc. The output is huge (often 500MB-2GB compressed) and contains
  9. # personal data, hostnames, paths under /Users/, network info. Don't share
  10. # without inspection.
  11. set -u
  12. ACTION="run"
  13. while [[ $# -gt 0 ]]; do
  14. case "$1" in
  15. --inspect) ACTION="inspect"; shift ;;
  16. --inspect=*) ACTION="inspect"; BUNDLE="${1#--inspect=}"; shift ;;
  17. --list) ACTION="list"; shift ;;
  18. --help|-h)
  19. cat <<EOF
  20. Usage: $0 [options]
  21. (no args) Trigger sysdiagnose; report where the bundle is written
  22. --list List existing sysdiagnose bundles on this machine
  23. --inspect[=PATH] Inspect a bundle and report what it contains
  24. --json, --redact, --quiet, --verbose
  25. Apple's sysdiagnose:
  26. Bundle location: /var/tmp/sysdiagnose_*.tar.gz
  27. Trigger via:
  28. sudo sysdiagnose CLI, prompts for trigger
  29. Option-Cmd-Ctrl-Shift-. Keyboard chord (system-wide)
  30. What's in a sysdiagnose bundle (high level):
  31. - Full unified log dump (last few hours / days)
  32. - system_profiler full report
  33. - kextstat / kext info
  34. - Process listing + memory usage
  35. - Network state (ifconfig, netstat, route, scutil)
  36. - pmset state and log
  37. - DiskUtil and APFS state
  38. - DiagnosticReports/ (crashes + panics)
  39. - Spindump (samples of running processes)
  40. - IORegistry dump
  41. - Configuration profiles
  42. Privacy: bundles contain hostnames, usernames, paths under /Users/, IP
  43. addresses, sometimes app-specific identifiers. Inspect before sharing.
  44. EOF
  45. exit 0 ;;
  46. *) shift ;;
  47. esac
  48. done
  49. source "$(dirname "$0")/_lib/common.sh"
  50. parse_common_flags "$@"
  51. maybe_filter_self "$@"
  52. case "$ACTION" in
  53. list)
  54. section "1. EXISTING SYSDIAGNOSE BUNDLES"
  55. bundles=$(ls -lt /var/tmp/sysdiagnose_*.tar.gz 2>/dev/null | head -10)
  56. if [[ -z "$bundles" ]]; then
  57. log_info "Sysdiagnose bundles" "none found in /var/tmp"
  58. else
  59. count=$(echo "$bundles" | wc -l | tr -d ' ')
  60. log_info "Sysdiagnose bundles" "$count found"
  61. echo "$bundles" | sed 's/^/ /'
  62. fi
  63. ;;
  64. inspect)
  65. section "1. BUNDLE INSPECTION"
  66. BUNDLE="${BUNDLE:-$(ls -t /var/tmp/sysdiagnose_*.tar.gz 2>/dev/null | head -1)}"
  67. if [[ -z "$BUNDLE" ]] || [[ ! -f "$BUNDLE" ]]; then
  68. log_fail "Bundle" "not found: $BUNDLE"
  69. exit 3
  70. fi
  71. log_pass "Bundle" "$BUNDLE"
  72. size=$(ls -lh "$BUNDLE" 2>/dev/null | awk '{print $5}')
  73. note " Size: $size"
  74. # Probe contents without extracting
  75. note ""
  76. note " Top-level contents:"
  77. tar tzf "$BUNDLE" 2>/dev/null | awk -F/ '{print $1"/"$2}' | sort -u | head -20 | sed 's/^/ /'
  78. # Sensitive contents inventory
  79. note ""
  80. note " Potentially sensitive contents:"
  81. for pattern in "logarchive" "DiagnosticReports" "system_profiler" "ifconfig" "scutil" "profiles" "TCC"; do
  82. count=$(tar tzf "$BUNDLE" 2>/dev/null | grep -c "$pattern" || echo 0)
  83. count="${count:-0}"
  84. if [[ "$count" -gt 0 ]]; then
  85. printf " %-25s %s files\n" "$pattern" "$count"
  86. fi
  87. done
  88. note ""
  89. note " Before sharing this bundle:"
  90. note " 1. Extract: tar xzf $BUNDLE -C /tmp/inspect"
  91. note " 2. Review: less /tmp/inspect/sysdiagnose_*/system_profiler.spx"
  92. note " 3. Search for sensitive content: grep -r 'private-data-pattern' /tmp/inspect"
  93. note " 4. If sharing publicly: use redaction tools (BBEdit, sed) on extracted log files first"
  94. ;;
  95. run)
  96. section "1. PRECONDITION CHECK"
  97. # sysdiagnose needs sudo
  98. if ! sudo -n true 2>/dev/null; then
  99. log_warn "sudo" "this script needs sudo to invoke sysdiagnose"
  100. note " Run with: sudo bash $0"
  101. note " Or trigger via keyboard chord: Option-Cmd-Ctrl-Shift-."
  102. note " Or: sudo sysdiagnose -f /tmp/ (saves to /tmp instead of /var/tmp)"
  103. emit_summary
  104. exit 5
  105. fi
  106. log_pass "sudo" "available"
  107. # Free space check
  108. free_mb=$(df -m /var/tmp 2>/dev/null | awk 'NR==2{print $4}')
  109. if [[ "${free_mb:-0}" -lt 2048 ]]; then
  110. log_warn "Free space on /var/tmp" "${free_mb} MB — sysdiagnose may need 1-2 GB"
  111. else
  112. log_pass "Free space on /var/tmp" "${free_mb} MB"
  113. fi
  114. section "2. RUNNING SYSDIAGNOSE"
  115. note " This takes 5-15 minutes and produces a large bundle in /var/tmp."
  116. note " Skipping the privacy prompt with -u (no UI) and not generating a profile (-Q):"
  117. note ""
  118. sudo sysdiagnose -u -Q -A "macops-helper" 2>&1 | tail -10 | sed 's/^/ /'
  119. bundle=$(ls -t /var/tmp/sysdiagnose_*.tar.gz 2>/dev/null | head -1)
  120. if [[ -n "$bundle" ]] && [[ -f "$bundle" ]]; then
  121. log_pass "Bundle written" "$bundle"
  122. size=$(ls -lh "$bundle" | awk '{print $5}')
  123. note " Size: $size"
  124. note ""
  125. note " Next:"
  126. note " bash $0 --inspect # see what's in the bundle"
  127. note " bash $0 --list # all bundles on this machine"
  128. note " To share with Apple support, upload directly via Apple Feedback Assistant."
  129. else
  130. log_warn "Bundle" "not found after sysdiagnose ran"
  131. fi
  132. ;;
  133. esac
  134. emit_summary