update-state.sh 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. #!/usr/bin/env bash
  2. # mac-ops :: update-state.sh
  3. # macOS Software Update audit: auto-update settings, pending updates,
  4. # update history, App Store update settings.
  5. set -u
  6. while [[ $# -gt 0 ]]; do
  7. case "$1" in
  8. --help|-h)
  9. cat <<EOF
  10. Usage: $0 [options]
  11. --json, --redact, --quiet, --verbose
  12. Reports:
  13. 1. macOS Software Update auto-policy
  14. 2. Pending updates (softwareupdate -l)
  15. 3. macOS version + build
  16. 4. Update install history (last 10)
  17. 5. App Store auto-update settings
  18. 6. Pending app updates from Mac App Store
  19. Skip the spinner: 'softwareupdate -l' contacts Apple's servers and can take
  20. 30-60 seconds. The script prints expected-delay markers.
  21. EOF
  22. exit 0 ;;
  23. *) shift ;;
  24. esac
  25. done
  26. source "$(dirname "$0")/_lib/common.sh"
  27. parse_common_flags "$@"
  28. maybe_filter_self "$@"
  29. # ----------------------------------------------------------------------------
  30. section "1. AUTO-UPDATE POLICY"
  31. # ----------------------------------------------------------------------------
  32. # Read from com.apple.SoftwareUpdate
  33. auto_check=$(defaults read /Library/Preferences/com.apple.SoftwareUpdate AutomaticCheckEnabled 2>/dev/null)
  34. auto_download=$(defaults read /Library/Preferences/com.apple.SoftwareUpdate AutomaticDownload 2>/dev/null)
  35. auto_install_macos=$(defaults read /Library/Preferences/com.apple.SoftwareUpdate AutomaticallyInstallMacOSUpdates 2>/dev/null)
  36. auto_install_app=$(defaults read /Library/Preferences/com.apple.commerce AutoUpdate 2>/dev/null)
  37. auto_install_security=$(defaults read /Library/Preferences/com.apple.SoftwareUpdate ConfigDataInstall 2>/dev/null)
  38. show_bool() {
  39. case "$1" in
  40. 1) echo "ON" ;;
  41. 0) echo "OFF" ;;
  42. *) echo "(unset)" ;;
  43. esac
  44. }
  45. note " Software Update preferences:"
  46. note " Check for updates automatically: $(show_bool "$auto_check")"
  47. note " Download updates when available: $(show_bool "$auto_download")"
  48. note " Install macOS updates: $(show_bool "$auto_install_macos")"
  49. note " Install app updates from App Store: $(show_bool "$auto_install_app")"
  50. note " Install system data + security: $(show_bool "$auto_install_security")"
  51. if [[ "$auto_check" == "1" ]] && [[ "$auto_install_security" == "1" ]]; then
  52. log_pass "Security updates" "auto-install ON"
  53. else
  54. log_warn "Security updates" "auto-install OFF — patches won't apply without manual action"
  55. fi
  56. # ----------------------------------------------------------------------------
  57. section "2. MACOS VERSION"
  58. # ----------------------------------------------------------------------------
  59. prod=$(sw_vers -productName 2>/dev/null)
  60. ver=$(sw_vers -productVersion 2>/dev/null)
  61. build=$(sw_vers -buildVersion 2>/dev/null)
  62. note " $prod $ver ($build)"
  63. log_info "macOS version" "$ver build $build"
  64. # ----------------------------------------------------------------------------
  65. section "3. PENDING UPDATES"
  66. # ----------------------------------------------------------------------------
  67. note " Checking with Apple's servers (this can take 30-60s)..."
  68. pending=$(softwareupdate -l 2>&1 | tail -20)
  69. if echo "$pending" | grep -q "No new software available"; then
  70. log_pass "Pending updates" "none — system is current"
  71. elif echo "$pending" | grep -q "Software Update found"; then
  72. note " Pending list:"
  73. echo "$pending" | grep -E "(\*|^Software|Title:|Action:|Recommended:)" | head -20 | sed 's/^/ /'
  74. update_count=$(echo "$pending" | grep -c "Title:" || echo 0)
  75. log_warn "Pending updates" "$update_count items pending"
  76. else
  77. log_info "softwareupdate output" "$(echo "$pending" | head -3 | tr '\n' ' ')"
  78. fi
  79. # ----------------------------------------------------------------------------
  80. section "4. RECENT UPDATE HISTORY"
  81. # ----------------------------------------------------------------------------
  82. history_plist="/Library/Updates/index.plist"
  83. note " Last 10 install events from /Library/Updates (best-effort):"
  84. if [[ -r "$history_plist" ]]; then
  85. # Can't easily parse the plist without knowing the schema; show
  86. # mtime of the directory entries as a proxy
  87. ls -lt /Library/Updates 2>/dev/null | head -10 | sed 's/^/ /'
  88. fi
  89. # Also check installer logs
  90. recent_installs=$(log show --last 30d --style compact \
  91. --predicate 'process == "softwareupdated" AND eventMessage CONTAINS[c] "installed"' \
  92. 2>/dev/null | tail -5)
  93. if [[ -n "$recent_installs" ]]; then
  94. note ""
  95. note " Recent softwareupdated install entries (log, last 30d):"
  96. echo "$recent_installs" | sed 's/^/ /'
  97. fi
  98. # ----------------------------------------------------------------------------
  99. section "5. MAS / APP STORE"
  100. # ----------------------------------------------------------------------------
  101. mas_check=$(defaults read /Library/Preferences/com.apple.commerce AutoUpdate 2>/dev/null)
  102. note " App Store auto-update: $(show_bool "$mas_check")"
  103. # Is mas installed (third-party MAS CLI)?
  104. if command -v mas >/dev/null 2>&1; then
  105. note ""
  106. note " mas (Mac App Store CLI) is installed."
  107. mas_outdated=$(mas outdated 2>/dev/null)
  108. if [[ -n "$mas_outdated" ]]; then
  109. n=$(echo "$mas_outdated" | wc -l | tr -d ' ')
  110. log_info "Pending MAS updates" "$n (via mas outdated)"
  111. echo "$mas_outdated" | head -10 | sed 's/^/ /'
  112. else
  113. log_pass "MAS apps" "all up to date"
  114. fi
  115. fi
  116. # ----------------------------------------------------------------------------
  117. section "6. RECOMMENDED UPDATES"
  118. # ----------------------------------------------------------------------------
  119. # Recommended security/system updates that Apple wants you to install
  120. # (subset of softwareupdate -l output)
  121. recommended=$(softwareupdate -l 2>&1 | awk '/Recommended: YES/{print prev} {prev=$0}' | head -5)
  122. if [[ -n "$recommended" ]]; then
  123. log_warn "Recommended updates pending" "see list"
  124. echo "$recommended" | sed 's/^/ /'
  125. fi
  126. # ----------------------------------------------------------------------------
  127. emit_summary
  128. if [[ "$JSON_MODE" -eq 0 ]]; then
  129. echo
  130. note " Install playbook:"
  131. note " softwareupdate -l # list pending"
  132. note " sudo softwareupdate -i -a -R # install all (recommended), reboot if needed"
  133. note " softwareupdate --install <name> # specific update"
  134. fi