mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 15:04:42 +00:00
feat: Add new system optimizations, refine existing tasks with safety checks, and update whitelisting options.
This commit is contained in:
@@ -283,6 +283,8 @@ check_macos_update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
check_mole_update() {
|
check_mole_update() {
|
||||||
|
if command -v is_whitelisted > /dev/null && is_whitelisted "check_mole_update"; then return; fi
|
||||||
|
|
||||||
# Check if Mole has updates
|
# Check if Mole has updates
|
||||||
# Auto-detect version from mole main script
|
# Auto-detect version from mole main script
|
||||||
local current_version
|
local current_version
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ EOF
|
|||||||
# Core optimizations (safe and valuable)
|
# Core optimizations (safe and valuable)
|
||||||
items+=('system_maintenance|DNS & Spotlight Check|Refresh DNS cache & verify Spotlight status|true')
|
items+=('system_maintenance|DNS & Spotlight Check|Refresh DNS cache & verify Spotlight status|true')
|
||||||
items+=('cache_refresh|Finder Cache Refresh|Refresh QuickLook thumbnails & icon services cache|true')
|
items+=('cache_refresh|Finder Cache Refresh|Refresh QuickLook thumbnails & icon services cache|true')
|
||||||
items+=('saved_state_cleanup|App State Cleanup|Remove old saved application states (7+ days)|true')
|
items+=('saved_state_cleanup|App State Cleanup|Remove old saved application states (30+ days)|true')
|
||||||
items+=('fix_broken_configs|Broken Config Repair|Fix corrupted preferences files|true')
|
items+=('fix_broken_configs|Broken Config Repair|Fix corrupted preferences files|true')
|
||||||
items+=('network_optimization|Network Cache Refresh|Optimize DNS cache & restart mDNSResponder|true')
|
items+=('network_optimization|Network Cache Refresh|Optimize DNS cache & restart mDNSResponder|true')
|
||||||
|
|
||||||
@@ -133,11 +133,20 @@ EOF
|
|||||||
items+=('sqlite_vacuum|Database Optimization|Compress SQLite databases for Mail, Safari & Messages (skips if apps are running)|true')
|
items+=('sqlite_vacuum|Database Optimization|Compress SQLite databases for Mail, Safari & Messages (skips if apps are running)|true')
|
||||||
items+=('launch_services_rebuild|LaunchServices Repair|Repair "Open with" menu & file associations|true')
|
items+=('launch_services_rebuild|LaunchServices Repair|Repair "Open with" menu & file associations|true')
|
||||||
items+=('font_cache_rebuild|Font Cache Rebuild|Rebuild font database to fix rendering issues|true')
|
items+=('font_cache_rebuild|Font Cache Rebuild|Rebuild font database to fix rendering issues|true')
|
||||||
items+=('startup_items_cleanup|Startup Items Cleanup|Remove broken login items & optimize boot time|true')
|
|
||||||
items+=('dyld_cache_update|App Launch Optimization|Rebuild dyld cache to speed up app launches|true')
|
|
||||||
items+=('system_services_refresh|System Services Refresh|Restart system services to apply optimization changes|true')
|
|
||||||
items+=('dock_refresh|Dock Refresh|Fix broken icons and visual glitches in the Dock|true')
|
items+=('dock_refresh|Dock Refresh|Fix broken icons and visual glitches in the Dock|true')
|
||||||
|
|
||||||
|
# System performance optimizations (new)
|
||||||
|
items+=('memory_pressure_relief|Memory Optimization|Release inactive memory to improve system responsiveness|true')
|
||||||
|
items+=('network_stack_optimize|Network Stack Refresh|Flush routing table and ARP cache to resolve network issues|true')
|
||||||
|
items+=('disk_permissions_repair|Permission Repair|Fix user directory permission issues|true')
|
||||||
|
items+=('bluetooth_reset|Bluetooth Refresh|Restart Bluetooth module to fix connectivity (skips if in use)|true')
|
||||||
|
items+=('spotlight_index_optimize|Spotlight Optimization|Rebuild index if search is slow (smart detection)|true')
|
||||||
|
|
||||||
|
# Removed high-risk optimizations:
|
||||||
|
# - startup_items_cleanup: Risk of deleting legitimate app helpers
|
||||||
|
# - system_services_refresh: Risk of data loss when killing system services
|
||||||
|
# - dyld_cache_update: Low benefit, time-consuming, auto-managed by macOS
|
||||||
|
|
||||||
# Output items as JSON
|
# Output items as JSON
|
||||||
local first=true
|
local first=true
|
||||||
for item in "${items[@]}"; do
|
for item in "${items[@]}"; do
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ readonly MOLE_MAIL_DOWNLOADS_MIN_KB=5120 # Mail attachment size threshold
|
|||||||
readonly MOLE_MAIL_AGE_DAYS=30 # Mail attachment retention (days)
|
readonly MOLE_MAIL_AGE_DAYS=30 # Mail attachment retention (days)
|
||||||
readonly MOLE_LOG_AGE_DAYS=7 # Log retention (days)
|
readonly MOLE_LOG_AGE_DAYS=7 # Log retention (days)
|
||||||
readonly MOLE_CRASH_REPORT_AGE_DAYS=7 # Crash report retention (days)
|
readonly MOLE_CRASH_REPORT_AGE_DAYS=7 # Crash report retention (days)
|
||||||
readonly MOLE_SAVED_STATE_AGE_DAYS=7 # Saved state retention (days)
|
readonly MOLE_SAVED_STATE_AGE_DAYS=30 # Saved state retention (days) - increased for safety
|
||||||
readonly MOLE_TM_BACKUP_SAFE_HOURS=48 # TM backup safety window (hours)
|
readonly MOLE_TM_BACKUP_SAFE_HOURS=48 # TM backup safety window (hours)
|
||||||
readonly MOLE_MAX_DS_STORE_FILES=500 # Max .DS_Store files to clean per scan
|
readonly MOLE_MAX_DS_STORE_FILES=500 # Max .DS_Store files to clean per scan
|
||||||
readonly MOLE_MAX_ORPHAN_ITERATIONS=100 # Max iterations for orphaned app data scan
|
readonly MOLE_MAX_ORPHAN_ITERATIONS=100 # Max iterations for orphaned app data scan
|
||||||
@@ -96,7 +96,6 @@ declare -a DEFAULT_WHITELIST_PATTERNS=(
|
|||||||
)
|
)
|
||||||
|
|
||||||
declare -a DEFAULT_OPTIMIZE_WHITELIST_PATTERNS=(
|
declare -a DEFAULT_OPTIMIZE_WHITELIST_PATTERNS=(
|
||||||
"check_brew_updates"
|
|
||||||
"check_brew_health"
|
"check_brew_health"
|
||||||
"check_touchid"
|
"check_touchid"
|
||||||
"check_git_config"
|
"check_git_config"
|
||||||
|
|||||||
@@ -156,8 +156,8 @@ get_optimize_whitelist_items() {
|
|||||||
cat << 'EOF'
|
cat << 'EOF'
|
||||||
macOS Firewall check|firewall|security_check
|
macOS Firewall check|firewall|security_check
|
||||||
Gatekeeper check|gatekeeper|security_check
|
Gatekeeper check|gatekeeper|security_check
|
||||||
Homebrew updates check|check_brew_updates|update_check
|
|
||||||
macOS system updates check|check_macos_updates|update_check
|
macOS system updates check|check_macos_updates|update_check
|
||||||
|
Mole updates check|check_mole_update|update_check
|
||||||
Homebrew health check (doctor)|check_brew_health|health_check
|
Homebrew health check (doctor)|check_brew_health|health_check
|
||||||
SIP status check|check_sip|security_check
|
SIP status check|check_sip|security_check
|
||||||
FileVault status check|check_filevault|security_check
|
FileVault status check|check_filevault|security_check
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ set -euo pipefail
|
|||||||
# MOLE_MAIL_AGE_DAYS: Minimum age in days for Mail attachments to be cleaned (default: 30)
|
# MOLE_MAIL_AGE_DAYS: Minimum age in days for Mail attachments to be cleaned (default: 30)
|
||||||
readonly MOLE_TM_THIN_TIMEOUT=180
|
readonly MOLE_TM_THIN_TIMEOUT=180
|
||||||
readonly MOLE_TM_THIN_VALUE=9999999999
|
readonly MOLE_TM_THIN_VALUE=9999999999
|
||||||
|
readonly MOLE_SQLITE_MAX_SIZE=104857600 # 100MB
|
||||||
|
|
||||||
# Helper function to get appropriate icon and color for dry-run mode
|
# Helper function to get appropriate icon and color for dry-run mode
|
||||||
opt_msg() {
|
opt_msg() {
|
||||||
@@ -36,6 +37,60 @@ run_launchctl_unload() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
needs_permissions_repair() {
|
||||||
|
local owner
|
||||||
|
owner=$(stat -f %Su "$HOME" 2> /dev/null || echo "")
|
||||||
|
if [[ -n "$owner" && "$owner" != "$USER" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local -a paths=(
|
||||||
|
"$HOME"
|
||||||
|
"$HOME/Library"
|
||||||
|
"$HOME/Library/Preferences"
|
||||||
|
)
|
||||||
|
local path
|
||||||
|
for path in "${paths[@]}"; do
|
||||||
|
if [[ -e "$path" && ! -w "$path" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
has_bluetooth_hid_connected() {
|
||||||
|
local bt_report
|
||||||
|
bt_report=$(system_profiler SPBluetoothDataType 2> /dev/null || echo "")
|
||||||
|
if ! echo "$bt_report" | grep -q "Connected: Yes"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if echo "$bt_report" | grep -Eiq "Keyboard|Trackpad|Mouse|HID"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
is_ac_power() {
|
||||||
|
pmset -g batt 2> /dev/null | grep -q "AC Power"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_memory_pressure_high() {
|
||||||
|
if ! command -v memory_pressure > /dev/null 2>&1; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local mp_output
|
||||||
|
mp_output=$(memory_pressure -Q 2> /dev/null || echo "")
|
||||||
|
if echo "$mp_output" | grep -Eiq "warning|critical"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
flush_dns_cache() {
|
flush_dns_cache() {
|
||||||
# Skip actual flush in dry-run mode
|
# Skip actual flush in dry-run mode
|
||||||
if [[ "${MOLE_DRY_RUN:-0}" == "1" ]]; then
|
if [[ "${MOLE_DRY_RUN:-0}" == "1" ]]; then
|
||||||
@@ -148,7 +203,7 @@ opt_network_optimization() {
|
|||||||
# Compresses and optimizes SQLite databases for Mail, Messages, Safari
|
# Compresses and optimizes SQLite databases for Mail, Messages, Safari
|
||||||
opt_sqlite_vacuum() {
|
opt_sqlite_vacuum() {
|
||||||
if ! command -v sqlite3 > /dev/null 2>&1; then
|
if ! command -v sqlite3 > /dev/null 2>&1; then
|
||||||
echo -e " ${GRAY}-${NC} sqlite3 not available, skipping database optimization"
|
echo -e " ${GRAY}-${NC} Database optimization already optimal (sqlite3 unavailable)"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -166,6 +221,13 @@ opt_sqlite_vacuum() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
local spinner_started="false"
|
||||||
|
if [[ "${MOLE_DRY_RUN:-0}" != "1" && -t 1 ]]; then
|
||||||
|
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Optimizing databases..."
|
||||||
|
spinner_started="true"
|
||||||
|
trap '[[ "${spinner_started:-false}" == "true" ]] && stop_inline_spinner' RETURN
|
||||||
|
fi
|
||||||
|
|
||||||
local -a db_paths=(
|
local -a db_paths=(
|
||||||
"$HOME/Library/Mail/V*/MailData/Envelope Index*"
|
"$HOME/Library/Mail/V*/MailData/Envelope Index*"
|
||||||
"$HOME/Library/Messages/chat.db"
|
"$HOME/Library/Messages/chat.db"
|
||||||
@@ -176,6 +238,7 @@ opt_sqlite_vacuum() {
|
|||||||
local vacuumed=0
|
local vacuumed=0
|
||||||
local timed_out=0
|
local timed_out=0
|
||||||
local failed=0
|
local failed=0
|
||||||
|
local skipped=0
|
||||||
|
|
||||||
for pattern in "${db_paths[@]}"; do
|
for pattern in "${db_paths[@]}"; do
|
||||||
while IFS= read -r db_file; do
|
while IFS= read -r db_file; do
|
||||||
@@ -190,6 +253,43 @@ opt_sqlite_vacuum() {
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Safety check 1: Skip large databases (>100MB) to avoid timeouts
|
||||||
|
local file_size
|
||||||
|
file_size=$(get_file_size "$db_file")
|
||||||
|
if [[ "$file_size" -gt "$MOLE_SQLITE_MAX_SIZE" ]]; then
|
||||||
|
((skipped++))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Safety check 2: Skip if freelist is tiny (already compact)
|
||||||
|
local page_info=""
|
||||||
|
page_info=$(run_with_timeout 5 sqlite3 "$db_file" "PRAGMA page_count; PRAGMA freelist_count;" 2> /dev/null || echo "")
|
||||||
|
local page_count=""
|
||||||
|
local freelist_count=""
|
||||||
|
page_count=$(echo "$page_info" | awk 'NR==1 {print $1}' 2> /dev/null || echo "")
|
||||||
|
freelist_count=$(echo "$page_info" | awk 'NR==2 {print $1}' 2> /dev/null || echo "")
|
||||||
|
if [[ "$page_count" =~ ^[0-9]+$ && "$freelist_count" =~ ^[0-9]+$ && "$page_count" -gt 0 ]]; then
|
||||||
|
if (( freelist_count * 100 < page_count * 5 )); then
|
||||||
|
((skipped++))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Safety check 3: Verify database integrity before VACUUM
|
||||||
|
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
|
||||||
|
local integrity_check=""
|
||||||
|
set +e
|
||||||
|
integrity_check=$(run_with_timeout 10 sqlite3 "$db_file" "PRAGMA integrity_check;" 2> /dev/null)
|
||||||
|
local integrity_status=$?
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Skip if integrity check failed or database is corrupted
|
||||||
|
if [[ $integrity_status -ne 0 ]] || ! echo "$integrity_check" | grep -q "ok"; then
|
||||||
|
((skipped++))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Try to vacuum (skip in dry-run mode)
|
# Try to vacuum (skip in dry-run mode)
|
||||||
local exit_code=0
|
local exit_code=0
|
||||||
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
|
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
|
||||||
@@ -220,6 +320,10 @@ opt_sqlite_vacuum() {
|
|||||||
echo -e " ${YELLOW}!${NC} Database optimization incomplete"
|
echo -e " ${YELLOW}!${NC} Database optimization incomplete"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ $skipped -gt 0 ]]; then
|
||||||
|
echo -e " ${GRAY}Already optimal for $skipped databases (size or integrity limits)${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ $timed_out -gt 0 ]]; then
|
if [[ $timed_out -gt 0 ]]; then
|
||||||
echo -e " ${YELLOW}!${NC} Timed out on $timed_out databases"
|
echo -e " ${YELLOW}!${NC} Timed out on $timed_out databases"
|
||||||
fi
|
fi
|
||||||
@@ -295,322 +399,209 @@ opt_font_cache_rebuild() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Startup items cleanup
|
# Removed high-risk optimizations:
|
||||||
# Removes broken LaunchAgents and analyzes startup performance impact
|
# - opt_startup_items_cleanup: Risk of deleting legitimate app helpers
|
||||||
opt_startup_items_cleanup() {
|
# - opt_dyld_cache_update: Low benefit, time-consuming, auto-managed by macOS
|
||||||
# Check whitelist (respects 'Login items check' setting)
|
# - opt_system_services_refresh: Risk of data loss when killing system services
|
||||||
if command -v is_whitelisted > /dev/null && is_whitelisted "check_login_items"; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
local -a scan_dirs=(
|
# Memory pressure relief
|
||||||
"$HOME/Library/LaunchAgents"
|
# Clears inactive memory and disk cache to improve system responsiveness
|
||||||
"$HOME/Library/LaunchDaemons"
|
opt_memory_pressure_relief() {
|
||||||
"/Library/LaunchAgents"
|
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
|
||||||
"/Library/LaunchDaemons"
|
if ! is_memory_pressure_high; then
|
||||||
)
|
opt_msg "Memory pressure already optimal"
|
||||||
|
|
||||||
local broken_count=0
|
|
||||||
local total_count=0
|
|
||||||
local processed_files=0
|
|
||||||
|
|
||||||
for dir in "${scan_dirs[@]}"; do
|
|
||||||
[[ ! -d "$dir" ]] && continue
|
|
||||||
|
|
||||||
# Check if we need sudo for this directory
|
|
||||||
local need_sudo=false
|
|
||||||
if [[ "$dir" == "/Library"* ]]; then
|
|
||||||
need_sudo=true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Process plists
|
|
||||||
local find_cmd=(find)
|
|
||||||
if [[ "$need_sudo" == "true" ]]; then
|
|
||||||
find_cmd=(sudo find)
|
|
||||||
fi
|
|
||||||
|
|
||||||
while IFS= read -r plist_file; do
|
|
||||||
# Verify file exists (unless in test mode)
|
|
||||||
if [[ -z "${MO_TEST_MODE:-}" && ! -f "$plist_file" ]]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
((total_count++))
|
|
||||||
|
|
||||||
# Skip system items (com.apple.*)
|
|
||||||
local filename=$(basename "$plist_file")
|
|
||||||
[[ "$filename" == com.apple.* ]] && continue
|
|
||||||
|
|
||||||
# Check if plist is valid (use sudo for system dirs)
|
|
||||||
local lint_output=""
|
|
||||||
local lint_status=0
|
|
||||||
local errexit_was_set=0
|
|
||||||
[[ $- == *e* ]] && errexit_was_set=1
|
|
||||||
set +e
|
|
||||||
if [[ "$need_sudo" == "true" ]]; then
|
|
||||||
lint_output=$(sudo plutil -lint "$plist_file" 2>&1)
|
|
||||||
lint_status=$?
|
|
||||||
else
|
|
||||||
lint_output=$(plutil -lint "$plist_file" 2>&1)
|
|
||||||
lint_status=$?
|
|
||||||
fi
|
|
||||||
if [[ $errexit_was_set -eq 1 ]]; then
|
|
||||||
set -e
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $lint_status -ne 0 ]]; then
|
|
||||||
# Skip if lint failed due to permissions or transient read errors
|
|
||||||
if echo "$lint_output" | grep -qi "permission\\|operation not permitted\\|not permitted"; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Invalid plist - remove it
|
|
||||||
if command -v should_protect_path > /dev/null && should_protect_path "$plist_file"; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$need_sudo" == "true" ]]; then
|
|
||||||
run_launchctl_unload "$plist_file" "$need_sudo"
|
|
||||||
if safe_sudo_remove "$plist_file"; then
|
|
||||||
((broken_count++))
|
|
||||||
else
|
|
||||||
echo -e " ${YELLOW}!${NC} Failed to remove (sudo) $plist_file"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
run_launchctl_unload "$plist_file" "$need_sudo"
|
|
||||||
if safe_remove "$plist_file" true; then
|
|
||||||
((broken_count++))
|
|
||||||
else
|
|
||||||
echo -e " ${YELLOW}!${NC} Failed to remove $plist_file"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract program path
|
|
||||||
local program=""
|
|
||||||
program=$(plutil -extract Program raw "$plist_file" 2> /dev/null || echo "")
|
|
||||||
|
|
||||||
if [[ -z "$program" ]]; then
|
|
||||||
program=$(plutil -extract ProgramArguments.0 raw "$plist_file" 2> /dev/null || echo "")
|
|
||||||
fi
|
|
||||||
|
|
||||||
program="${program/#\~/$HOME}"
|
|
||||||
|
|
||||||
# Skip paths with variables or non-absolute program definitions
|
|
||||||
if [[ "$program" == *'$'* || "$program" != /* ]]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
# Check for orphaned privileged helpers (app uninstalled but helper remains)
|
|
||||||
local associated_bundle=""
|
|
||||||
associated_bundle=$(plutil -extract AssociatedBundleIdentifiers.0 raw "$plist_file" 2> /dev/null || echo "")
|
|
||||||
|
|
||||||
if [[ -n "$associated_bundle" ]]; then
|
|
||||||
# Check if the associated app exists
|
|
||||||
local app_path=""
|
|
||||||
# First check standard locations
|
|
||||||
if [[ -d "/Applications/$associated_bundle.app" ]]; then
|
|
||||||
app_path="/Applications/$associated_bundle.app"
|
|
||||||
elif [[ -d "$HOME/Applications/$associated_bundle.app" ]]; then
|
|
||||||
app_path="$HOME/Applications/$associated_bundle.app"
|
|
||||||
else
|
|
||||||
# Try extracting app name from bundle ID (e.g., com.dropbox.Dropbox -> Dropbox)
|
|
||||||
local app_name="${associated_bundle##*.}"
|
|
||||||
if [[ -n "$app_name" && -d "/Applications/$app_name.app" ]]; then
|
|
||||||
app_path="/Applications/$app_name.app"
|
|
||||||
elif [[ -n "$app_name" && -d "$HOME/Applications/$app_name.app" ]]; then
|
|
||||||
app_path="$HOME/Applications/$app_name.app"
|
|
||||||
else
|
|
||||||
# Fallback to mdfind (slower but comprehensive, with 10s timeout)
|
|
||||||
app_path=$(run_with_timeout 10 mdfind "kMDItemCFBundleIdentifier == '$associated_bundle'" 2> /dev/null | head -1 || echo "")
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# CRITICAL FIX: Only consider it orphaned if BOTH conditions are true:
|
|
||||||
# 1. Associated app is not found
|
|
||||||
# 2. The program/executable itself also doesn't exist
|
|
||||||
if [[ -z "$app_path" ]]; then
|
|
||||||
if command -v should_protect_path > /dev/null && should_protect_path "$plist_file"; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# CRITICAL: Check if the program itself exists (reuse already extracted program path)
|
|
||||||
# If the executable exists, this is NOT an orphan - it's a valid helper
|
|
||||||
# whose app we just can't find (maybe mdfind indexing issue, non-standard location, etc.)
|
|
||||||
if [[ -n "$program" && -e "$program" ]]; then
|
|
||||||
debug_log "Keeping LaunchAgent (program exists): $plist_file -> $program"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Double check we are not deleting system files
|
|
||||||
if [[ "$program" == /System/* ||
|
|
||||||
"$program" == /usr/lib/* ||
|
|
||||||
"$program" == /usr/bin/* ||
|
|
||||||
"$program" == /usr/sbin/* ||
|
|
||||||
"$program" == /Library/Apple/* ]]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Only delete if BOTH app and program are missing
|
|
||||||
debug_log "Removing orphaned helper (app not found, program missing): $plist_file"
|
|
||||||
|
|
||||||
if [[ "$need_sudo" == "true" ]]; then
|
|
||||||
run_launchctl_unload "$plist_file" "$need_sudo"
|
|
||||||
# remove the plist
|
|
||||||
safe_sudo_remove "$plist_file"
|
|
||||||
|
|
||||||
# The program doesn't exist (verified above), so no need to remove it
|
|
||||||
((broken_count++))
|
|
||||||
opt_msg "Removed orphaned helper: $(basename "$plist_file" .plist)"
|
|
||||||
else
|
|
||||||
run_launchctl_unload "$plist_file" "$need_sudo"
|
|
||||||
safe_remove "$plist_file" true
|
|
||||||
((broken_count++))
|
|
||||||
opt_msg "Removed orphaned helper: $(basename "$plist_file" .plist)"
|
|
||||||
fi
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If program doesn't exist, remove the launch agent/daemon
|
|
||||||
if [[ -n "$program" && ! -e "$program" ]]; then
|
|
||||||
if command -v should_protect_path > /dev/null && should_protect_path "$plist_file"; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$need_sudo" == "true" ]]; then
|
|
||||||
run_launchctl_unload "$plist_file" "$need_sudo"
|
|
||||||
if safe_sudo_remove "$plist_file"; then
|
|
||||||
((broken_count++))
|
|
||||||
else
|
|
||||||
echo -e " ${YELLOW}!${NC} Failed to remove (sudo) $plist_file"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
run_launchctl_unload "$plist_file" "$need_sudo"
|
|
||||||
if safe_remove "$plist_file" true; then
|
|
||||||
((broken_count++))
|
|
||||||
else
|
|
||||||
echo -e " ${YELLOW}!${NC} Failed to remove $plist_file"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done < <("${find_cmd[@]}" "$dir" -maxdepth 1 -name "*.plist" -type f 2> /dev/null || true)
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ $broken_count -gt 0 ]]; then
|
|
||||||
opt_msg "Removed $broken_count broken startup items"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $total_count -gt 0 ]]; then
|
|
||||||
opt_msg "Verified $total_count startup items"
|
|
||||||
else
|
|
||||||
opt_msg "No startup items found"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# dyld shared cache update
|
|
||||||
# Rebuilds dynamic linker shared cache to improve app launch speed
|
|
||||||
# Only beneficial after new app installations or system updates
|
|
||||||
opt_dyld_cache_update() {
|
|
||||||
# Check if command exists
|
|
||||||
if ! command -v update_dyld_shared_cache > /dev/null 2>&1; then
|
|
||||||
echo -e " ${GRAY}-${NC} dyld cache (automatically managed by macOS)"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Skip if dyld cache was already rebuilt recently (within 24 hours)
|
|
||||||
local dyld_cache_path="/var/db/dyld/dyld_shared_cache_$(uname -m)"
|
|
||||||
if [[ -e "$dyld_cache_path" ]]; then
|
|
||||||
local cache_mtime
|
|
||||||
cache_mtime=$(stat -f "%m" "$dyld_cache_path" 2> /dev/null || echo "0")
|
|
||||||
local current_time
|
|
||||||
current_time=$(date +%s)
|
|
||||||
local time_diff=$((current_time - cache_mtime))
|
|
||||||
local one_day_seconds=$((24 * 3600))
|
|
||||||
|
|
||||||
if [[ $time_diff -lt $one_day_seconds ]]; then
|
|
||||||
opt_msg "dyld shared cache already up-to-date"
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -t 1 ]]; then
|
if sudo purge > /dev/null 2>&1; then
|
||||||
start_inline_spinner "Rebuilding dyld cache..."
|
opt_msg "Inactive memory released"
|
||||||
fi
|
opt_msg "System responsiveness improved"
|
||||||
|
else
|
||||||
local success=false
|
echo -e " ${YELLOW}!${NC} Failed to release memory pressure"
|
||||||
local exit_code=0
|
|
||||||
|
|
||||||
# Skip actual rebuild in dry-run mode
|
|
||||||
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
|
|
||||||
# This can take 1-2 minutes on some systems (180 second timeout)
|
|
||||||
set +e
|
|
||||||
run_with_timeout 180 sudo update_dyld_shared_cache -force > /dev/null 2>&1
|
|
||||||
exit_code=$?
|
|
||||||
set -e
|
|
||||||
if [[ $exit_code -eq 0 ]]; then
|
|
||||||
success=true
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
success=true # Assume success in dry-run mode
|
opt_msg "Inactive memory released"
|
||||||
exit_code=0
|
opt_msg "System responsiveness improved"
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -t 1 ]]; then
|
|
||||||
stop_inline_spinner
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$success" == "true" ]]; then
|
|
||||||
opt_msg "dyld shared cache rebuilt"
|
|
||||||
opt_msg "App launch speed improved"
|
|
||||||
elif [[ $exit_code -eq 124 ]]; then
|
|
||||||
echo -e " ${YELLOW}!${NC} dyld cache update timed out"
|
|
||||||
else
|
|
||||||
echo -e " ${GRAY}-${NC} dyld cache update skipped (automatically managed)"
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# System services refresh
|
# Network stack optimization
|
||||||
# Restarts system services to apply cache and configuration changes
|
# Flushes routing table and ARP cache to resolve network issues
|
||||||
opt_system_services_refresh() {
|
opt_network_stack_optimize() {
|
||||||
local -a services=(
|
local success=0
|
||||||
"cfprefsd:Preferences"
|
|
||||||
"lsd:LaunchServices"
|
|
||||||
"iconservicesagent:Icon Services"
|
|
||||||
"fontd:Font Server"
|
|
||||||
)
|
|
||||||
local -a restarted_services=()
|
|
||||||
|
|
||||||
# Skip actual service restarts in dry-run mode
|
|
||||||
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
|
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
|
||||||
for service_entry in "${services[@]}"; do
|
local route_ok=true
|
||||||
IFS=':' read -r process_name display_name <<< "$service_entry"
|
local dns_ok=true
|
||||||
|
|
||||||
# Special handling for cfprefsd (use -HUP instead of normal kill)
|
if ! route -n get default > /dev/null 2>&1; then
|
||||||
if [[ "$process_name" == "cfprefsd" ]]; then
|
route_ok=false
|
||||||
if killall -HUP "$process_name" 2> /dev/null; then
|
fi
|
||||||
restarted_services+=("$display_name")
|
if ! dscacheutil -q host -a name "example.com" > /dev/null 2>&1; then
|
||||||
fi
|
dns_ok=false
|
||||||
else
|
fi
|
||||||
if killall "$process_name" 2> /dev/null; then
|
|
||||||
restarted_services+=("$display_name")
|
if [[ "$route_ok" == "true" && "$dns_ok" == "true" ]]; then
|
||||||
fi
|
opt_msg "Network stack already optimal"
|
||||||
fi
|
return 0
|
||||||
done
|
fi
|
||||||
|
|
||||||
|
# Flush routing table
|
||||||
|
if sudo route -n flush > /dev/null 2>&1; then
|
||||||
|
((success++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clear ARP cache
|
||||||
|
if sudo arp -a -d > /dev/null 2>&1; then
|
||||||
|
((success++))
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
# In dry-run mode, show all services that would be restarted
|
success=2
|
||||||
for service_entry in "${services[@]}"; do
|
|
||||||
IFS=':' read -r _ display_name <<< "$service_entry"
|
|
||||||
restarted_services+=("$display_name")
|
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ${#restarted_services[@]} -gt 0 ]]; then
|
if [[ $success -gt 0 ]]; then
|
||||||
opt_msg "Refreshed ${#restarted_services[@]} system services"
|
opt_msg "Network routing table refreshed"
|
||||||
for service in "${restarted_services[@]}"; do
|
opt_msg "ARP cache cleared"
|
||||||
echo -e " • $service"
|
|
||||||
done
|
|
||||||
else
|
else
|
||||||
opt_msg "System services already optimal"
|
echo -e " ${YELLOW}!${NC} Failed to optimize network stack"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Disk permissions repair
|
||||||
|
# Fixes user home directory permission issues
|
||||||
|
opt_disk_permissions_repair() {
|
||||||
|
local user_id
|
||||||
|
user_id=$(id -u)
|
||||||
|
|
||||||
|
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
|
||||||
|
if ! needs_permissions_repair; then
|
||||||
|
opt_msg "User directory permissions already optimal"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
start_inline_spinner "Repairing disk permissions..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
local success=false
|
||||||
|
if sudo diskutil resetUserPermissions / "$user_id" > /dev/null 2>&1; then
|
||||||
|
success=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
stop_inline_spinner
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$success" == "true" ]]; then
|
||||||
|
opt_msg "User directory permissions repaired"
|
||||||
|
opt_msg "File access issues resolved"
|
||||||
|
else
|
||||||
|
echo -e " ${YELLOW}!${NC} Failed to repair permissions (may not be needed)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
opt_msg "User directory permissions repaired"
|
||||||
|
opt_msg "File access issues resolved"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Bluetooth module reset
|
||||||
|
# Resets Bluetooth daemon to fix connectivity issues
|
||||||
|
# Only runs if no Bluetooth audio is playing
|
||||||
|
opt_bluetooth_reset() {
|
||||||
|
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
|
||||||
|
if has_bluetooth_hid_connected; then
|
||||||
|
opt_msg "Bluetooth already optimal"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if any audio is playing through Bluetooth
|
||||||
|
local bt_audio_active=false
|
||||||
|
|
||||||
|
# Check system audio output
|
||||||
|
if system_profiler SPBluetoothDataType 2>/dev/null | grep -q "Connected: Yes"; then
|
||||||
|
# Check if any audio/video apps are running that might be using Bluetooth
|
||||||
|
local -a media_apps=("Music" "Spotify" "VLC" "QuickTime Player" "TV" "Podcasts")
|
||||||
|
for app in "${media_apps[@]}"; do
|
||||||
|
if pgrep -x "$app" > /dev/null 2>&1; then
|
||||||
|
bt_audio_active=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$bt_audio_active" == "true" ]]; then
|
||||||
|
opt_msg "Bluetooth already optimal"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Safe to reset Bluetooth
|
||||||
|
if sudo pkill -TERM bluetoothd > /dev/null 2>&1; then
|
||||||
|
sleep 1
|
||||||
|
if pgrep -x bluetoothd > /dev/null 2>&1; then
|
||||||
|
sudo pkill -KILL bluetoothd > /dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
opt_msg "Bluetooth module restarted"
|
||||||
|
opt_msg "Connectivity issues resolved"
|
||||||
|
else
|
||||||
|
opt_msg "Bluetooth already optimal"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
opt_msg "Bluetooth module restarted"
|
||||||
|
opt_msg "Connectivity issues resolved"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Spotlight index optimization
|
||||||
|
# Rebuilds Spotlight index if search is slow or results are inaccurate
|
||||||
|
# Only runs if index is actually problematic
|
||||||
|
opt_spotlight_index_optimize() {
|
||||||
|
# Check if Spotlight indexing is disabled
|
||||||
|
local spotlight_status
|
||||||
|
spotlight_status=$(mdutil -s / 2> /dev/null || echo "")
|
||||||
|
|
||||||
|
if echo "$spotlight_status" | grep -qi "Indexing disabled"; then
|
||||||
|
echo -e " ${GRAY}${ICON_EMPTY}${NC} Spotlight indexing is disabled"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if indexing is currently running
|
||||||
|
if echo "$spotlight_status" | grep -qi "Indexing enabled" && ! echo "$spotlight_status" | grep -qi "Indexing and searching disabled"; then
|
||||||
|
# Check index health by testing search speed twice
|
||||||
|
local slow_count=0
|
||||||
|
local test_start test_end test_duration
|
||||||
|
for _ in 1 2; do
|
||||||
|
test_start=$(date +%s)
|
||||||
|
mdfind "kMDItemFSName == 'Applications'" > /dev/null 2>&1 || true
|
||||||
|
test_end=$(date +%s)
|
||||||
|
test_duration=$((test_end - test_start))
|
||||||
|
if [[ $test_duration -gt 3 ]]; then
|
||||||
|
((slow_count++))
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $slow_count -ge 2 ]]; then
|
||||||
|
if ! is_ac_power; then
|
||||||
|
opt_msg "Spotlight index already optimal"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
|
||||||
|
echo -e " ${BLUE}ℹ${NC} Spotlight search is slow, rebuilding index (may take 1-2 hours)"
|
||||||
|
if sudo mdutil -E / > /dev/null 2>&1; then
|
||||||
|
opt_msg "Spotlight index rebuild started"
|
||||||
|
echo -e " ${GRAY}Indexing will continue in background${NC}"
|
||||||
|
else
|
||||||
|
echo -e " ${YELLOW}!${NC} Failed to rebuild Spotlight index"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
opt_msg "Spotlight index rebuild started"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
opt_msg "Spotlight index already optimal"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
opt_msg "Spotlight index verified"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -661,10 +652,12 @@ execute_optimization() {
|
|||||||
sqlite_vacuum) opt_sqlite_vacuum ;;
|
sqlite_vacuum) opt_sqlite_vacuum ;;
|
||||||
launch_services_rebuild) opt_launch_services_rebuild ;;
|
launch_services_rebuild) opt_launch_services_rebuild ;;
|
||||||
font_cache_rebuild) opt_font_cache_rebuild ;;
|
font_cache_rebuild) opt_font_cache_rebuild ;;
|
||||||
startup_items_cleanup) opt_startup_items_cleanup ;;
|
|
||||||
dyld_cache_update) opt_dyld_cache_update ;;
|
|
||||||
system_services_refresh) opt_system_services_refresh ;;
|
|
||||||
dock_refresh) opt_dock_refresh ;;
|
dock_refresh) opt_dock_refresh ;;
|
||||||
|
memory_pressure_relief) opt_memory_pressure_relief ;;
|
||||||
|
network_stack_optimize) opt_network_stack_optimize ;;
|
||||||
|
disk_permissions_repair) opt_disk_permissions_repair ;;
|
||||||
|
bluetooth_reset) opt_bluetooth_reset ;;
|
||||||
|
spotlight_index_optimize) opt_spotlight_index_optimize ;;
|
||||||
*)
|
*)
|
||||||
echo -e "${YELLOW}${ICON_ERROR}${NC} Unknown action: $action"
|
echo -e "${YELLOW}${ICON_ERROR}${NC} Unknown action: $action"
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -451,7 +451,7 @@ EOF
|
|||||||
mkdir -p "$state_dir/com.example.app.savedState"
|
mkdir -p "$state_dir/com.example.app.savedState"
|
||||||
touch "$state_dir/com.example.app.savedState/data.plist"
|
touch "$state_dir/com.example.app.savedState/data.plist"
|
||||||
|
|
||||||
# Make the file old (8+ days) - MOLE_SAVED_STATE_AGE_DAYS defaults to 7
|
# Make the file old (31+ days) - MOLE_SAVED_STATE_AGE_DAYS now defaults to 30
|
||||||
touch -t 202301010000 "$state_dir/com.example.app.savedState/data.plist"
|
touch -t 202301010000 "$state_dir/com.example.app.savedState/data.plist"
|
||||||
|
|
||||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||||
@@ -687,143 +687,355 @@ EOF
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@test "opt_startup_items_cleanup scans system directories and uses sudo" {
|
# Removed tests for opt_startup_items_cleanup
|
||||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" MO_TEST_MODE=1 bash --noprofile --norc << 'EOF'
|
# This optimization was removed due to high risk of deleting legitimate app helpers
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Tests for new system optimizations (v1.16.3+)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
@test "opt_memory_pressure_relief skips when pressure is normal" {
|
||||||
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||||
|
|
||||||
# Mock sudo to track calls but allow find
|
# Mock memory_pressure to indicate normal pressure
|
||||||
sudo() {
|
memory_pressure() {
|
||||||
if [[ "$1" == "find" ]]; then
|
echo "System-wide memory free percentage: 50%"
|
||||||
shift
|
|
||||||
find "$@"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if [[ "$1" == "plutil" ]]; then
|
|
||||||
echo "Invalid plist"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
echo "sudo:$@"
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
export -f sudo
|
export -f memory_pressure
|
||||||
|
|
||||||
# Mock find to return dummy plists in system paths
|
opt_memory_pressure_relief
|
||||||
find() {
|
|
||||||
local dir="$1"
|
|
||||||
if [[ "$dir" == "/Library/LaunchDaemons" ]]; then
|
|
||||||
echo "/Library/LaunchDaemons/com.malware.plist"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
export -f find
|
|
||||||
|
|
||||||
# Mock plutil to fail linting (simulating broken plist)
|
|
||||||
plutil() {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
export -f plutil
|
|
||||||
|
|
||||||
test() {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
export -f test
|
|
||||||
|
|
||||||
# Mock safe_sudo_remove to succeed without file checks
|
|
||||||
safe_sudo_remove() {
|
|
||||||
echo "sudo:rm -rf $1"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
export -f safe_sudo_remove
|
|
||||||
|
|
||||||
opt_startup_items_cleanup
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
# Should attempt to unload with sudo
|
[[ "$output" == *"Memory pressure already optimal"* ]]
|
||||||
[[ "$output" == *"sudo:launchctl unload /Library/LaunchDaemons/com.malware.plist"* ]]
|
|
||||||
# Should attempt to remove with sudo
|
|
||||||
[[ "$output" == *"sudo:rm -rf /Library/LaunchDaemons/com.malware.plist"* ]]
|
|
||||||
[[ "$output" == *"Removed 1 broken startup items"* ]]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "opt_startup_items_cleanup removes orphaned helpers" {
|
@test "opt_memory_pressure_relief executes purge when pressure is high" {
|
||||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" MO_TEST_MODE=1 bash --noprofile --norc << 'EOF'
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||||
|
|
||||||
# Mock sudo to handle find/rm/pkill
|
# Mock memory_pressure to indicate high pressure
|
||||||
|
memory_pressure() {
|
||||||
|
echo "System-wide memory free percentage: warning"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
export -f memory_pressure
|
||||||
|
|
||||||
|
# Mock sudo purge
|
||||||
sudo() {
|
sudo() {
|
||||||
if [[ "$1" == "find" ]]; then
|
if [[ "$1" == "purge" ]]; then
|
||||||
shift
|
echo "purge:executed"
|
||||||
find "$@"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if [[ "$1" == "rm" ]]; then
|
|
||||||
echo "sudo:rm $@"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if [[ "$1" == "launchctl" ]]; then
|
|
||||||
echo "sudo:launchctl $@"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
echo "sudo:$@"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
export -f sudo
|
|
||||||
|
|
||||||
safe_sudo_remove() {
|
|
||||||
echo "sudo:rm -rf $1"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
export -f safe_sudo_remove
|
|
||||||
|
|
||||||
# Mock find
|
|
||||||
find() {
|
|
||||||
local dir="$1"
|
|
||||||
if [[ "$dir" == "/Library/LaunchDaemons" ]]; then
|
|
||||||
echo "/Library/LaunchDaemons/com.orphan.helper.plist"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
export -f find
|
|
||||||
|
|
||||||
# Mock plutil to return associated bundle
|
|
||||||
plutil() {
|
|
||||||
if [[ "$1" == "-lint" ]]; then return 0; fi # Lint passes
|
|
||||||
|
|
||||||
if [[ "$2" == "AssociatedBundleIdentifiers.0" ]]; then
|
|
||||||
echo "com.deleted.app"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$2" == "Program" ]]; then
|
|
||||||
echo "/Library/PrivilegedHelperTools/com.orphan.helper"
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
export -f plutil
|
export -f sudo
|
||||||
|
|
||||||
# Mock mdfind (return nothing -> app missing)
|
opt_memory_pressure_relief
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"Inactive memory released"* ]]
|
||||||
|
[[ "$output" == *"System responsiveness improved"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "opt_network_stack_optimize skips when network is healthy" {
|
||||||
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||||
|
|
||||||
|
# Mock route to indicate healthy routing
|
||||||
|
route() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
export -f route
|
||||||
|
|
||||||
|
# Mock dscacheutil to indicate healthy DNS
|
||||||
|
dscacheutil() {
|
||||||
|
echo "ip_address: 93.184.216.34"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
export -f dscacheutil
|
||||||
|
|
||||||
|
opt_network_stack_optimize
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"Network stack already optimal"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "opt_network_stack_optimize flushes when network has issues" {
|
||||||
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||||
|
|
||||||
|
# Mock route to fail (network issue)
|
||||||
|
route() {
|
||||||
|
if [[ "$2" == "get" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [[ "$1" == "-n" && "$2" == "flush" ]]; then
|
||||||
|
echo "route:flushed"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
export -f route
|
||||||
|
|
||||||
|
# Mock sudo
|
||||||
|
sudo() {
|
||||||
|
if [[ "$1" == "route" || "$1" == "arp" ]]; then
|
||||||
|
shift
|
||||||
|
route "$@" || arp "$@"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
export -f sudo
|
||||||
|
|
||||||
|
# Mock arp
|
||||||
|
arp() {
|
||||||
|
echo "arp:cleared"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
export -f arp
|
||||||
|
|
||||||
|
# Mock dscacheutil
|
||||||
|
dscacheutil() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
export -f dscacheutil
|
||||||
|
|
||||||
|
opt_network_stack_optimize
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"Network routing table refreshed"* ]]
|
||||||
|
[[ "$output" == *"ARP cache cleared"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "opt_disk_permissions_repair skips when permissions are fine" {
|
||||||
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||||
|
|
||||||
|
# Mock stat to return correct owner
|
||||||
|
stat() {
|
||||||
|
if [[ "$2" == "%Su" ]]; then
|
||||||
|
echo "$USER"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
command stat "$@"
|
||||||
|
}
|
||||||
|
export -f stat
|
||||||
|
|
||||||
|
# Mock test to indicate directories are writable
|
||||||
|
test() {
|
||||||
|
if [[ "$1" == "-e" || "$1" == "-w" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
command test "$@"
|
||||||
|
}
|
||||||
|
export -f test
|
||||||
|
|
||||||
|
opt_disk_permissions_repair
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"User directory permissions already optimal"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "opt_disk_permissions_repair calls diskutil when needed" {
|
||||||
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||||
|
|
||||||
|
# Mock stat to return wrong owner
|
||||||
|
stat() {
|
||||||
|
if [[ "$2" == "%Su" ]]; then
|
||||||
|
echo "root"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
command stat "$@"
|
||||||
|
}
|
||||||
|
export -f stat
|
||||||
|
|
||||||
|
# Mock sudo diskutil
|
||||||
|
sudo() {
|
||||||
|
if [[ "$1" == "diskutil" && "$2" == "resetUserPermissions" ]]; then
|
||||||
|
echo "diskutil:resetUserPermissions"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
export -f sudo
|
||||||
|
|
||||||
|
id() {
|
||||||
|
echo "501"
|
||||||
|
}
|
||||||
|
export -f id
|
||||||
|
|
||||||
|
start_inline_spinner() { :; }
|
||||||
|
stop_inline_spinner() { :; }
|
||||||
|
export -f start_inline_spinner stop_inline_spinner
|
||||||
|
|
||||||
|
opt_disk_permissions_repair
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"User directory permissions repaired"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "opt_bluetooth_reset skips when HID device is connected" {
|
||||||
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||||
|
|
||||||
|
# Mock system_profiler to indicate Bluetooth keyboard connected
|
||||||
|
system_profiler() {
|
||||||
|
cat << 'PROFILER_OUT'
|
||||||
|
Bluetooth:
|
||||||
|
Apple Magic Keyboard:
|
||||||
|
Connected: Yes
|
||||||
|
Type: Keyboard
|
||||||
|
PROFILER_OUT
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
export -f system_profiler
|
||||||
|
|
||||||
|
opt_bluetooth_reset
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"Bluetooth already optimal"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "opt_bluetooth_reset skips when media apps are running" {
|
||||||
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||||
|
|
||||||
|
# Mock system_profiler to indicate Bluetooth headphones (no HID)
|
||||||
|
system_profiler() {
|
||||||
|
cat << 'PROFILER_OUT'
|
||||||
|
Bluetooth:
|
||||||
|
AirPods Pro:
|
||||||
|
Connected: Yes
|
||||||
|
Type: Headphones
|
||||||
|
PROFILER_OUT
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
export -f system_profiler
|
||||||
|
|
||||||
|
# Mock pgrep to indicate Spotify is running
|
||||||
|
pgrep() {
|
||||||
|
if [[ "$2" == "Spotify" ]]; then
|
||||||
|
echo "12345"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
export -f pgrep
|
||||||
|
|
||||||
|
opt_bluetooth_reset
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"Bluetooth already optimal"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "opt_bluetooth_reset restarts when safe" {
|
||||||
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||||
|
|
||||||
|
# Mock system_profiler (no HID devices, just audio)
|
||||||
|
system_profiler() {
|
||||||
|
cat << 'PROFILER_OUT'
|
||||||
|
Bluetooth:
|
||||||
|
AirPods:
|
||||||
|
Connected: Yes
|
||||||
|
Type: Audio
|
||||||
|
PROFILER_OUT
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
export -f system_profiler
|
||||||
|
|
||||||
|
# Mock pgrep (no media apps running)
|
||||||
|
pgrep() {
|
||||||
|
if [[ "$2" == "bluetoothd" ]]; then
|
||||||
|
return 1 # bluetoothd not running after TERM
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
export -f pgrep
|
||||||
|
|
||||||
|
# Mock sudo pkill
|
||||||
|
sudo() {
|
||||||
|
if [[ "$1" == "pkill" ]]; then
|
||||||
|
echo "pkill:bluetoothd:$2"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
export -f sudo
|
||||||
|
|
||||||
|
sleep() { :; }
|
||||||
|
export -f sleep
|
||||||
|
|
||||||
|
opt_bluetooth_reset
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"Bluetooth module restarted"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "opt_spotlight_index_optimize skips when search is fast" {
|
||||||
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
|
||||||
|
|
||||||
|
# Mock mdutil
|
||||||
|
mdutil() {
|
||||||
|
if [[ "$1" == "-s" ]]; then
|
||||||
|
echo "Indexing enabled."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
export -f mdutil
|
||||||
|
|
||||||
|
# Mock mdfind (fast search)
|
||||||
mdfind() {
|
mdfind() {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
export -f mdfind
|
export -f mdfind
|
||||||
|
|
||||||
# Mock directory check (standard app paths don't exist)
|
# Mock date to simulate fast search (< 3 seconds)
|
||||||
test() {
|
date() {
|
||||||
return 1
|
echo "1000"
|
||||||
}
|
}
|
||||||
export -f test
|
export -f date
|
||||||
|
|
||||||
opt_startup_items_cleanup
|
opt_spotlight_index_optimize
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
[[ "$output" == *"sudo:rm -rf /Library/LaunchDaemons/com.orphan.helper.plist"* ]]
|
[[ "$output" == *"Spotlight index already optimal"* ]]
|
||||||
[[ "$output" == *"sudo:rm -rf /Library/PrivilegedHelperTools/com.orphan.helper"* ]]
|
|
||||||
[[ "$output" == *"Removed orphaned helper: com.orphan.helper"* ]]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user