mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 15:39: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() {
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"* ]]
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user