1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-04 20:19:45 +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

@@ -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