1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-04 12:41:46 +00:00

feat: Add new system optimizations, refine existing tasks with safety checks, and update whitelisting options.

This commit is contained in:
Tw93
2025-12-30 15:44:52 +08:00
parent 08aec02283
commit f0c9683048
6 changed files with 632 additions and 417 deletions

View File

@@ -283,6 +283,8 @@ check_macos_update() {
}
check_mole_update() {
if command -v is_whitelisted > /dev/null && is_whitelisted "check_mole_update"; then return; fi
# Check if Mole has updates
# Auto-detect version from mole main script
local current_version

View File

@@ -125,7 +125,7 @@ EOF
# Core optimizations (safe and valuable)
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+=('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+=('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+=('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+=('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')
# 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
local first=true
for item in "${items[@]}"; do

View File

@@ -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_LOG_AGE_DAYS=7 # Log 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_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
@@ -96,7 +96,6 @@ declare -a DEFAULT_WHITELIST_PATTERNS=(
)
declare -a DEFAULT_OPTIMIZE_WHITELIST_PATTERNS=(
"check_brew_updates"
"check_brew_health"
"check_touchid"
"check_git_config"

View File

@@ -156,8 +156,8 @@ get_optimize_whitelist_items() {
cat << 'EOF'
macOS Firewall check|firewall|security_check
Gatekeeper check|gatekeeper|security_check
Homebrew updates check|check_brew_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
SIP status check|check_sip|security_check
FileVault status check|check_filevault|security_check

View File

@@ -10,6 +10,7 @@ set -euo pipefail
# 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_VALUE=9999999999
readonly MOLE_SQLITE_MAX_SIZE=104857600 # 100MB
# Helper function to get appropriate icon and color for dry-run mode
opt_msg() {
@@ -36,6 +37,60 @@ run_launchctl_unload() {
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() {
# Skip actual flush in dry-run mode
if [[ "${MOLE_DRY_RUN:-0}" == "1" ]]; then
@@ -148,7 +203,7 @@ opt_network_optimization() {
# Compresses and optimizes SQLite databases for Mail, Messages, Safari
opt_sqlite_vacuum() {
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
fi
@@ -166,6 +221,13 @@ opt_sqlite_vacuum() {
return 0
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=(
"$HOME/Library/Mail/V*/MailData/Envelope Index*"
"$HOME/Library/Messages/chat.db"
@@ -176,6 +238,7 @@ opt_sqlite_vacuum() {
local vacuumed=0
local timed_out=0
local failed=0
local skipped=0
for pattern in "${db_paths[@]}"; do
while IFS= read -r db_file; do
@@ -190,6 +253,43 @@ opt_sqlite_vacuum() {
continue
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)
local exit_code=0
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
@@ -220,6 +320,10 @@ opt_sqlite_vacuum() {
echo -e " ${YELLOW}!${NC} Database optimization incomplete"
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
echo -e " ${YELLOW}!${NC} Timed out on $timed_out databases"
fi
@@ -295,322 +399,209 @@ opt_font_cache_rebuild() {
fi
}
# Startup items cleanup
# Removes broken LaunchAgents and analyzes startup performance impact
opt_startup_items_cleanup() {
# Check whitelist (respects 'Login items check' setting)
if command -v is_whitelisted > /dev/null && is_whitelisted "check_login_items"; then
return 0
fi
# Removed high-risk optimizations:
# - opt_startup_items_cleanup: Risk of deleting legitimate app helpers
# - opt_dyld_cache_update: Low benefit, time-consuming, auto-managed by macOS
# - opt_system_services_refresh: Risk of data loss when killing system services
local -a scan_dirs=(
"$HOME/Library/LaunchAgents"
"$HOME/Library/LaunchDaemons"
"/Library/LaunchAgents"
"/Library/LaunchDaemons"
)
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"
# Memory pressure relief
# Clears inactive memory and disk cache to improve system responsiveness
opt_memory_pressure_relief() {
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
if ! is_memory_pressure_high; then
opt_msg "Memory pressure already optimal"
return 0
fi
fi
if [[ -t 1 ]]; then
start_inline_spinner "Rebuilding dyld cache..."
fi
local success=false
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
if sudo purge > /dev/null 2>&1; then
opt_msg "Inactive memory released"
opt_msg "System responsiveness improved"
else
echo -e " ${YELLOW}!${NC} Failed to release memory pressure"
fi
else
success=true # Assume success in dry-run mode
exit_code=0
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)"
opt_msg "Inactive memory released"
opt_msg "System responsiveness improved"
fi
}
# System services refresh
# Restarts system services to apply cache and configuration changes
opt_system_services_refresh() {
local -a services=(
"cfprefsd:Preferences"
"lsd:LaunchServices"
"iconservicesagent:Icon Services"
"fontd:Font Server"
)
local -a restarted_services=()
# Network stack optimization
# Flushes routing table and ARP cache to resolve network issues
opt_network_stack_optimize() {
local success=0
# Skip actual service restarts in dry-run mode
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
for service_entry in "${services[@]}"; do
IFS=':' read -r process_name display_name <<< "$service_entry"
local route_ok=true
local dns_ok=true
# Special handling for cfprefsd (use -HUP instead of normal kill)
if [[ "$process_name" == "cfprefsd" ]]; then
if killall -HUP "$process_name" 2> /dev/null; then
restarted_services+=("$display_name")
fi
else
if killall "$process_name" 2> /dev/null; then
restarted_services+=("$display_name")
fi
fi
done
if ! route -n get default > /dev/null 2>&1; then
route_ok=false
fi
if ! dscacheutil -q host -a name "example.com" > /dev/null 2>&1; then
dns_ok=false
fi
if [[ "$route_ok" == "true" && "$dns_ok" == "true" ]]; then
opt_msg "Network stack already optimal"
return 0
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
# In dry-run mode, show all services that would be restarted
for service_entry in "${services[@]}"; do
IFS=':' read -r _ display_name <<< "$service_entry"
restarted_services+=("$display_name")
done
success=2
fi
if [[ ${#restarted_services[@]} -gt 0 ]]; then
opt_msg "Refreshed ${#restarted_services[@]} system services"
for service in "${restarted_services[@]}"; do
echo -e "$service"
done
if [[ $success -gt 0 ]]; then
opt_msg "Network routing table refreshed"
opt_msg "ARP cache cleared"
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
}
@@ -661,10 +652,12 @@ execute_optimization() {
sqlite_vacuum) opt_sqlite_vacuum ;;
launch_services_rebuild) opt_launch_services_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 ;;
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"
return 1

View File

@@ -451,7 +451,7 @@ EOF
mkdir -p "$state_dir/com.example.app.savedState"
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"
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" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" MO_TEST_MODE=1 bash --noprofile --norc << 'EOF'
# Removed tests for opt_startup_items_cleanup
# 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
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
# Mock sudo to track calls but allow find
sudo() {
if [[ "$1" == "find" ]]; then
shift
find "$@"
return 0
fi
if [[ "$1" == "plutil" ]]; then
echo "Invalid plist"
return 1
fi
echo "sudo:$@"
# Mock memory_pressure to indicate normal pressure
memory_pressure() {
echo "System-wide memory free percentage: 50%"
return 0
}
export -f sudo
export -f memory_pressure
# Mock find to return dummy plists in system paths
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
opt_memory_pressure_relief
EOF
[ "$status" -eq 0 ]
# Should attempt to unload with sudo
[[ "$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"* ]]
[[ "$output" == *"Memory pressure already optimal"* ]]
}
@test "opt_startup_items_cleanup removes orphaned helpers" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" MO_TEST_MODE=1 bash --noprofile --norc << 'EOF'
@test "opt_memory_pressure_relief executes purge when pressure is high" {
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 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() {
if [[ "$1" == "find" ]]; then
shift
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"
if [[ "$1" == "purge" ]]; then
echo "purge:executed"
return 0
fi
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() {
return 0
}
export -f mdfind
# Mock directory check (standard app paths don't exist)
test() {
return 1
# Mock date to simulate fast search (< 3 seconds)
date() {
echo "1000"
}
export -f test
export -f date
opt_startup_items_cleanup
opt_spotlight_index_optimize
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"sudo:rm -rf /Library/LaunchDaemons/com.orphan.helper.plist"* ]]
[[ "$output" == *"sudo:rm -rf /Library/PrivilegedHelperTools/com.orphan.helper"* ]]
[[ "$output" == *"Removed orphaned helper: com.orphan.helper"* ]]
[[ "$output" == *"Spotlight index already optimal"* ]]
}