|
#!/usr/bin/env bash |
|
set -euo pipefail |
|
|
|
# ============================================================ |
|
# macOS Lean Mode (Interactive) - Mac mini friendly |
|
# |
|
# ✅ Does: |
|
# - Disable selected Apple LaunchAgents (per-user) and LaunchDaemons (system) |
|
# using `launchctl bootout` + `launchctl disable`. |
|
# - Creates a backup log so you can restore what was changed. |
|
# |
|
# ❌ Does NOT: |
|
# - Delete apps from /System/Applications |
|
# - Require SIP / authenticated-root changes |
|
# |
|
# Backup log: |
|
# - ~/.macos-lean-mode-backup/disabled-services-YYYYMMDD-HHMMSS.txt |
|
# |
|
# Restore: |
|
# - Option 4 in menu restores from latest backup file. |
|
# |
|
# ============================================================ |
|
|
|
UID_NOW="$(id -u)" |
|
|
|
BACKUP_DIR="$HOME/.macos-lean-mode-backup" |
|
TIMESTAMP="$(date +"%Y%m%d-%H%M%S")" |
|
BACKUP_FILE="$BACKUP_DIR/disabled-services-$TIMESTAMP.txt" |
|
|
|
mkdir -p "$BACKUP_DIR" |
|
|
|
log_disabled() { |
|
echo "$1" >> "$BACKUP_FILE" |
|
} |
|
|
|
hr() { printf "%s\n" "------------------------------------------------------------"; } |
|
die() { echo "ERROR: $*" >&2; exit 1; } |
|
|
|
has_cmd() { command -v "$1" >/dev/null 2>&1; } |
|
|
|
require_cmds() { |
|
has_cmd launchctl || die "launchctl not found" |
|
has_cmd sudo || die "sudo not found" |
|
} |
|
|
|
disable_user_agent() { |
|
local label="$1" |
|
echo "→ [USER] disabling: $label" |
|
log_disabled "gui:${UID_NOW}:$label" |
|
launchctl bootout "gui/${UID_NOW}/${label}" >/dev/null 2>&1 || true |
|
launchctl disable "gui/${UID_NOW}/${label}" >/dev/null 2>&1 || true |
|
} |
|
|
|
disable_system_daemon() { |
|
local label="$1" |
|
echo "→ [SYSTEM] disabling: $label" |
|
log_disabled "system:$label" |
|
sudo launchctl bootout "system/${label}" >/dev/null 2>&1 || true |
|
sudo launchctl disable "system/${label}" >/dev/null 2>&1 || true |
|
} |
|
|
|
enable_user_agent() { |
|
local uid="$1" |
|
local label="$2" |
|
echo "→ [USER] enabling: $label" |
|
launchctl enable "gui/${uid}/${label}" >/dev/null 2>&1 || true |
|
launchctl kickstart -k "gui/${uid}/${label}" >/dev/null 2>&1 || true |
|
} |
|
|
|
enable_system_daemon() { |
|
local label="$1" |
|
echo "→ [SYSTEM] enabling: $label" |
|
sudo launchctl enable "system/${label}" >/dev/null 2>&1 || true |
|
sudo launchctl kickstart -k "system/${label}" >/dev/null 2>&1 || true |
|
} |
|
|
|
# ============================================================ |
|
# Buckets (based on the b0gdanw Tahoe gist list) |
|
# ============================================================ |
|
|
|
# ✅ SAFE: low blast radius, good ROI |
|
# Expected performance benefit: |
|
# - fewer telemetry/promo/tips computations |
|
# - slightly lower idle CPU + fewer wakeups |
|
# - small RAM savings over long uptime |
|
SAFE_USER_AGENTS=( |
|
"com.apple.ap.adprivacyd" |
|
"com.apple.ap.promotedcontentd" |
|
"com.apple.inputanalyticsd" |
|
"com.apple.geoanalyticsd" |
|
"com.apple.UsageTrackingAgent" |
|
"com.apple.tipsd" |
|
"com.apple.suggestd" |
|
) |
|
|
|
SAFE_SYSTEM_DAEMONS=( |
|
"com.apple.analyticsd" |
|
"com.apple.audioanalyticsd" |
|
"com.apple.ecosystemanalyticsd" |
|
"com.apple.wifianalyticsd" |
|
) |
|
|
|
# 🟡 MODERATE: use-case dependent |
|
# Expected performance benefit: |
|
# - noticeable reduction in background CPU spikes (Siri stack, Photos analysis) |
|
# - moderate RAM savings if these stacks are active |
|
# Tradeoff: disables Siri, Photos analysis/indexing, some continuity/media bits |
|
MODERATE_USER_AGENTS=( |
|
"com.apple.assistantd" |
|
"com.apple.Siri.agent" |
|
"com.apple.siriinferenced" |
|
"com.apple.sirittsd" |
|
"com.apple.siriactionsd" |
|
"com.apple.siriknowledged" |
|
"com.apple.watchlistd" |
|
"com.apple.videosubscriptionsd" |
|
"com.apple.ScreenTimeAgent" |
|
"com.apple.photoanalysisd" |
|
"com.apple.photolibraryd" |
|
"com.apple.rapportd-user" |
|
) |
|
|
|
MODERATE_SYSTEM_DAEMONS=( |
|
"com.apple.findmy.findmybeaconingd" |
|
) |
|
|
|
# 🔴 RISKY: higher chance of breaking features |
|
# Expected performance benefit: |
|
# - can reduce background sync and “smart feature” overhead |
|
# Tradeoff: |
|
# - may break iCloud/keychain sync, Time Machine, location, sharing, screen sharing, |
|
# Finder previews/thumbnails, and other subtle system behaviors |
|
RISKY_USER_AGENTS=( |
|
"com.apple.security.cloudkeychainproxy3" |
|
"com.apple.protectedcloudstorage.protectedcloudkeysyncing" |
|
"com.apple.sharingd" |
|
"com.apple.quicklook" |
|
"com.apple.quicklook.ui.helper" |
|
"com.apple.quicklook.ThumbnailsAgent" |
|
"com.apple.screensharing.agent" |
|
"com.apple.screensharing.menuextra" |
|
"com.apple.screensharing.MessagesAgent" |
|
) |
|
|
|
RISKY_SYSTEM_DAEMONS=( |
|
"com.apple.backupd" |
|
"com.apple.backupd-helper" |
|
"com.apple.cloudd" |
|
"com.apple.icloud.searchpartyd" |
|
"com.apple.locationd" |
|
"com.apple.coreduetd" |
|
"com.apple.modelmanagerd" |
|
"com.apple.triald.system" |
|
"com.apple.screensharing" |
|
) |
|
|
|
# ============================================================ |
|
# Preview / UX |
|
# ============================================================ |
|
|
|
count_items() { |
|
local -n arr="$1" |
|
echo "${#arr[@]}" |
|
} |
|
|
|
print_list() { |
|
local -n arr="$1" |
|
for x in "${arr[@]}"; do echo " - $x"; done |
|
} |
|
|
|
preview_tier() { |
|
local tier="$1" |
|
hr |
|
echo "Tier: $tier" |
|
case "$tier" in |
|
SAFE) |
|
echo "Includes:" |
|
echo " User agents: $(count_items SAFE_USER_AGENTS)" |
|
echo " System daemons:$(count_items SAFE_SYSTEM_DAEMONS)" |
|
echo |
|
echo "Performance (expected):" |
|
echo " - fewer background wakeups (telemetry/tips/suggestions)" |
|
echo " - slightly lower idle CPU" |
|
echo " - small RAM savings over long uptime" |
|
echo |
|
echo "Labels:" |
|
print_list SAFE_USER_AGENTS |
|
print_list SAFE_SYSTEM_DAEMONS |
|
;; |
|
MODERATE) |
|
echo "Includes:" |
|
echo " User agents: $(count_items MODERATE_USER_AGENTS)" |
|
echo " System daemons:$(count_items MODERATE_SYSTEM_DAEMONS)" |
|
echo |
|
echo "Performance (expected):" |
|
echo " - noticeably fewer CPU spikes (Siri/Photos analysis)" |
|
echo " - moderate RAM savings if these subsystems run" |
|
echo |
|
echo "Tradeoffs:" |
|
echo " - disables Siri stack, Photos analysis/indexing, some continuity/media bits" |
|
echo |
|
echo "Labels:" |
|
print_list MODERATE_USER_AGENTS |
|
print_list MODERATE_SYSTEM_DAEMONS |
|
;; |
|
RISKY) |
|
echo "Includes:" |
|
echo " User agents: $(count_items RISKY_USER_AGENTS)" |
|
echo " System daemons:$(count_items RISKY_SYSTEM_DAEMONS)" |
|
echo |
|
echo "Performance (expected):" |
|
echo " - potential reduction in background sync and smart-feature overhead" |
|
echo |
|
echo "Tradeoffs (high risk):" |
|
echo " - may break iCloud/keychain sync, Time Machine, location, sharing/screen sharing" |
|
echo " - may break Finder previews/thumbnails (Quick Look)" |
|
echo |
|
echo "Labels:" |
|
print_list RISKY_USER_AGENTS |
|
print_list RISKY_SYSTEM_DAEMONS |
|
;; |
|
*) |
|
die "Unknown tier preview: $tier" |
|
;; |
|
esac |
|
hr |
|
} |
|
|
|
apply_safe() { |
|
: > "$BACKUP_FILE" |
|
echo "Backup log: $BACKUP_FILE" |
|
for a in "${SAFE_USER_AGENTS[@]}"; do disable_user_agent "$a"; done |
|
for d in "${SAFE_SYSTEM_DAEMONS[@]}"; do disable_system_daemon "$d"; done |
|
} |
|
|
|
apply_moderate() { |
|
for a in "${MODERATE_USER_AGENTS[@]}"; do disable_user_agent "$a"; done |
|
for d in "${MODERATE_SYSTEM_DAEMONS[@]}"; do disable_system_daemon "$d"; done |
|
} |
|
|
|
apply_risky() { |
|
for a in "${RISKY_USER_AGENTS[@]}"; do disable_user_agent "$a"; done |
|
for d in "${RISKY_SYSTEM_DAEMONS[@]}"; do disable_system_daemon "$d"; done |
|
} |
|
|
|
restore_latest() { |
|
local latest |
|
latest="$(ls -t "$BACKUP_DIR"/disabled-services-*.txt 2>/dev/null | head -n 1 || true)" |
|
[ -n "$latest" ] || die "No backup files found in $BACKUP_DIR" |
|
echo "Restoring from: $latest" |
|
hr |
|
|
|
while read -r line; do |
|
# gui:<uid>:<label> OR system:<label> |
|
IFS=":" read -r scope a b <<< "$line" |
|
if [ "$scope" = "gui" ]; then |
|
enable_user_agent "$a" "$b" |
|
elif [ "$scope" = "system" ]; then |
|
enable_system_daemon "$a" |
|
fi |
|
done < "$latest" |
|
|
|
hr |
|
echo "Restore complete. Reboot recommended." |
|
} |
|
|
|
nuclear_reset_info() { |
|
hr |
|
echo "NUCLEAR RESET (manual):" |
|
echo |
|
echo "If things are badly broken and you want to reset launchctl overrides:" |
|
echo " sudo rm -rf /private/var/db/com.apple.xpc.launchd/disabled.*" |
|
echo " sudo rm -rf /private/var/db/com.apple.xpc.launchd/overrides.*" |
|
echo " sudo reboot" |
|
echo |
|
echo "Warning: This resets ALL launchctl overrides, not just this script." |
|
hr |
|
} |
|
|
|
confirm() { |
|
local prompt="$1" |
|
read -r -p "$prompt [y/N]: " ans |
|
case "${ans,,}" in |
|
y|yes) return 0 ;; |
|
*) return 1 ;; |
|
esac |
|
} |
|
|
|
menu() { |
|
echo "macOS Lean Mode (Interactive)" |
|
echo "User UID: $UID_NOW" |
|
echo "Backup dir: $BACKUP_DIR" |
|
hr |
|
echo "Choose:" |
|
echo " 1) SAFE (recommended) - low risk, good ROI" |
|
echo " 2) SAFE + MODERATE - more savings, may disable features" |
|
echo " 3) SAFE + MODERATE + RISKY - maximum debloat (high breakage risk)" |
|
echo " 4) RESTORE last run - re-enable what this script disabled" |
|
echo " 5) Nuclear reset info - last resort instructions" |
|
echo " 0) Exit" |
|
hr |
|
} |
|
|
|
main() { |
|
require_cmds |
|
|
|
menu |
|
read -r -p "Enter choice: " choice |
|
|
|
case "$choice" in |
|
1) |
|
preview_tier SAFE |
|
confirm "Apply SAFE tier?" || exit 0 |
|
apply_safe |
|
echo "SAFE applied. Reboot recommended: sudo reboot" |
|
;; |
|
2) |
|
preview_tier SAFE |
|
preview_tier MODERATE |
|
confirm "Apply SAFE + MODERATE tiers?" || exit 0 |
|
apply_safe |
|
apply_moderate |
|
echo "SAFE+MODERATE applied. Reboot recommended: sudo reboot" |
|
;; |
|
3) |
|
preview_tier SAFE |
|
preview_tier MODERATE |
|
preview_tier RISKY |
|
echo "High risk option selected." |
|
echo "If you use iCloud, Time Machine, Location Services, Screen Sharing, or Finder previews—do NOT proceed." |
|
confirm "Type YES to proceed with RISKY tier?" && true || { echo "Cancelled."; exit 0; } |
|
# Stronger confirmation |
|
read -r -p "Final confirmation: type EXACTLY 'I ACCEPT BREAKAGE' to continue: " final |
|
[ "$final" = "I ACCEPT BREAKAGE" ] || { echo "Cancelled."; exit 0; } |
|
apply_safe |
|
apply_moderate |
|
apply_risky |
|
echo "All tiers applied. Reboot recommended: sudo reboot" |
|
;; |
|
4) |
|
restore_latest |
|
;; |
|
5) |
|
nuclear_reset_info |
|
;; |
|
0) |
|
echo "Bye." |
|
;; |
|
*) |
|
die "Invalid choice." |
|
;; |
|
esac |
|
|
|
hr |
|
echo "Notes:" |
|
echo "- Backup log created per run. Latest file is used for restore." |
|
echo "- If issues persist, restore (option 4) + reboot." |
|
echo "- If still broken, use nuclear reset (option 5) or reinstall macOS over existing install." |
|
hr |
|
} |
|
|
|
main "$@" |