quickrun.sh 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. #!/usr/bin/env bash
  2. # mac-ops :: quickrun.sh
  3. # One-shot "what's wrong with my Mac?" runner. Sequences the 5 highest-yield
  4. # audits and emits a single consolidated report.
  5. #
  6. # This is the script to run if you have 60 seconds and want to know whether
  7. # something needs attention. For deep dives, drill into individual scripts.
  8. set -u
  9. SHORT_DAYS=7
  10. while [[ $# -gt 0 ]]; do
  11. case "$1" in
  12. --days) SHORT_DAYS="$2"; shift 2 ;;
  13. --help|-h)
  14. cat <<EOF
  15. Usage: $0 [options]
  16. --days N Lookback for log-based audits (default: 7)
  17. --json Emit consolidated NDJSON
  18. --redact Mask private addrs / hostnames / serials
  19. Runs (in order):
  20. 1. health-audit — orchestrator across 8 rungs
  21. 2. startup-audit — login items + launchd inventory
  22. 3. storage-pressure — APFS snapshots + caches breakdown
  23. 4. wake-reasons — pmset log analysis (last week)
  24. 5. tcc-audit (denied) — what privacy permissions were denied
  25. Output: one consolidated SUMMARY at end with all PASS/FAIL counts and the
  26. top 3 issues to address.
  27. Time: 60-90 seconds typical (log show queries dominate).
  28. EOF
  29. exit 0 ;;
  30. *) shift ;;
  31. esac
  32. done
  33. source "$(dirname "$0")/_lib/common.sh"
  34. parse_common_flags "$@"
  35. maybe_filter_self "$@"
  36. source "$(dirname "$0")/_lib/panel.sh"
  37. panel_init
  38. SCRIPTS_DIR="$(dirname "$0")"
  39. # Aggregate counters across all sub-scripts
  40. TOTAL_PASS=0
  41. TOTAL_FAIL=0
  42. TOTAL_WARN=0
  43. TOTAL_INFO=0
  44. ALL_FAILS=()
  45. ALL_WARNS=()
  46. run_subscript() {
  47. local label="$1"
  48. local script="$2"
  49. shift 2
  50. note ""
  51. note "════════════════════════════════════════════════════════════════"
  52. note " $label"
  53. note "════════════════════════════════════════════════════════════════"
  54. # Capture sub-script output (suppress its own SUMMARY since we'll do our own).
  55. # Force NO_PANEL so children emit parseable plain text — we render our own panel.
  56. local out
  57. out=$(NO_PANEL=1 bash "$SCRIPTS_DIR/$script" --quiet "$@" 2>&1 || true)
  58. # Echo the sub-script's findings (everything before its SUMMARY)
  59. echo "$out" | sed -n '/^=== /,/^=== SUMMARY ===/p' | sed '/^=== SUMMARY ===/q' | sed '$d'
  60. # Extract sub-script's pass/fail/warn counts from its SUMMARY line
  61. local summary_line
  62. summary_line=$(echo "$out" | grep -E "^ PASS: [0-9]+" | head -1)
  63. if [[ -n "$summary_line" ]]; then
  64. local pass fail warn info
  65. pass=$(echo "$summary_line" | grep -oE "PASS: [0-9]+" | awk '{print $2}')
  66. fail=$(echo "$summary_line" | grep -oE "FAIL: [0-9]+" | awk '{print $2}')
  67. warn=$(echo "$summary_line" | grep -oE "WARN: [0-9]+" | awk '{print $2}')
  68. info=$(echo "$summary_line" | grep -oE "INFO: [0-9]+" | awk '{print $2}')
  69. TOTAL_PASS=$((TOTAL_PASS + ${pass:-0}))
  70. TOTAL_FAIL=$((TOTAL_FAIL + ${fail:-0}))
  71. TOTAL_WARN=$((TOTAL_WARN + ${warn:-0}))
  72. TOTAL_INFO=$((TOTAL_INFO + ${info:-0}))
  73. fi
  74. # Collect FAIL and WARN lines for the consolidated summary
  75. while IFS= read -r line; do
  76. [[ -n "$line" ]] && ALL_FAILS+=("[$label] $line")
  77. done < <(echo "$out" | grep -E "^\[FAIL\]" | sed 's/^\[FAIL\] //')
  78. while IFS= read -r line; do
  79. [[ -n "$line" ]] && ALL_WARNS+=("[$label] $line")
  80. done < <(echo "$out" | grep -E "^\[WARN\]" | sed 's/^\[WARN\] //')
  81. }
  82. note " Starting mac-ops quickrun (this takes ~60-90s due to log show queries)..."
  83. run_subscript "1. HEALTH AUDIT" "health-audit.sh" --days "$SHORT_DAYS"
  84. run_subscript "2. STARTUP INVENTORY" "startup-audit.sh"
  85. run_subscript "3. STORAGE PRESSURE" "storage-pressure.sh"
  86. run_subscript "4. WAKE REASONS (last ${SHORT_DAYS}d)" "wake-reasons.sh" --since "${SHORT_DAYS}d"
  87. run_subscript "5. TCC DENIALS" "tcc-audit.sh" --denied
  88. # ----------------------------------------------------------------------------
  89. note ""
  90. note "════════════════════════════════════════════════════════════════"
  91. note " CONSOLIDATED VERDICT"
  92. note "════════════════════════════════════════════════════════════════"
  93. if [[ "$JSON_MODE" -eq 1 ]]; then
  94. printf '{"type":"quickrun_summary","pass":%d,"fail":%d,"warn":%d,"info":%d,"fail_count":%d,"warn_count":%d}\n' \
  95. "$TOTAL_PASS" "$TOTAL_FAIL" "$TOTAL_WARN" "$TOTAL_INFO" \
  96. "${#ALL_FAILS[@]}" "${#ALL_WARNS[@]}"
  97. elif panel_enabled; then
  98. hostname_short=$(scutil --get LocalHostName 2>/dev/null | head -c 30 || hostname -s | head -c 30)
  99. echo ""
  100. term_panel_open mac-ops "mac-ops · quickrun" "$hostname_short"
  101. term_panel_vert
  102. term_summary_line "$((TOTAL_PASS+TOTAL_FAIL+TOTAL_WARN+TOTAL_INFO)) checks across 5 audits · $TOTAL_FAIL fail · $TOTAL_WARN warn"
  103. term_panel_vert
  104. if [[ "${#ALL_FAILS[@]}" -gt 0 ]]; then
  105. term_section "FAILED" "failing" "${#ALL_FAILS[@]}"
  106. n=${#ALL_FAILS[@]}
  107. i=0
  108. for f in "${ALL_FAILS[@]}"; do
  109. i=$((i+1))
  110. if [[ "$i" -eq "$n" ]]; then connector="$TERM_TREE_LAST"; else connector="$TERM_TREE_BRANCH"; fi
  111. line="$f"
  112. (( ${#line} > 100 )) && line="${line:0:97}..."
  113. printf '%s %s %s\n' \
  114. "$(term_color dim "$TERM_TREE_VERT")" \
  115. "$connector" \
  116. "$line"
  117. [[ "$i" -ge 10 ]] && { printf '%s %s %s\n' "$(term_color dim "$TERM_TREE_VERT")" "$TERM_TREE_LAST" "$(term_color dim "... +$((n-10)) more")"; break; }
  118. done
  119. term_panel_vert
  120. fi
  121. if [[ "${#ALL_WARNS[@]}" -gt 0 ]]; then
  122. term_section "WARN" "warning" "${#ALL_WARNS[@]}"
  123. n=${#ALL_WARNS[@]}
  124. i=0
  125. for w in "${ALL_WARNS[@]}"; do
  126. i=$((i+1))
  127. if [[ "$i" -eq "$n" ]]; then connector="$TERM_TREE_LAST"; else connector="$TERM_TREE_BRANCH"; fi
  128. line="$w"
  129. (( ${#line} > 100 )) && line="${line:0:97}..."
  130. printf '%s %s %s\n' \
  131. "$(term_color dim "$TERM_TREE_VERT")" \
  132. "$connector" \
  133. "$line"
  134. [[ "$i" -ge 10 ]] && { printf '%s %s %s\n' "$(term_color dim "$TERM_TREE_VERT")" "$TERM_TREE_LAST" "$(term_color dim "... +$((n-10)) more")"; break; }
  135. done
  136. term_panel_vert
  137. fi
  138. term_section "OK" "pass" "$TOTAL_PASS"
  139. if [[ "$TOTAL_INFO" -gt 0 ]]; then
  140. term_section "info" "info" "$TOTAL_INFO"
  141. fi
  142. term_panel_vert
  143. health_state="healthy"
  144. [[ "$TOTAL_WARN" -gt 0 ]] && health_state="warning"
  145. [[ "$TOTAL_FAIL" -gt 0 ]] && health_state="critical"
  146. right_health="$(term_health "$health_state" "$TOTAL_FAIL fail · $TOTAL_WARN warn")"
  147. term_panel_close "" "$right_health"
  148. echo ""
  149. if [[ "${#ALL_FAILS[@]}" -eq 0 ]] && [[ "${#ALL_WARNS[@]}" -eq 0 ]]; then
  150. echo " ✓ System looks clean. No FAILs or WARNs across 5 audits."
  151. fi
  152. echo " Next: drill any FAIL into its source script (--verbose for detail)"
  153. else
  154. echo
  155. echo " Aggregate: PASS $TOTAL_PASS FAIL $TOTAL_FAIL WARN $TOTAL_WARN INFO $TOTAL_INFO"
  156. echo
  157. if [[ "${#ALL_FAILS[@]}" -gt 0 ]]; then
  158. echo " ⚠ FAILURES (${#ALL_FAILS[@]}):"
  159. for f in ${ALL_FAILS[@]+"${ALL_FAILS[@]}"}; do
  160. echo " • $f"
  161. done | head -10
  162. fi
  163. if [[ "${#ALL_WARNS[@]}" -gt 0 ]]; then
  164. echo
  165. echo " ⚠ WARNINGS (${#ALL_WARNS[@]}):"
  166. for w in ${ALL_WARNS[@]+"${ALL_WARNS[@]}"}; do
  167. echo " • $w"
  168. done | head -10
  169. fi
  170. if [[ "${#ALL_FAILS[@]}" -eq 0 ]] && [[ "${#ALL_WARNS[@]}" -eq 0 ]]; then
  171. echo " ✓ System looks clean. No FAILs or WARNs across 5 audits."
  172. fi
  173. echo
  174. echo " Next steps:"
  175. echo " - Drill into any FAIL: see 'Next:' lines emitted by each sub-script"
  176. echo " - Run individual scripts with --verbose for more detail"
  177. echo " - See references/worked-examples.md for diagnostic patterns"
  178. fi