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

feat: add new system optimization tasks and implement orphaned startup item cleanup

This commit is contained in:
Tw93
2025-12-29 19:06:04 +08:00
parent 7af6b39875
commit 44e91be007
9 changed files with 682 additions and 185 deletions

View File

@@ -4,7 +4,7 @@
**Security Audit & Compliance Report**
Version 1.15.3 | December 26, 2025
Version 1.15.9 | December 29, 2025
---
@@ -31,9 +31,9 @@ Version 1.15.3 | December 26, 2025
| Attribute | Details |
|-----------|---------|
| Audit Date | December 26, 2025 |
| Audit Date | December 29, 2025 |
| Audit Conclusion | **PASSED** |
| Mole Version | V1.15.3 |
| Mole Version | V1.15.9 |
| Audited Branch | `main` (HEAD) |
| Scope | Shell scripts, Go binaries, Configuration |
| Methodology | Static analysis, Threat modeling, Code review |
@@ -183,6 +183,20 @@ For user-selected app removal:
| Time Machine | Local snapshots, backups | Checks `backupd` process, aborts if active |
| VPN & Proxy | Shadowsocks, V2Ray, Tailscale, Clash | Protects network configs |
| AI & LLM Tools | Cursor, Claude, ChatGPT, Ollama, LM Studio | Protects models, tokens, sessions |
| Startup Items | `com.apple.*` LaunchAgents/Daemons | System items unconditionally skipped |
**Orphaned Helper Cleanup (`opt_startup_items_cleanup`):**
Removes LaunchAgents/Daemons whose associated app has been uninstalled:
- Checks `AssociatedBundleIdentifiers` to detect orphans
- Skips all `com.apple.*` system items
- Skips paths under `/System/*`, `/usr/bin/*`, `/usr/lib/*`, `/usr/sbin/*`, `/Library/Apple/*`
- Uses `safe_remove` / `safe_sudo_remove` with path validation
- Unloads service via `launchctl` before deletion
- `mdfind` operations have 10-second timeout protection
**Code:** `lib/optimize/tasks.sh:opt_startup_items_cleanup()`
### Crash Safety & Atomic Operations
@@ -193,6 +207,9 @@ For user-selected app removal:
| Volume Scanning | Timeout + filesystem check | Auto-skip unresponsive NFS/SMB/AFP mounts |
| Homebrew Cache | Pre-flight size check | Skip if <50MB (avoids 30-120s delay) |
| Network Volume Check | `diskutil info` with timeout | Prevents hangs on slow/dead mounts |
| SQLite Vacuum | App-running check + 20s timeout | Skips if Mail/Safari/Messages running |
| dyld Cache Update | 24-hour freshness check + 180s timeout | Skips if recently updated |
| App Bundle Search | 10s timeout on mdfind | Fallback to standard paths |
**Timeout Example:**

View File

@@ -193,22 +193,6 @@ ensure_directory() {
ensure_user_dir "$expanded_path"
}
count_local_snapshots() {
if ! command -v tmutil > /dev/null 2>&1; then
echo 0
return
fi
local output
output=$(tmutil listlocalsnapshots / 2> /dev/null || true)
if [[ -z "$output" ]]; then
echo 0
return
fi
echo "$output" | grep -c "com.apple.TimeMachine." | tr -d ' '
}
declare -a SECURITY_FIXES=()
collect_security_fix_actions() {
@@ -224,7 +208,7 @@ collect_security_fix_actions() {
fi
fi
if touchid_supported && ! touchid_configured; then
if ! is_whitelisted "touchid"; then
if ! is_whitelisted "check_touchid"; then
SECURITY_FIXES+=("touchid|Enable Touch ID for sudo")
fi
fi
@@ -424,7 +408,7 @@ main() {
done < "$opts_file"
echo ""
ensure_sudo_session "System optimization requires admin access (Touch ID or password)" || true
ensure_sudo_session "System optimization requires admin access" || true
export FIRST_ACTION=true
if [[ ${#safe_items[@]} -gt 0 ]]; then

View File

@@ -122,12 +122,22 @@ EOF
# Collect all optimization items
local -a items=()
# Always-on items (no size checks - instant)
items+=('system_maintenance|System Database Maintenance|Rebuild LaunchServices, refresh DNS & verify Spotlight|true')
items+=('cache_refresh|Finder & Safari Cache Refresh|Refresh QuickLook, icon services & Safari caches|true')
items+=('maintenance_scripts|System Log Rotation|Rotate and compress system logs with newsyslog|true')
items+=('swap_cleanup|Virtual Memory Refresh|Reset swap files and dynamic pager service|true')
items+=('network_optimization|Network Stack Optimization|Refresh DNS, rebuild ARP & restart mDNSResponder|true')
# 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+=('fix_broken_configs|Broken Config Repair|Fix corrupted preferences files|true')
items+=('network_optimization|Network Cache Refresh|Optimize DNS cache & restart mDNSResponder|true')
# Advanced optimizations (high value, auto-run with safety checks)
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')
# Output items as JSON
local first=true
for item in "${items[@]}"; do

View File

@@ -532,6 +532,7 @@ mktemp_file() {
# Cleanup all tracked temp files and directories
cleanup_temp_files() {
stop_inline_spinner 2> /dev/null || true
local file
if [[ ${#MOLE_TEMP_FILES[@]} -gt 0 ]]; then
for file in "${MOLE_TEMP_FILES[@]}"; do

View File

@@ -33,7 +33,7 @@ fix_broken_preferences() {
plutil -lint "$plist_file" > /dev/null 2>&1 && continue
# Remove broken plist
rm -f "$plist_file" 2> /dev/null || true
safe_remove "$plist_file" true > /dev/null 2>&1 || true
((broken_count++))
done < <(command find "$prefs_dir" -maxdepth 1 -name "*.plist" -type f 2> /dev/null || true)
@@ -53,7 +53,7 @@ fix_broken_preferences() {
plutil -lint "$plist_file" > /dev/null 2>&1 && continue
rm -f "$plist_file" 2> /dev/null || true
safe_remove "$plist_file" true > /dev/null 2>&1 || true
((broken_count++))
done < <(command find "$byhost_dir" -name "*.plist" -type f 2> /dev/null || true)
fi
@@ -61,54 +61,4 @@ fix_broken_preferences() {
echo "$broken_count"
}
# ============================================================================
# Broken Login Items Cleanup
# Find and remove login items pointing to non-existent files
# ============================================================================
# Clean login items with missing executables
fix_broken_login_items() {
local launch_agents_dir="$HOME/Library/LaunchAgents"
[[ -d "$launch_agents_dir" ]] || return 0
# Check whitelist
if command -v is_whitelisted > /dev/null && is_whitelisted "check_login_items"; then return 0; fi
local broken_count=0
while IFS= read -r plist_file; do
[[ -f "$plist_file" ]] || continue
# Skip system items
local filename
filename=$(basename "$plist_file")
case "$filename" in
com.apple.*)
continue
;;
esac
# Extract Program or ProgramArguments[0] from plist using plutil
local program=""
program=$(plutil -extract Program raw "$plist_file" 2> /dev/null || echo "")
if [[ -z "$program" ]]; then
# Try ProgramArguments array (first element)
program=$(plutil -extract ProgramArguments.0 raw "$plist_file" 2> /dev/null || echo "")
fi
# Expand tilde in path if present
program="${program/#\~/$HOME}"
# Skip if no program found or program exists
[[ -z "$program" ]] && continue
[[ -e "$program" ]] && continue
# Program doesn't exist - this is a broken login item
launchctl unload "$plist_file" 2> /dev/null || true
rm -f "$plist_file" 2> /dev/null || true
((broken_count++))
done < <(command find "$launch_agents_dir" -name "*.plist" -type f 2> /dev/null || true)
echo "$broken_count"
}

View File

@@ -12,7 +12,11 @@ readonly MOLE_TM_THIN_TIMEOUT=180
readonly MOLE_TM_THIN_VALUE=9999999999
flush_dns_cache() {
sudo dscacheutil -flushcache 2> /dev/null && sudo killall -HUP mDNSResponder 2> /dev/null
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
@@ -38,7 +42,8 @@ opt_system_maintenance() {
done
}
# Refresh Finder and Safari caches
# 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
@@ -48,8 +53,6 @@ opt_cache_refresh() {
"$HOME/Library/Caches/com.apple.QuickLook.thumbnailcache"
"$HOME/Library/Caches/com.apple.iconservices.store"
"$HOME/Library/Caches/com.apple.iconservices"
"$HOME/Library/Caches/com.apple.Safari/WebKitCache"
"$HOME/Library/Caches/com.apple.Safari/Favicon"
)
for target_path in "${cache_targets[@]}"; do
@@ -64,50 +67,11 @@ opt_cache_refresh() {
echo -e " ${GREEN}${NC} QuickLook thumbnails refreshed"
echo -e " ${GREEN}${NC} Icon services cache rebuilt"
echo -e " ${GREEN}${NC} Safari web cache optimized"
}
# Run periodic maintenance scripts
opt_maintenance_scripts() {
if [[ -t 1 ]]; then
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Rotating logs..."
fi
# Removed: opt_maintenance_scripts - macOS handles log rotation automatically via launchd
local success=false
if run_with_timeout 120 sudo newsyslog > /dev/null 2>&1; then
success=true
fi
if [[ -t 1 ]]; then
stop_inline_spinner
fi
if [[ "$success" == "true" ]]; then
echo -e " ${GREEN}${NC} System logs rotated"
else
echo -e " ${YELLOW}!${NC} Failed to rotate logs"
fi
}
# Refresh wireless interfaces
opt_radio_refresh() {
# Only restart Bluetooth service, do NOT delete pairing information
if sudo pkill -HUP bluetoothd 2> /dev/null; then
echo -e " ${GREEN}${NC} Bluetooth controller refreshed"
fi
local wifi_interface
wifi_interface=$(networksetup -listallhardwareports | awk '/Wi-Fi/{getline; print $2}' | head -1)
if [[ -n "$wifi_interface" ]]; then
if sudo bash -c "trap '' INT TERM; ifconfig '$wifi_interface' down; sleep 1; ifconfig '$wifi_interface' up" 2> /dev/null; then
echo -e " ${GREEN}${NC} Wi-Fi interface reset"
fi
fi
# Restart AirDrop interface
sudo bash -c "trap '' INT TERM; ifconfig awdl0 down; ifconfig awdl0 up" 2> /dev/null || true
echo -e " ${GREEN}${NC} AirDrop service restarted"
}
# 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() {
@@ -125,47 +89,416 @@ opt_saved_state_cleanup() {
echo -e " ${GREEN}${NC} App saved states optimized"
}
# Swap cleanup: reset swap files
opt_swap_cleanup() {
if sudo launchctl unload /System/Library/LaunchDaemons/com.apple.dynamic_pager.plist > /dev/null 2>&1; then
sudo launchctl load /System/Library/LaunchDaemons/com.apple.dynamic_pager.plist > /dev/null 2>&1 || true
echo -e " ${GREEN}${NC} Swap cache reset"
# 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 " ${YELLOW}!${NC} Failed to reset swap"
echo -e " ${GREEN}${NC} All preference files valid"
fi
}
# Startup cache: rebuild kernel caches (handled automatically by modern macOS)
opt_startup_cache() {
echo -e " ${GRAY}-${NC} Startup cache (auto-managed by macOS)"
# 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
}
# Local snapshots: thin Time Machine snapshots
opt_local_snapshots() {
if ! command -v tmutil > /dev/null 2>&1; then
echo -e "${YELLOW}!${NC} tmutil not available on this system"
return
# 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 before after
before=$(count_local_snapshots)
if [[ "$before" -eq 0 ]]; then
echo -e "${GREEN}${ICON_SUCCESS}${NC} No local snapshots to thin"
return
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 "$MOLE_TM_THIN_TIMEOUT" sudo tmutil thinlocalsnapshots / "$MOLE_TM_THIN_VALUE" 4 > /dev/null 2>&1
run_with_timeout 180 sudo update_dyld_shared_cache -force > /dev/null 2>&1
exit_code=$?
set -e
if [[ "$exit_code" -eq 0 ]]; then
if [[ $exit_code -eq 0 ]]; then
success=true
fi
@@ -173,51 +506,82 @@ opt_local_snapshots() {
stop_inline_spinner
fi
after=$(count_local_snapshots)
local removed=$((before - after))
[[ "$removed" -lt 0 ]] && removed=0
if [[ "$success" == "true" ]]; then
echo -e "${GREEN}${ICON_SUCCESS}${NC} Removed $removed snapshots (remaining: $after)"
elif [[ "$exit_code" -eq 124 ]]; then
echo -e "${YELLOW}!${NC} Timed out after ${MOLE_TM_THIN_TIMEOUT}s"
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 "${YELLOW}!${NC} Failed with exit code $exit_code"
echo -e " ${GRAY}-${NC} dyld cache update skipped (automatically managed)"
fi
}
# Fix broken system configurations
# Repairs corrupted preference files and broken login items
opt_fix_broken_configs() {
local broken_prefs=$(fix_broken_preferences)
local broken_items=$(fix_broken_login_items)
# System services refresh
# Restarts system services to apply cache and configuration changes
opt_system_services_refresh() {
local -a restarted_services=()
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"
# cfprefsd - Preferences cache daemon (ensures fixed preferences take effect)
if killall -HUP cfprefsd 2>/dev/null; then
restarted_services+=("Preferences")
fi
if [[ $broken_items -gt 0 ]]; then
echo -e " ${GREEN}${NC} Removed $broken_items broken login items"
# 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} All login items functional"
echo -e " ${GREEN}${NC} System services already optimal"
fi
}
# Network cache optimization
opt_network_optimization() {
flush_dns_cache
echo -e " ${GREEN}${NC} DNS cache refreshed"
# 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
if sudo arp -d -a > /dev/null 2>&1; then
echo -e " ${GREEN}${NC} ARP cache rebuilt"
# 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
echo -e " ${GREEN}${NC} mDNSResponder optimized"
# 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"
}
# Clean Spotlight user caches
# Execute optimization by action name
execute_optimization() {
@@ -227,14 +591,16 @@ execute_optimization() {
case "$action" in
system_maintenance) opt_system_maintenance ;;
cache_refresh) opt_cache_refresh ;;
maintenance_scripts) opt_maintenance_scripts ;;
radio_refresh) opt_radio_refresh ;;
saved_state_cleanup) opt_saved_state_cleanup ;;
swap_cleanup) opt_swap_cleanup ;;
startup_cache) opt_startup_cache ;;
local_snapshots) opt_local_snapshots ;;
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

2
mole
View File

@@ -25,7 +25,7 @@ source "$SCRIPT_DIR/lib/core/common.sh"
trap cleanup_temp_files EXIT INT TERM
# Version info
VERSION="1.15.8"
VERSION="1.15.9"
MOLE_TAGLINE="Deep clean and optimize your Mac."
# Check TouchID configuration

View File

@@ -541,16 +541,12 @@ source "$PROJECT_ROOT/lib/optimize/tasks.sh"
fix_broken_preferences() {
echo 2
}
fix_broken_login_items() {
echo 1
}
opt_fix_broken_configs
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Fixed 2 broken preference files"* ]]
[[ "$output" == *"Removed 1 broken login items"* ]]
[[ "$output" == *"Repaired 2 corrupted preference files"* ]]
}
# ============================================================================
@@ -685,3 +681,149 @@ EOF
[ "$status" -eq 0 ]
[[ "$output" == *"WOULD_CLEAN=no"* ]]
}
@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'
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:$@"
return 0
}
export -f sudo
# 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
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"* ]]
}
@test "opt_startup_items_cleanup removes orphaned helpers" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" MO_TEST_MODE=1 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
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"
return 0
fi
return 1
}
export -f plutil
# Mock mdfind (return nothing -> app missing)
mdfind() {
return 0
}
export -f mdfind
# Mock directory check (standard app paths don't exist)
test() {
return 1
}
export -f test
opt_startup_items_cleanup
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"* ]]
}

View File

@@ -0,0 +1,27 @@
# Mole Cleanup Preview - 2025-12-29 19:05:19
#
# How to protect files:
# 1. Copy any path below to ~/.config/mole/whitelist
# 2. Run: mo clean --whitelist
#
# Example:
# /Users/*/Library/Caches/com.example.app
#
=== User essentials ===
/Users/tw93/www/Mole/tests/tmp-clean-home.IdiWGJ/Library/Caches/TestApp # 4KB
=== Finder metadata ===
=== macOS system caches ===
=== Sandboxed app caches ===
=== Browsers ===
=== Cloud storage ===
=== Office applications ===
=== Developer tools ===