1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-04 14:26:46 +00:00
Files
Mole/lib/optimize/tasks.sh
2025-12-29 11:07:20 +00:00

609 lines
21 KiB
Bash

#!/bin/bash
# Optimization Tasks
set -euo pipefail
# Configuration constants
# MOLE_TM_THIN_TIMEOUT: Max seconds to wait for tmutil thinning (default: 180)
# MOLE_TM_THIN_VALUE: Bytes to thin for local snapshots (default: 9999999999)
# MOLE_MAIL_DOWNLOADS_MIN_KB: Minimum size in KB before cleaning Mail attachments (default: 5120)
# 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
flush_dns_cache() {
if sudo dscacheutil -flushcache 2> /dev/null && sudo killall -HUP mDNSResponder 2> /dev/null; then
MOLE_DNS_FLUSHED=1
return 0
fi
return 1
}
# Rebuild databases and flush caches
opt_system_maintenance() {
local -a results=()
local darwin_major
darwin_major=$(get_darwin_major)
if flush_dns_cache; then
results+=("${GREEN}${NC} DNS cache flushed")
fi
local spotlight_status
spotlight_status=$(mdutil -s / 2> /dev/null || echo "")
if echo "$spotlight_status" | grep -qi "Indexing disabled"; then
results+=("${GRAY}${ICON_EMPTY}${NC} Spotlight indexing disabled")
else
results+=("${GREEN}${NC} Spotlight index verified")
fi
for result in "${results[@]}"; do
echo -e " $result"
done
}
# Refresh Finder caches (QuickLook and icon services)
# Note: Safari caches are cleaned separately in clean/user.sh, so excluded here
opt_cache_refresh() {
qlmanage -r cache > /dev/null 2>&1 || true
qlmanage -r > /dev/null 2>&1 || true
local refreshed=0
local -a cache_targets=(
"$HOME/Library/Caches/com.apple.QuickLook.thumbnailcache"
"$HOME/Library/Caches/com.apple.iconservices.store"
"$HOME/Library/Caches/com.apple.iconservices"
)
for target_path in "${cache_targets[@]}"; do
if [[ -e "$target_path" ]]; then
if ! should_protect_path "$target_path"; then
if safe_remove "$target_path" true; then
((refreshed++))
fi
fi
fi
done
echo -e " ${GREEN}${NC} QuickLook thumbnails refreshed"
echo -e " ${GREEN}${NC} Icon services cache rebuilt"
}
# Removed: opt_maintenance_scripts - macOS handles log rotation automatically via launchd
# Removed: opt_radio_refresh - Interrupts active user connections (WiFi, Bluetooth), degrading UX
# Saved state: remove OLD app saved states (7+ days)
opt_saved_state_cleanup() {
local state_dir="$HOME/Library/Saved Application State"
if [[ -d "$state_dir" ]]; then
while IFS= read -r -d '' state_path; do
if should_protect_path "$state_path"; then
continue
fi
safe_remove "$state_path" true > /dev/null 2>&1
done < <(command find "$state_dir" -type d -name "*.savedState" -mtime "+$MOLE_SAVED_STATE_AGE_DAYS" -print0 2> /dev/null)
fi
echo -e " ${GREEN}${NC} App saved states optimized"
}
# Removed: opt_swap_cleanup - Direct virtual memory operations pose system crash risk
# Removed: opt_startup_cache - Modern macOS has no such mechanism
# Removed: opt_local_snapshots - Deletes user Time Machine recovery points, breaks backup continuity
opt_fix_broken_configs() {
local broken_prefs=$(fix_broken_preferences)
if [[ $broken_prefs -gt 0 ]]; then
echo -e " ${GREEN}${NC} Repaired $broken_prefs corrupted preference files"
else
echo -e " ${GREEN}${NC} All preference files valid"
fi
}
# Network cache optimization
opt_network_optimization() {
if [[ "${MOLE_DNS_FLUSHED:-0}" == "1" ]]; then
echo -e " ${GREEN}${NC} DNS cache already refreshed"
echo -e " ${GREEN}${NC} mDNSResponder already restarted"
return 0
fi
if flush_dns_cache; then
echo -e " ${GREEN}${NC} DNS cache refreshed"
echo -e " ${GREEN}${NC} mDNSResponder restarted"
else
echo -e " ${YELLOW}!${NC} Failed to refresh DNS cache"
fi
}
# SQLite database vacuum 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"
return 0
fi
local -a busy_apps=()
local -a check_apps=("Mail" "Safari" "Messages")
local app
for app in "${check_apps[@]}"; do
if pgrep -x "$app" > /dev/null 2>&1; then
busy_apps+=("$app")
fi
done
if [[ ${#busy_apps[@]} -gt 0 ]]; then
echo -e " ${YELLOW}!${NC} Close these apps before database optimization: ${busy_apps[*]}"
return 0
fi
local -a db_paths=(
"$HOME/Library/Mail/V*/MailData/Envelope Index*"
"$HOME/Library/Messages/chat.db"
"$HOME/Library/Safari/History.db"
"$HOME/Library/Safari/TopSites.db"
)
local vacuumed=0
local timed_out=0
local failed=0
for pattern in "${db_paths[@]}"; do
while IFS= read -r db_file; do
[[ ! -f "$db_file" ]] && continue
[[ "$db_file" == *"-wal" || "$db_file" == *"-shm" ]] && continue
# Skip if protected
should_protect_path "$db_file" && continue
# Verify it's a SQLite database
if ! file "$db_file" 2> /dev/null | grep -q "SQLite"; then
continue
fi
# Try to vacuum
local exit_code=0
set +e
run_with_timeout 20 sqlite3 "$db_file" "VACUUM;" 2> /dev/null
exit_code=$?
set -e
if [[ $exit_code -eq 0 ]]; then
((vacuumed++))
elif [[ $exit_code -eq 124 ]]; then
((timed_out++))
else
((failed++))
fi
done < <(compgen -G "$pattern" || true)
done
if [[ $vacuumed -gt 0 ]]; then
echo -e " ${GREEN}${NC} Optimized $vacuumed databases for Mail, Safari, Messages"
elif [[ $timed_out -eq 0 && $failed -eq 0 ]]; then
echo -e " ${GREEN}${NC} All databases already optimized"
else
echo -e " ${YELLOW}!${NC} Database optimization incomplete"
fi
if [[ $timed_out -gt 0 ]]; then
echo -e " ${YELLOW}!${NC} Timed out on $timed_out databases"
fi
if [[ $failed -gt 0 ]]; then
echo -e " ${YELLOW}!${NC} Failed on $failed databases"
fi
}
# LaunchServices database rebuild
# Fixes "Open with" menu issues, duplicate apps, broken file associations
opt_launch_services_rebuild() {
if [[ -t 1 ]]; then
start_inline_spinner ""
fi
local lsregister="/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister"
if [[ -f "$lsregister" ]]; then
local success=0
set +e
"$lsregister" -r -domain local -domain user -domain system > /dev/null 2>&1
success=$?
if [[ $success -ne 0 ]]; then
"$lsregister" -r -domain local -domain user > /dev/null 2>&1
success=$?
fi
set -e
if [[ -t 1 ]]; then
stop_inline_spinner
fi
if [[ $success -eq 0 ]]; then
echo -e " ${GREEN}${NC} LaunchServices repaired"
echo -e " ${GREEN}${NC} File associations refreshed"
else
echo -e " ${YELLOW}!${NC} Failed to rebuild LaunchServices"
fi
else
if [[ -t 1 ]]; then
stop_inline_spinner
fi
echo -e " ${YELLOW}!${NC} lsregister not found"
fi
}
# Font cache rebuild
# Fixes font rendering issues, missing fonts, and character display problems
opt_font_cache_rebuild() {
local success=false
if sudo atsutil databases -remove > /dev/null 2>&1; then
success=true
fi
if [[ "$success" == "true" ]]; then
echo -e " ${GREEN}${NC} Font cache cleared"
echo -e " ${GREEN}${NC} System will rebuild font database automatically"
else
echo -e " ${YELLOW}!${NC} Failed to clear font cache"
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
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
sudo launchctl unload "$plist_file" 2> /dev/null || true
if safe_sudo_remove "$plist_file"; then
((broken_count++))
else
echo -e " ${YELLOW}!${NC} Failed to remove (sudo) $plist_file"
fi
else
launchctl unload "$plist_file" 2> /dev/null || true
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
# 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
# If associated app is MISSING, this is an orphan
if [[ -z "$app_path" ]]; then
if command -v should_protect_path > /dev/null && should_protect_path "$plist_file"; then
continue
fi
# Get the helper tool 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}"
# 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
if [[ "$need_sudo" == "true" ]]; then
sudo launchctl unload "$plist_file" 2> /dev/null || true
# remove the plist
safe_sudo_remove "$plist_file"
# AND remove the helper binary if it exists and is not protected
if [[ -n "$program" && -f "$program" ]]; then
safe_sudo_remove "$program"
fi
((broken_count++))
echo -e " ${GREEN}${NC} Removed orphaned helper: $(basename "$program")"
else
launchctl unload "$plist_file" 2> /dev/null || true
safe_remove "$plist_file" true
if [[ -n "$program" && -f "$program" ]]; then
safe_remove "$program" true
fi
((broken_count++))
echo -e " ${GREEN}${NC} Removed orphaned helper: $(basename "$program")"
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
sudo launchctl unload "$plist_file" 2> /dev/null || true
if safe_sudo_remove "$plist_file"; then
((broken_count++))
else
echo -e " ${YELLOW}!${NC} Failed to remove (sudo) $plist_file"
fi
else
launchctl unload "$plist_file" 2> /dev/null || true
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
echo -e " ${GREEN}${NC} Removed $broken_count broken startup items"
fi
if [[ $total_count -gt 0 ]]; then
echo -e " ${GREEN}${NC} Verified $total_count startup items"
else
echo -e " ${GREEN}${NC} 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
echo -e " ${GREEN}${NC} dyld shared cache already up-to-date"
return 0
fi
fi
if [[ -t 1 ]]; then
start_inline_spinner "Rebuilding dyld cache..."
fi
local success=false
local exit_code=0
# This can take 1-2 minutes on some systems (180 second timeout)
set +e
run_with_timeout 180 sudo update_dyld_shared_cache -force > /dev/null 2>&1
exit_code=$?
set -e
if [[ $exit_code -eq 0 ]]; then
success=true
fi
if [[ -t 1 ]]; then
stop_inline_spinner
fi
if [[ "$success" == "true" ]]; then
echo -e " ${GREEN}${NC} dyld shared cache rebuilt"
echo -e " ${GREEN}${NC} App launch speed improved"
elif [[ $exit_code -eq 124 ]]; then
echo -e " ${YELLOW}!${NC} dyld cache update timed out"
else
echo -e " ${GRAY}-${NC} dyld cache update skipped (automatically managed)"
fi
}
# System services refresh
# Restarts system services to apply cache and configuration changes
opt_system_services_refresh() {
local -a restarted_services=()
# cfprefsd - Preferences cache daemon (ensures fixed preferences take effect)
if killall -HUP cfprefsd 2> /dev/null; then
restarted_services+=("Preferences")
fi
# lsd - LaunchServices daemon (ensures rebuild takes effect)
if killall lsd 2> /dev/null; then
restarted_services+=("LaunchServices")
fi
# iconservicesagent - Icon services (ensures cache refresh takes effect)
if killall iconservicesagent 2> /dev/null; then
restarted_services+=("Icon Services")
fi
# fontd - Font server (ensures font cache refresh takes effect)
if killall fontd 2> /dev/null; then
restarted_services+=("Font Server")
fi
if [[ ${#restarted_services[@]} -gt 0 ]]; then
echo -e " ${GREEN}${NC} Refreshed ${#restarted_services[@]} system services"
for service in "${restarted_services[@]}"; do
echo -e "$service"
done
else
echo -e " ${GREEN}${NC} System services already optimal"
fi
}
# Dock cache refresh
# Fixes broken icons, duplicate items, and visual glitches in the Dock
opt_dock_refresh() {
local dock_support="$HOME/Library/Application Support/Dock"
local refreshed=false
# Remove Dock database files (icons, positions, etc.)
if [[ -d "$dock_support" ]]; then
while IFS= read -r db_file; do
if [[ -f "$db_file" ]]; then
safe_remove "$db_file" true > /dev/null 2>&1 && refreshed=true
fi
done < <(find "$dock_support" -name "*.db" -type f 2> /dev/null || true)
fi
# Also clear Dock plist cache
local dock_plist="$HOME/Library/Preferences/com.apple.dock.plist"
if [[ -f "$dock_plist" ]]; then
# Just touch to invalidate cache, don't delete (preserves user settings)
touch "$dock_plist" 2> /dev/null || true
fi
# Restart Dock to apply changes
killall Dock 2> /dev/null || true
if [[ "$refreshed" == "true" ]]; then
echo -e " ${GREEN}${NC} Dock cache cleared"
fi
echo -e " ${GREEN}${NC} Dock refreshed"
}
# Execute optimization by action name
execute_optimization() {
local action="$1"
local path="${2:-}"
case "$action" in
system_maintenance) opt_system_maintenance ;;
cache_refresh) opt_cache_refresh ;;
saved_state_cleanup) opt_saved_state_cleanup ;;
fix_broken_configs) opt_fix_broken_configs ;;
network_optimization) opt_network_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 ;;
*)
echo -e "${YELLOW}${ICON_ERROR}${NC} Unknown action: $action"
return 1
;;
esac
}