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

Safety and Reliability Improvements

This commit is contained in:
Tw93
2025-12-04 15:06:45 +08:00
parent 54bbbcce47
commit a92d352376
12 changed files with 137 additions and 96 deletions

View File

@@ -152,9 +152,9 @@ cleanup() {
INLINE_SPINNER_PID=""
fi
# Clear any spinner output
# Clear any spinner output - spinner outputs to stderr
if [[ -t 1 ]]; then
printf "\r\033[K"
printf "\r\033[K" >&2
fi
# Stop sudo session
@@ -164,8 +164,8 @@ cleanup() {
# If interrupted, show message
if [[ "$signal" == "INT" ]] || [[ $exit_code -eq 130 ]]; then
printf "\r\033[K"
echo -e "${YELLOW}Interrupted by user${NC}"
printf "\r\033[K" >&2
echo -e "${YELLOW}Interrupted by user${NC}" >&2
fi
}
@@ -271,9 +271,14 @@ safe_clean() {
for path in "${existing_paths[@]}"; do
(
local size
size=$(get_path_size_kb "$path")
# Timeout protection: prevent du from hanging on problematic paths
size=$(run_with_timeout 5 get_path_size_kb "$path")
[[ -z "$size" || ! "$size" =~ ^[0-9]+$ ]] && size=0
local count
count=$(find "$path" -type f 2> /dev/null | wc -l | tr -d ' ')
# Timeout protection: prevent find from hanging on problematic paths
count=$(run_with_timeout 10 sh -c "find \"$path\" -type f 2> /dev/null | wc -l | tr -d ' '")
# If timeout or error, set count to 0 to skip this path
[[ -z "$count" || ! "$count" =~ ^[0-9]+$ ]] && count=0
# Use index + PID for unique filename
local tmp_file="$temp_dir/result_${idx}.$$"
echo "$size $count" > "$tmp_file"
@@ -330,9 +335,14 @@ safe_clean() {
for path in "${existing_paths[@]}"; do
local size_bytes
size_bytes=$(get_path_size_kb "$path")
# Timeout protection: prevent du from hanging on problematic paths
size_bytes=$(run_with_timeout 5 get_path_size_kb "$path")
[[ -z "$size_bytes" || ! "$size_bytes" =~ ^[0-9]+$ ]] && size_bytes=0
local count
count=$(find "$path" -type f 2> /dev/null | wc -l | tr -d ' ')
# Timeout protection: prevent find from hanging on problematic paths
count=$(run_with_timeout 10 sh -c "find \"$path\" -type f 2> /dev/null | wc -l | tr -d ' '")
# If timeout or error, set count to 0 to skip this path
[[ -z "$count" || ! "$count" =~ ^[0-9]+$ ]] && count=0
if [[ "$count" -gt 0 && "$size_bytes" -gt 0 ]]; then
if [[ "$DRY_RUN" != "true" ]]; then
@@ -676,7 +686,7 @@ perform_cleanup() {
[[ -f "$app/Contents/Info.plist" ]] || continue
bundle_id=$(defaults read "$app/Contents/Info.plist" CFBundleIdentifier 2> /dev/null || echo "")
[[ -n "$bundle_id" ]] && echo "$bundle_id" >> "$installed_bundles"
done < <(run_with_timeout 10 find "$search_path" -maxdepth 2 -type d -name "*.app" 2> /dev/null || true)
done < <(run_with_timeout 10 command find "$search_path" -maxdepth 2 -type d -name "*.app" 2> /dev/null || true)
done
# Get running applications and LaunchAgents with timeout protection

View File

@@ -183,8 +183,8 @@ scan_applications() {
done < <(
# Scan both system and user application directories
# Using maxdepth 3 to find apps in subdirectories (e.g., Adobe apps in /Applications/Adobe X/)
find /Applications -name "*.app" -maxdepth 3 -print0 2> /dev/null
find ~/Applications -name "*.app" -maxdepth 3 -print0 2> /dev/null
command find /Applications -name "*.app" -maxdepth 3 -print0 2> /dev/null
command find ~/Applications -name "*.app" -maxdepth 3 -print0 2> /dev/null
)
# Second pass: process each app with parallel size calculation

View File

@@ -441,7 +441,7 @@ get_macos_update_labels() {
# ============================================================================
check_disk_space() {
local free_gb=$(df -H / | awk 'NR==2 {print $4}' | sed 's/G//')
local free_gb=$(command df -H / | awk 'NR==2 {print $4}' | sed 's/G//')
local free_num=$(echo "$free_gb" | tr -d 'G' | cut -d'.' -f1)
export DISK_FREE_GB=$free_num

View File

@@ -32,7 +32,7 @@ clean_ds_store_tree() {
)
# Build find command to avoid unbound array expansion with set -u
local -a find_cmd=("find" "$target")
local -a find_cmd=("command" "find" "$target")
if [[ "$target" == "$HOME" ]]; then
find_cmd+=("-maxdepth" "5")
fi
@@ -100,7 +100,7 @@ clean_orphaned_app_data() {
for app_dir in "${app_dirs[@]}"; do
[[ -d "$app_dir" ]] || continue
find "$app_dir" -name "*.app" -maxdepth 3 -type d 2> /dev/null | while IFS= read -r app_path; do
command find "$app_dir" -name "*.app" -maxdepth 3 -type d 2> /dev/null | while IFS= read -r app_path; do
local bundle_id
bundle_id=$(defaults read "$app_path/Contents/Info.plist" CFBundleIdentifier 2> /dev/null || echo "")
[[ -n "$bundle_id" ]] && echo "$bundle_id"

View File

@@ -44,7 +44,7 @@ check_tcc_permissions() {
# Trigger all TCC prompts upfront by accessing each directory
# Using find -maxdepth 1 ensures we touch the directory without deep scanning
for dir in "${tcc_dirs[@]}"; do
[[ -d "$dir" ]] && find "$dir" -maxdepth 1 -type d > /dev/null 2>&1
[[ -d "$dir" ]] && command find "$dir" -maxdepth 1 -type d > /dev/null 2>&1
done
stop_inline_spinner
@@ -94,7 +94,7 @@ clean_service_worker_cache() {
fi
cleaned_size=$((cleaned_size + size))
fi
done < <(find "$cache_path" -type d -depth 2 2> /dev/null)
done < <(command find "$cache_path" -type d -depth 2 2> /dev/null)
if [[ $cleaned_size -gt 0 ]]; then
local cleaned_mb=$((cleaned_size / 1024))
@@ -120,7 +120,7 @@ clean_project_caches() {
local nextjs_tmp_file
nextjs_tmp_file=$(create_temp_file)
(
find "$HOME" -P -mount -type d -name ".next" -maxdepth 3 \
command find "$HOME" -P -mount -type d -name ".next" -maxdepth 3 \
-not -path "*/Library/*" \
-not -path "*/.Trash/*" \
-not -path "*/node_modules/*" \
@@ -164,7 +164,7 @@ clean_project_caches() {
local pycache_tmp_file
pycache_tmp_file=$(create_temp_file)
(
find "$HOME" -P -mount -type d -name "__pycache__" -maxdepth 3 \
command find "$HOME" -P -mount -type d -name "__pycache__" -maxdepth 3 \
-not -path "*/Library/*" \
-not -path "*/.Trash/*" \
-not -path "*/node_modules/*" \

View File

@@ -49,7 +49,7 @@ clean_broken_preferences() {
((broken_count++))
((total_size_kb += size_kb))
fi
done < <(find "$prefs_dir" -maxdepth 1 -name "*.plist" -type f 2> /dev/null || true)
done < <(command find "$prefs_dir" -maxdepth 1 -name "*.plist" -type f 2> /dev/null || true)
# Check ByHost preferences
local byhost_dir="$prefs_dir/ByHost"
@@ -76,7 +76,7 @@ clean_broken_preferences() {
((broken_count++))
((total_size_kb += size_kb))
fi
done < <(find "$byhost_dir" -name "*.plist" -type f 2> /dev/null || true)
done < <(command find "$byhost_dir" -name "*.plist" -type f 2> /dev/null || true)
fi
if [[ -t 1 ]]; then
@@ -153,7 +153,7 @@ clean_broken_login_items() {
((broken_count++))
((total_size_kb += size_kb))
done < <(find "$launch_agents_dir" -name "*.plist" -type f 2> /dev/null || true)
done < <(command find "$launch_agents_dir" -name "*.plist" -type f 2> /dev/null || true)
if [[ -t 1 ]]; then
stop_inline_spinner

View File

@@ -13,12 +13,12 @@ clean_deep_system() {
# Clean old temp files
local tmp_cleaned=0
local tmp_count=$(sudo find /tmp -type f -mtime +"${MOLE_TEMP_FILE_AGE_DAYS}" 2> /dev/null | wc -l | tr -d ' ')
local tmp_count=$(sudo command find /tmp -type f -mtime +"${MOLE_TEMP_FILE_AGE_DAYS}" 2> /dev/null | wc -l | tr -d ' ')
if [[ "$tmp_count" -gt 0 ]]; then
safe_sudo_find_delete "/tmp" "*" "${MOLE_TEMP_FILE_AGE_DAYS}" "f" || true
tmp_cleaned=1
fi
local var_tmp_count=$(sudo find /var/tmp -type f -mtime +"${MOLE_TEMP_FILE_AGE_DAYS}" 2> /dev/null | wc -l | tr -d ' ')
local var_tmp_count=$(sudo command find /var/tmp -type f -mtime +"${MOLE_TEMP_FILE_AGE_DAYS}" 2> /dev/null | wc -l | tr -d ' ')
if [[ "$var_tmp_count" -gt 0 ]]; then
safe_sudo_find_delete "/var/tmp" "*" "${MOLE_TEMP_FILE_AGE_DAYS}" "f" || true
tmp_cleaned=1
@@ -47,7 +47,7 @@ clean_deep_system() {
while IFS= read -r -d '' item; do
# Skip system-protected files (restricted flag)
local item_flags
item_flags=$(stat -f%Sf "$item" 2> /dev/null || echo "")
item_flags=$(command stat -f%Sf "$item" 2> /dev/null || echo "")
if [[ "$item_flags" == *"restricted"* ]]; then
continue
fi
@@ -55,7 +55,7 @@ clean_deep_system() {
if safe_sudo_remove "$item"; then
((updates_cleaned++))
fi
done < <(find /Library/Updates -mindepth 1 -maxdepth 1 -print0 2> /dev/null)
done < <(command find /Library/Updates -mindepth 1 -maxdepth 1 -print0 2> /dev/null)
[[ $updates_cleaned -gt 0 ]] && log_success "System library updates"
fi
fi
@@ -103,7 +103,7 @@ clean_time_machine_failed_backups() {
fi
fi
local fs_type=$(df -T "$volume" 2> /dev/null | tail -1 | awk '{print $2}')
local fs_type=$(command df -T "$volume" 2> /dev/null | tail -1 | awk '{print $2}')
case "$fs_type" in
nfs | smbfs | afpfs | cifs | webdav) continue ;;
esac
@@ -150,7 +150,7 @@ clean_time_machine_failed_backups() {
note_activity
fi
fi
done < <(find "$backupdb_dir" -maxdepth 3 -type d \( -name "*.inProgress" -o -name "*.inprogress" \) 2> /dev/null || true)
done < <(command find "$backupdb_dir" -maxdepth 3 -type d \( -name "*.inProgress" -o -name "*.inprogress" \) 2> /dev/null || true)
fi
# APFS style backups (.backupbundle or .sparsebundle)
@@ -200,7 +200,7 @@ clean_time_machine_failed_backups() {
note_activity
fi
fi
done < <(find "$mounted_path" -maxdepth 3 -type d \( -name "*.inProgress" -o -name "*.inprogress" \) 2> /dev/null || true)
done < <(command find "$mounted_path" -maxdepth 3 -type d \( -name "*.inProgress" -o -name "*.inprogress" \) 2> /dev/null || true)
fi
done
done

View File

@@ -15,7 +15,7 @@ clean_user_essentials() {
[[ -d "$volume" && -d "$volume/.Trashes" && -w "$volume" ]] || continue
# Skip network volumes
local fs_type=$(df -T "$volume" 2> /dev/null | tail -1 | awk '{print $2}')
local fs_type=$(command df -T "$volume" 2> /dev/null | tail -1 | awk '{print $2}')
case "$fs_type" in
nfs | smbfs | afpfs | cifs | webdav) continue ;;
esac
@@ -26,7 +26,7 @@ clean_user_essentials() {
# Safely iterate and remove each item
while IFS= read -r -d '' item; do
safe_remove "$item" true || true
done < <(find "$volume/.Trashes" -mindepth 1 -maxdepth 1 -print0 2> /dev/null)
done < <(command find "$volume/.Trashes" -mindepth 1 -maxdepth 1 -print0 2> /dev/null)
fi
fi
done
@@ -65,7 +65,7 @@ clean_finder_metadata() {
[[ -d "$volume" && -w "$volume" ]] || continue
local fs_type=""
fs_type=$(df -T "$volume" 2> /dev/null | tail -1 | awk '{print $2}')
fs_type=$(command df -T "$volume" 2> /dev/null | tail -1 | awk '{print $2}')
case "$fs_type" in
nfs | smbfs | afpfs | cifs | webdav) continue ;;
esac
@@ -150,7 +150,7 @@ clean_browsers() {
[[ "$sw_path" == *"Arc"* ]] && browser_name="Arc"
[[ "$profile_name" != "Default" ]] && browser_name="$browser_name ($profile_name)"
clean_service_worker_cache "$browser_name" "$sw_path"
done < <(find "$HOME/Library/Application Support/Google/Chrome" \
done < <(command find "$HOME/Library/Application Support/Google/Chrome" \
"$HOME/Library/Application Support/Microsoft Edge" \
"$HOME/Library/Application Support/BraveSoftware/Brave-Browser" \
"$HOME/Library/Application Support/Arc/User Data" \
@@ -250,7 +250,7 @@ clean_application_support_logs() {
local profile_name=$(basename "$profile_path")
[[ "$profile_name" == "User Data" ]] && profile_name=$(basename "$(dirname "$profile_path")")
clean_service_worker_cache "$app_name ($profile_name)" "$sw_cache"
done < <(find "$app_dir" -maxdepth 4 -type d \( -name "CacheStorage" -o -name "ScriptCache" \) -path "*/Service Worker/*" 2> /dev/null || true)
done < <(command find "$app_dir" -maxdepth 4 -type d \( -name "CacheStorage" -o -name "ScriptCache" \) -path "*/Service Worker/*" 2> /dev/null || true)
# Clean stale update downloads (older than 7 days)
if [[ -d "$app_dir/update" ]] && ls "$app_dir/update" > /dev/null 2>&1; then
@@ -259,7 +259,7 @@ clean_application_support_logs() {
if [[ $dir_age_days -ge $MOLE_TEMP_FILE_AGE_DAYS ]]; then
safe_clean "$update_dir" "Stale update: $app_name"
fi
done < <(find "$app_dir/update" -mindepth 1 -maxdepth 1 -type d 2> /dev/null || true)
done < <(command find "$app_dir/update" -mindepth 1 -maxdepth 1 -type d 2> /dev/null || true)
fi
done
@@ -268,17 +268,17 @@ clean_application_support_logs() {
while IFS= read -r logs_dir; do
local container_name=$(basename "$(dirname "$logs_dir")")
safe_clean "$logs_dir"/* "Group container logs: $container_name"
done < <(find "$HOME/Library/Group Containers" -maxdepth 2 -type d -name "Logs" 2> /dev/null || true)
done < <(command find "$HOME/Library/Group Containers" -maxdepth 2 -type d -name "Logs" 2> /dev/null || true)
fi
}
# Check and show iOS device backup info
check_ios_device_backups() {
local backup_dir="$HOME/Library/Application Support/MobileSync/Backup"
if [[ -d "$backup_dir" ]] && find "$backup_dir" -mindepth 1 -maxdepth 1 | read -r _; then
if [[ -d "$backup_dir" ]] && command find "$backup_dir" -mindepth 1 -maxdepth 1 | read -r _; then
local backup_kb=$(get_path_size_kb "$backup_dir")
if [[ -n "${backup_kb:-}" && "$backup_kb" -gt 102400 ]]; then
local backup_human=$(du -sh "$backup_dir" 2> /dev/null | awk '{print $1}')
local backup_human=$(command du -sh "$backup_dir" 2> /dev/null | awk '{print $1}')
note_activity
echo -e " Found ${GREEN}${backup_human}${NC} iOS backups"
echo -e " You can delete them manually: ${backup_dir}"

View File

@@ -241,7 +241,7 @@ safe_find_delete() {
fi
# Execute find with safety limits
find "$base_dir" \
command find "$base_dir" \
-maxdepth 3 \
-name "$pattern" \
-type "$type_filter" \
@@ -277,7 +277,7 @@ safe_sudo_find_delete() {
fi
# Execute find with safety limits
sudo find "$base_dir" \
sudo command find "$base_dir" \
-maxdepth 3 \
-name "$pattern" \
-type "$type_filter" \
@@ -386,7 +386,7 @@ detect_architecture() {
# Get free disk space on root volume (human-readable)
get_free_space() {
df -h / | awk 'NR==2 {print $4}'
command df -h / | awk 'NR==2 {print $4}'
}
# Clear terminal screen and move cursor to home
@@ -396,12 +396,16 @@ clear_screen() {
# Hide terminal cursor
hide_cursor() {
printf '\033[?25l'
[[ -t 1 ]] || return 0
# Output to stderr for consistency with spinner, ensure unbuffered
printf '\033[?25l' >&2
}
# Show terminal cursor
show_cursor() {
printf '\033[?25h'
[[ -t 1 ]] || return 0
# Output to stderr for consistency with spinner, ensure unbuffered
printf '\033[?25h' >&2
}
# Read single keypress and return normalized key name
@@ -997,7 +1001,13 @@ start_inline_spinner() {
# Stop inline spinner
stop_inline_spinner() {
if [[ -n "$INLINE_SPINNER_PID" ]]; then
kill "$INLINE_SPINNER_PID" 2> /dev/null || true
# Try graceful TERM first, then force KILL if needed
if kill -0 "$INLINE_SPINNER_PID" 2> /dev/null; then
kill -TERM "$INLINE_SPINNER_PID" 2> /dev/null || true
sleep 0.05 2> /dev/null || true
# Force kill if still running
kill -KILL "$INLINE_SPINNER_PID" 2> /dev/null || true
fi
wait "$INLINE_SPINNER_PID" 2> /dev/null || true
INLINE_SPINNER_PID=""
# Clear the line - use \033[2K to clear entire line, not just to end
@@ -1150,7 +1160,7 @@ clean_tool_cache() {
get_path_size_kb() {
local path="$1"
local result
result=$(du -sk "$path" 2> /dev/null | awk '{print $1}')
result=$(command du -sk "$path" 2> /dev/null | awk '{print $1}')
echo "${result:-0}"
}
@@ -1177,22 +1187,36 @@ force_kill_app() {
local app_name="$1"
local app_path="${2:-}"
# Use app path for precise matching if provided
local match_pattern="$app_name"
if [[ -n "$app_path" && -e "$app_path" ]]; then
# Use the app bundle path for more precise matching
match_pattern="$app_path"
# Get the executable name from bundle if app_path is provided
local exec_name=""
if [[ -n "$app_path" && -e "$app_path/Contents/Info.plist" ]]; then
exec_name=$(defaults read "$app_path/Contents/Info.plist" CFBundleExecutable 2> /dev/null || echo "")
fi
if pgrep -f "$match_pattern" > /dev/null 2>&1; then
pkill -f "$match_pattern" 2> /dev/null || true
sleep 1
# Use executable name for precise matching, fallback to app name
local match_pattern="${exec_name:-$app_name}"
# Check if process is actually running using exact match (-x)
if ! pgrep -x "$match_pattern" > /dev/null 2>&1; then
# Not running, return success
return 0
fi
if pgrep -f "$match_pattern" > /dev/null 2>&1; then
pkill -9 -f "$match_pattern" 2> /dev/null || true
sleep 1
# Try graceful termination first
pkill -x "$match_pattern" 2> /dev/null || true
sleep 1
# Check again after graceful kill
if ! pgrep -x "$match_pattern" > /dev/null 2>&1; then
return 0
fi
pgrep -f "$match_pattern" > /dev/null 2>&1 && return 1 || return 0
# Force kill if still running
pkill -9 -x "$match_pattern" 2> /dev/null || true
sleep 1
# Final check
pgrep -x "$match_pattern" > /dev/null 2>&1 && return 1 || return 0
}
# Remove application icons from the Dock (best effort)
@@ -1791,7 +1815,7 @@ find_app_files() {
[[ -f ~/Library/Preferences/"$bundle_id".plist ]] && files_to_clean+=("$HOME/Library/Preferences/$bundle_id.plist")
while IFS= read -r -d '' pref; do
files_to_clean+=("$pref")
done < <(find ~/Library/Preferences/ByHost \( -name "$bundle_id*.plist" \) -print0 2> /dev/null)
done < <(command find ~/Library/Preferences/ByHost \( -name "$bundle_id*.plist" \) -print0 2> /dev/null)
# Logs
[[ -d ~/Library/Logs/"$app_name" ]] && files_to_clean+=("$HOME/Library/Logs/$app_name")
@@ -1800,7 +1824,7 @@ find_app_files() {
# Crash Reports and Diagnostics
while IFS= read -r -d '' report; do
files_to_clean+=("$report")
done < <(find ~/Library/Logs/DiagnosticReports \( -name "*$app_name*" -o -name "*$bundle_id*" \) -print0 2> /dev/null)
done < <(command find ~/Library/Logs/DiagnosticReports \( -name "*$app_name*" -o -name "*$bundle_id*" \) -print0 2> /dev/null)
# Saved Application State
[[ -d ~/Library/Saved\ Application\ State/"$bundle_id".savedState ]] && files_to_clean+=("$HOME/Library/Saved Application State/$bundle_id.savedState")
@@ -1811,7 +1835,7 @@ find_app_files() {
# Group Containers
while IFS= read -r -d '' container; do
files_to_clean+=("$container")
done < <(find ~/Library/Group\ Containers -type d \( -name "*$bundle_id*" \) -print0 2> /dev/null)
done < <(command find ~/Library/Group\ Containers -type d \( -name "*$bundle_id*" \) -print0 2> /dev/null)
# WebKit data
[[ -d ~/Library/WebKit/"$bundle_id" ]] && files_to_clean+=("$HOME/Library/WebKit/$bundle_id")
@@ -1835,7 +1859,7 @@ find_app_files() {
# Internet Plug-Ins
while IFS= read -r -d '' plugin; do
files_to_clean+=("$plugin")
done < <(find ~/Library/Internet\ Plug-Ins \( -name "$bundle_id*" -o -name "$app_name*" \) -print0 2> /dev/null)
done < <(command find ~/Library/Internet\ Plug-Ins \( -name "$bundle_id*" -o -name "$app_name*" \) -print0 2> /dev/null)
# QuickLook Plugins
[[ -d ~/Library/QuickLook/"$app_name".qlgenerator ]] && files_to_clean+=("$HOME/Library/QuickLook/$app_name.qlgenerator")
@@ -1852,7 +1876,7 @@ find_app_files() {
# CoreData
while IFS= read -r -d '' coredata; do
files_to_clean+=("$coredata")
done < <(find ~/Library/CoreData \( -name "*$bundle_id*" -o -name "*$app_name*" \) -print0 2> /dev/null)
done < <(command find ~/Library/CoreData \( -name "*$bundle_id*" -o -name "*$app_name*" \) -print0 2> /dev/null)
# Autosave Information
[[ -d ~/Library/Autosave\ Information/"$bundle_id" ]] && files_to_clean+=("$HOME/Library/Autosave Information/$bundle_id")
@@ -1863,7 +1887,7 @@ find_app_files() {
# Receipts (user-level)
while IFS= read -r -d '' receipt; do
files_to_clean+=("$receipt")
done < <(find ~/Library/Receipts \( -name "*$bundle_id*" -o -name "*$app_name*" \) -print0 2> /dev/null)
done < <(command find ~/Library/Receipts \( -name "*$bundle_id*" -o -name "*$app_name*" \) -print0 2> /dev/null)
# Spotlight Plugins
[[ -d ~/Library/Spotlight/"$app_name".mdimporter ]] && files_to_clean+=("$HOME/Library/Spotlight/$app_name.mdimporter")
@@ -1871,7 +1895,7 @@ find_app_files() {
# Scripting Additions
while IFS= read -r -d '' scripting; do
files_to_clean+=("$scripting")
done < <(find ~/Library/ScriptingAdditions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find ~/Library/ScriptingAdditions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# Color Pickers
[[ -d ~/Library/ColorPickers/"$app_name".colorPicker ]] && files_to_clean+=("$HOME/Library/ColorPickers/$app_name.colorPicker")
@@ -1879,58 +1903,58 @@ find_app_files() {
# Quartz Compositions
while IFS= read -r -d '' composition; do
files_to_clean+=("$composition")
done < <(find ~/Library/Compositions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find ~/Library/Compositions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# Address Book Plug-Ins
while IFS= read -r -d '' plugin; do
files_to_clean+=("$plugin")
done < <(find ~/Library/Address\ Book\ Plug-Ins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find ~/Library/Address\ Book\ Plug-Ins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# Mail Bundles
while IFS= read -r -d '' bundle; do
files_to_clean+=("$bundle")
done < <(find ~/Library/Mail/Bundles \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find ~/Library/Mail/Bundles \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# Input Managers (app-specific only)
while IFS= read -r -d '' manager; do
files_to_clean+=("$manager")
done < <(find ~/Library/InputManagers \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find ~/Library/InputManagers \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# Custom Sounds
while IFS= read -r -d '' sound; do
files_to_clean+=("$sound")
done < <(find ~/Library/Sounds \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find ~/Library/Sounds \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# Plugins
while IFS= read -r -d '' plugin; do
files_to_clean+=("$plugin")
done < <(find ~/Library/Plugins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find ~/Library/Plugins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# Private Frameworks
while IFS= read -r -d '' framework; do
files_to_clean+=("$framework")
done < <(find ~/Library/PrivateFrameworks \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find ~/Library/PrivateFrameworks \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# Audio Plug-Ins
while IFS= read -r -d '' plugin; do
files_to_clean+=("$plugin")
done < <(find ~/Library/Audio/Plug-Ins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find ~/Library/Audio/Plug-Ins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# Components
while IFS= read -r -d '' component; do
files_to_clean+=("$component")
done < <(find ~/Library/Components \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find ~/Library/Components \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# Metadata
while IFS= read -r -d '' metadata; do
files_to_clean+=("$metadata")
done < <(find ~/Library/Metadata \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find ~/Library/Metadata \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# Workflows
[[ -d ~/Library/Workflows/"$app_name".workflow ]] && files_to_clean+=("$HOME/Library/Workflows/$app_name.workflow")
while IFS= read -r -d '' workflow; do
files_to_clean+=("$workflow")
done < <(find ~/Library/Workflows \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find ~/Library/Workflows \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# Favorites (excluding Safari)
while IFS= read -r -d '' favorite; do
@@ -1939,7 +1963,7 @@ find_app_files() {
*Safari*) continue ;;
esac
files_to_clean+=("$favorite")
done < <(find ~/Library/Favorites \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find ~/Library/Favorites \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# Unix-style configuration directories and files (cross-platform apps)
[[ -d ~/.config/"$app_name" ]] && files_to_clean+=("$HOME/.config/$app_name")
@@ -1972,7 +1996,7 @@ find_app_system_files() {
# Privileged Helper Tools
while IFS= read -r -d '' helper; do
system_files+=("$helper")
done < <(find /Library/PrivilegedHelperTools \( -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find /Library/PrivilegedHelperTools \( -name "$bundle_id*" \) -print0 2> /dev/null)
# System Preferences
[[ -f /Library/Preferences/"$bundle_id".plist ]] && system_files+=("/Library/Preferences/$bundle_id.plist")
@@ -1980,7 +2004,7 @@ find_app_system_files() {
# Installation Receipts
while IFS= read -r -d '' receipt; do
system_files+=("$receipt")
done < <(find /private/var/db/receipts \( -name "*$bundle_id*" \) -print0 2> /dev/null)
done < <(command find /private/var/db/receipts \( -name "*$bundle_id*" \) -print0 2> /dev/null)
# System Logs
[[ -d /Library/Logs/"$app_name" ]] && system_files+=("/Library/Logs/$app_name")
@@ -1989,7 +2013,7 @@ find_app_system_files() {
# System Crash Reports and Diagnostics
while IFS= read -r -d '' report; do
system_files+=("$report")
done < <(find /Library/Logs/DiagnosticReports \( -name "*$app_name*" -o -name "*$bundle_id*" \) -print0 2> /dev/null)
done < <(command find /Library/Logs/DiagnosticReports \( -name "*$app_name*" -o -name "*$bundle_id*" \) -print0 2> /dev/null)
# System Frameworks
[[ -d /Library/Frameworks/"$app_name".framework ]] && system_files+=("/Library/Frameworks/$app_name.framework")
@@ -1997,7 +2021,7 @@ find_app_system_files() {
# System Internet Plug-Ins
while IFS= read -r -d '' plugin; do
system_files+=("$plugin")
done < <(find /Library/Internet\ Plug-Ins \( -name "$bundle_id*" -o -name "$app_name*" \) -print0 2> /dev/null)
done < <(command find /Library/Internet\ Plug-Ins \( -name "$bundle_id*" -o -name "$app_name*" \) -print0 2> /dev/null)
# System QuickLook Plugins
[[ -d /Library/QuickLook/"$app_name".qlgenerator ]] && system_files+=("/Library/QuickLook/$app_name.qlgenerator")
@@ -2005,7 +2029,7 @@ find_app_system_files() {
# System Receipts
while IFS= read -r -d '' receipt; do
system_files+=("$receipt")
done < <(find /Library/Receipts \( -name "*$bundle_id*" -o -name "*$app_name*" \) -print0 2> /dev/null)
done < <(command find /Library/Receipts \( -name "*$bundle_id*" -o -name "*$app_name*" \) -print0 2> /dev/null)
# System Spotlight Plugins
[[ -d /Library/Spotlight/"$app_name".mdimporter ]] && system_files+=("/Library/Spotlight/$app_name.mdimporter")
@@ -2013,7 +2037,7 @@ find_app_system_files() {
# System Scripting Additions
while IFS= read -r -d '' scripting; do
system_files+=("$scripting")
done < <(find /Library/ScriptingAdditions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find /Library/ScriptingAdditions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# System Color Pickers
[[ -d /Library/ColorPickers/"$app_name".colorPicker ]] && system_files+=("/Library/ColorPickers/$app_name.colorPicker")
@@ -2021,32 +2045,32 @@ find_app_system_files() {
# System Quartz Compositions
while IFS= read -r -d '' composition; do
system_files+=("$composition")
done < <(find /Library/Compositions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find /Library/Compositions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# System Address Book Plug-Ins
while IFS= read -r -d '' plugin; do
system_files+=("$plugin")
done < <(find /Library/Address\ Book\ Plug-Ins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find /Library/Address\ Book\ Plug-Ins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# System Mail Bundles
while IFS= read -r -d '' bundle; do
system_files+=("$bundle")
done < <(find /Library/Mail/Bundles \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find /Library/Mail/Bundles \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# System Input Managers
while IFS= read -r -d '' manager; do
system_files+=("$manager")
done < <(find /Library/InputManagers \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find /Library/InputManagers \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# System Sounds
while IFS= read -r -d '' sound; do
system_files+=("$sound")
done < <(find /Library/Sounds \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find /Library/Sounds \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# System Contextual Menu Items
while IFS= read -r -d '' item; do
system_files+=("$item")
done < <(find /Library/Contextual\ Menu\ Items \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find /Library/Contextual\ Menu\ Items \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# System Preference Panes
[[ -d /Library/PreferencePanes/"$app_name".prefPane ]] && system_files+=("/Library/PreferencePanes/$app_name.prefPane")
@@ -2061,17 +2085,17 @@ find_app_system_files() {
# System Audio Plug-Ins
while IFS= read -r -d '' plugin; do
system_files+=("$plugin")
done < <(find /Library/Audio/Plug-Ins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find /Library/Audio/Plug-Ins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# System Components
while IFS= read -r -d '' component; do
system_files+=("$component")
done < <(find /Library/Components \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find /Library/Components \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# System Extensions
while IFS= read -r -d '' extension; do
system_files+=("$extension")
done < <(find /Library/Extensions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
done < <(command find /Library/Extensions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null)
# Only print if array has elements
if [[ ${#system_files[@]} -gt 0 ]]; then

View File

@@ -207,7 +207,9 @@ opt_mail_downloads() {
local deleted=0
for target_path in "${mail_dirs[@]}"; do
if [[ -d "$target_path" ]]; then
local file_count=$(find "$target_path" -type f -mtime "+$MOLE_LOG_AGE_DAYS" 2> /dev/null | wc -l | tr -d ' ')
# Timeout protection: prevent find from hanging on large mail directories
local file_count=$(run_with_timeout 15 sh -c "find \"$target_path\" -type f -mtime \"+$MOLE_LOG_AGE_DAYS\" 2> /dev/null | wc -l | tr -d ' '")
[[ -z "$file_count" || ! "$file_count" =~ ^[0-9]+$ ]] && file_count=0
if [[ "$file_count" -gt 0 ]]; then
safe_find_delete "$target_path" "*" "$MOLE_LOG_AGE_DAYS" "f"
deleted=$((deleted + file_count))
@@ -238,7 +240,7 @@ opt_saved_state_cleanup() {
if safe_remove "$state_path" true; then
((deleted++))
fi
done < <(find "$state_dir" -type d -name "*.savedState" -mtime "+$MOLE_SAVED_STATE_AGE_DAYS" -print0 2> /dev/null)
done < <(command find "$state_dir" -type d -name "*.savedState" -mtime "+$MOLE_SAVED_STATE_AGE_DAYS" -print0 2> /dev/null)
if [[ $deleted -gt 0 ]]; then
echo -e "${GREEN}${ICON_SUCCESS}${NC} Removed $deleted old saved state(s)"
@@ -448,7 +450,7 @@ get_disk_info() {
local home="${HOME:-/}"
local df_output total_gb used_gb used_percent
df_output=$(df -k "$home" 2> /dev/null | tail -1)
df_output=$(command df -k "$home" 2> /dev/null | tail -1)
local total_kb used_kb
total_kb=$(echo "$df_output" | awk '{print $2}' 2> /dev/null || echo "0")

View File

@@ -120,8 +120,13 @@ batch_uninstall_applications() {
[[ -z "$selected_app" ]] && continue
IFS='|' read -r _ app_path app_name bundle_id _ _ <<< "$selected_app"
# Check if app is running (use app path for precise matching)
if pgrep -f "$app_path" > /dev/null 2>&1; then
# Check if app is running using executable name from bundle
local exec_name=""
if [[ -e "$app_path/Contents/Info.plist" ]]; then
exec_name=$(defaults read "$app_path/Contents/Info.plist" CFBundleExecutable 2> /dev/null || echo "")
fi
local check_pattern="${exec_name:-$app_name}"
if pgrep -x "$check_pattern" > /dev/null 2>&1; then
running_apps+=("$app_name")
fi

2
mole
View File

@@ -22,7 +22,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib/core/common.sh"
# Version info
VERSION="1.11.19"
VERSION="1.11.20"
MOLE_TAGLINE="can dig deep to clean your Mac."
# Check if Touch ID is already configured