| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- #!/usr/bin/env bash
- # mac-ops :: safe-disable-startup.sh
- # Disable a startup item by name pattern. Reversible.
- #
- # Mechanisms handled (no sudo for user-scope):
- # - Login Items (via osascript / System Events)
- # - User LaunchAgents (launchctl disable gui/$UID/<label>)
- # - System LaunchAgents (launchctl disable gui/$UID/<label>)
- #
- # Mechanisms handled (sudo required):
- # - System LaunchDaemons (sudo launchctl disable system/<label>)
- #
- # Default mode is DRY RUN. Pass --apply to actually disable.
- # Use --enable to reverse a prior disable.
- set -u
- NAME_PATTERN=""
- APPLY=0
- ENABLE=0
- LIST_ONLY=0
- while [[ $# -gt 0 ]]; do
- case "$1" in
- -n|--name) NAME_PATTERN="$2"; shift 2 ;;
- --list) LIST_ONLY=1; shift ;;
- --apply) APPLY=1; shift ;;
- --enable) ENABLE=1; APPLY=1; shift ;;
- --help|-h)
- cat <<EOF
- Usage: $0 [options]
- -n, --name PATTERN Glob pattern matching the entry label / name
- --list List all currently-disabled launchd entries
- --apply Actually perform the disable (default: dry-run)
- --enable Re-enable a previously disabled item (implies --apply)
- --json, --redact, --quiet, --verbose Standard flags
- Examples:
- $0 --list # show current disable state
- $0 -n 'com.adobe.*' # dry-run: what would be disabled?
- $0 -n 'com.adobe.*' --apply # disable Adobe agents
- $0 -n 'com.adobe.*' --enable # re-enable
- $0 -n 'Adobe Updater' --apply # also matches Login Item by name
- Note: System LaunchDaemons (/Library/LaunchDaemons) require sudo and operate
- on system/<label> instead of gui/\$UID/<label>. The script asks for sudo only
- when needed.
- EOF
- exit 0 ;;
- *) shift ;;
- esac
- done
- source "$(dirname "$0")/_lib/common.sh"
- parse_common_flags "$@"
- maybe_filter_self "$@"
- # ----------------------------------------------------------------------------
- # --list mode: show all disabled launchctl entries
- # ----------------------------------------------------------------------------
- if [[ "$LIST_ONLY" -eq 1 ]]; then
- section "DISABLED LAUNCHD ENTRIES (user domain)"
- if launchctl print-disabled "gui/$UID" 2>/dev/null | grep -E "=> (true|disabled)$" | sed 's/^/ /'; then
- :
- else
- note " (no user-domain disables, or print-disabled requires newer macOS)"
- fi
- section "DISABLED LAUNCHD ENTRIES (system domain — sudo)"
- if sudo -n launchctl print-disabled system 2>/dev/null | grep -E "=> (true|disabled)$" | sed 's/^/ /'; then
- :
- else
- note " (system domain requires sudo, or no entries disabled)"
- fi
- emit_summary
- exit 0
- fi
- if [[ -z "$NAME_PATTERN" ]]; then
- echo "Error: -n PATTERN required (or --list)" >&2
- exit "$EXIT_USAGE"
- fi
- # ----------------------------------------------------------------------------
- section "1. SEARCH MATCHES"
- # ----------------------------------------------------------------------------
- # Find matching LaunchAgent plists (user + system)
- user_matches=()
- sys_agent_matches=()
- sys_daemon_matches=()
- for p in "$HOME/Library/LaunchAgents"/*.plist /Library/LaunchAgents/*.plist; do
- [[ -f "$p" ]] || continue
- label=$(/usr/libexec/PlistBuddy -c "Print :Label" "$p" 2>/dev/null || basename "$p" .plist)
- # Match by label OR filename
- if [[ "$label" == $NAME_PATTERN ]] || [[ "$(basename "$p" .plist)" == $NAME_PATTERN ]]; then
- case "$p" in
- "$HOME"/*) user_matches+=("$label|$p") ;;
- *) sys_agent_matches+=("$label|$p") ;;
- esac
- fi
- done
- for p in /Library/LaunchDaemons/*.plist; do
- [[ -f "$p" ]] || continue
- label=$(/usr/libexec/PlistBuddy -c "Print :Label" "$p" 2>/dev/null || basename "$p" .plist)
- if [[ "$label" == $NAME_PATTERN ]] || [[ "$(basename "$p" .plist)" == $NAME_PATTERN ]]; then
- sys_daemon_matches+=("$label|$p")
- fi
- done
- # Find matching Login Items (by name only; AppleScript glob match)
- login_item_matches=()
- if items=$(osascript <<APPLESCRIPT 2>/dev/null
- tell application "System Events"
- set output to ""
- repeat with li in (every login item)
- set itemName to name of li
- if itemName is like "$NAME_PATTERN" then
- set output to output & itemName & linefeed
- end if
- end repeat
- return output
- end tell
- APPLESCRIPT
- ); then
- while IFS= read -r name; do
- [[ -n "$name" ]] && login_item_matches+=("$name")
- done <<< "$items"
- fi
- total_matches=$(( ${#user_matches[@]} + ${#sys_agent_matches[@]} + ${#sys_daemon_matches[@]} + ${#login_item_matches[@]} ))
- if [[ "$total_matches" -eq 0 ]]; then
- log_warn "Matches for '$NAME_PATTERN'" "0 — nothing to do"
- emit_summary
- exit "$EXIT_NOT_FOUND"
- fi
- log_pass "Matches for '$NAME_PATTERN'" "$total_matches"
- [[ ${#user_matches[@]} -gt 0 ]] && note " User LaunchAgents:" && printf " %s\n" "${user_matches[@]%|*}"
- [[ ${#sys_agent_matches[@]} -gt 0 ]] && note " System LaunchAgents:" && printf " %s\n" "${sys_agent_matches[@]%|*}"
- [[ ${#sys_daemon_matches[@]} -gt 0 ]] && note " System LaunchDaemons:" && printf " %s\n" "${sys_daemon_matches[@]%|*}"
- [[ ${#login_item_matches[@]} -gt 0 ]] && note " Login Items:" && printf " %s\n" "${login_item_matches[@]}"
- # ----------------------------------------------------------------------------
- if [[ "$APPLY" -eq 0 ]]; then
- section "2. DRY RUN — would $([[ "$ENABLE" -eq 1 ]] && echo enable || echo disable)"
- note " Pass --apply to perform the action."
- emit_summary
- exit 0
- fi
- # ----------------------------------------------------------------------------
- verb=$([[ "$ENABLE" -eq 1 ]] && echo enable || echo disable)
- section "2. APPLY — ${verb}"
- # launchctl verb selection
- lctl_verb=$([[ "$ENABLE" -eq 1 ]] && echo enable || echo disable)
- # Disable user agents (no sudo)
- for entry in ${user_matches[@]+"${user_matches[@]}"} ${sys_agent_matches[@]+"${sys_agent_matches[@]}"}; do
- label="${entry%|*}"
- if launchctl "$lctl_verb" "gui/$UID/$label" 2>/dev/null; then
- log_pass "launchctl $lctl_verb gui/$UID/$label"
- else
- log_warn "launchctl $lctl_verb gui/$UID/$label" "may already be in target state"
- fi
- done
- # Disable system daemons (sudo)
- if [[ ${#sys_daemon_matches[@]} -gt 0 ]]; then
- note " System daemons require sudo:"
- for entry in "${sys_daemon_matches[@]}"; do
- label="${entry%|*}"
- if sudo launchctl "$lctl_verb" "system/$label" 2>/dev/null; then
- log_pass "sudo launchctl $lctl_verb system/$label"
- else
- log_warn "sudo launchctl $lctl_verb system/$label" "may need sudo or already in state"
- fi
- done
- fi
- # Login Items (via osascript)
- for name in ${login_item_matches[@]+"${login_item_matches[@]}"}; do
- if [[ "$ENABLE" -eq 1 ]]; then
- log_warn "Login Item '$name'" "re-enable requires manual re-add (System Settings → Login Items)"
- else
- if osascript -e "tell application \"System Events\" to delete login item \"$name\"" 2>/dev/null; then
- log_pass "Removed Login Item" "$name"
- else
- log_warn "Login Item '$name'" "removal failed (TCC may be blocking System Events)"
- fi
- fi
- done
- emit_summary
|