mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 15:04:42 +00:00
Merge branch 'dev'
This commit is contained in:
399
bin/clean.sh
399
bin/clean.sh
@@ -4,6 +4,10 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Fix locale issues (avoid Perl warnings on non-English systems)
|
||||
export LC_ALL=C
|
||||
export LANG=C
|
||||
|
||||
# Get script directory and source common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/../lib/common.sh"
|
||||
@@ -12,18 +16,47 @@ source "$SCRIPT_DIR/../lib/common.sh"
|
||||
SYSTEM_CLEAN=false
|
||||
DRY_RUN=false
|
||||
IS_M_SERIES=$([ "$(uname -m)" = "arm64" ] && echo "true" || echo "false")
|
||||
|
||||
# Constants
|
||||
readonly MAX_PARALLEL_JOBS=15 # Maximum parallel background jobs
|
||||
readonly TEMP_FILE_AGE_DAYS=7 # Age threshold for temp file cleanup
|
||||
readonly ORPHAN_AGE_DAYS=60 # Age threshold for orphaned data
|
||||
readonly SIZE_1GB_KB=1048576 # 1GB in kilobytes
|
||||
readonly SIZE_1MB_KB=1024 # 1MB in kilobytes
|
||||
# Default whitelist patterns to avoid removing critical caches (can be extended by user)
|
||||
WHITELIST_PATTERNS=(
|
||||
"$HOME/Library/Caches/ms-playwright*"
|
||||
"$HOME/.cache/huggingface*"
|
||||
)
|
||||
WHITELIST_WARNINGS=()
|
||||
|
||||
# Load user-defined whitelist
|
||||
if [[ -f "$HOME/.config/mole/whitelist" ]]; then
|
||||
while IFS= read -r line; do
|
||||
# Trim whitespace
|
||||
line="${line#${line%%[![:space:]]*}}"
|
||||
line="${line%${line##*[![:space:]]}}"
|
||||
|
||||
# Skip empty lines and comments
|
||||
[[ -z "$line" || "$line" =~ ^# ]] && continue
|
||||
|
||||
# Expand tilde to home directory
|
||||
[[ "$line" == ~* ]] && line="${line/#~/$HOME}"
|
||||
|
||||
# Validate path format (allow safe characters only)
|
||||
if [[ ! "$line" =~ ^[a-zA-Z0-9/_.\*~\ @-]+$ ]]; then
|
||||
WHITELIST_WARNINGS+=("Invalid chars: $line")
|
||||
continue
|
||||
fi
|
||||
|
||||
# Prevent absolute path to critical system directories
|
||||
case "$line" in
|
||||
/System/*|/bin/*|/sbin/*|/usr/bin/*|/usr/sbin/*)
|
||||
WHITELIST_WARNINGS+=("System path: $line")
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
|
||||
WHITELIST_PATTERNS+=("$line")
|
||||
done < "$HOME/.config/mole/whitelist"
|
||||
fi
|
||||
@@ -44,18 +77,54 @@ note_activity() {
|
||||
}
|
||||
|
||||
# Cleanup background processes
|
||||
CLEANUP_DONE=false
|
||||
cleanup() {
|
||||
if [[ -n "$SUDO_KEEPALIVE_PID" ]]; then
|
||||
kill "$SUDO_KEEPALIVE_PID" 2>/dev/null || true
|
||||
SUDO_KEEPALIVE_PID=""
|
||||
local signal="${1:-EXIT}"
|
||||
local exit_code="${2:-$?}"
|
||||
|
||||
# Prevent multiple executions
|
||||
if [[ "$CLEANUP_DONE" == "true" ]]; then
|
||||
return 0
|
||||
fi
|
||||
CLEANUP_DONE=true
|
||||
|
||||
# Stop all spinners and clear the line
|
||||
if [[ -n "$SPINNER_PID" ]]; then
|
||||
kill "$SPINNER_PID" 2>/dev/null || true
|
||||
wait "$SPINNER_PID" 2>/dev/null || true
|
||||
SPINNER_PID=""
|
||||
fi
|
||||
|
||||
if [[ -n "$INLINE_SPINNER_PID" ]]; then
|
||||
kill "$INLINE_SPINNER_PID" 2>/dev/null || true
|
||||
wait "$INLINE_SPINNER_PID" 2>/dev/null || true
|
||||
INLINE_SPINNER_PID=""
|
||||
fi
|
||||
|
||||
# Clear any spinner output
|
||||
if [[ -t 1 ]]; then
|
||||
printf "\r\033[K"
|
||||
fi
|
||||
|
||||
# Stop sudo keepalive
|
||||
if [[ -n "$SUDO_KEEPALIVE_PID" ]]; then
|
||||
kill "$SUDO_KEEPALIVE_PID" 2>/dev/null || true
|
||||
wait "$SUDO_KEEPALIVE_PID" 2>/dev/null || true
|
||||
SUDO_KEEPALIVE_PID=""
|
||||
fi
|
||||
|
||||
show_cursor
|
||||
|
||||
# If interrupted, show message
|
||||
if [[ "$signal" == "INT" ]] || [[ $exit_code -eq 130 ]]; then
|
||||
printf "\r\033[K"
|
||||
echo -e "${YELLOW}Interrupted by user${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT INT TERM
|
||||
trap 'cleanup EXIT $?' EXIT
|
||||
trap 'cleanup INT 130; exit 130' INT
|
||||
trap 'cleanup TERM 143; exit 143' TERM
|
||||
|
||||
# Loading animation functions
|
||||
SPINNER_PID=""
|
||||
@@ -156,20 +225,25 @@ safe_clean() {
|
||||
|
||||
# Show progress indicator for potentially slow operations
|
||||
if [[ ${#existing_paths[@]} -gt 3 ]]; then
|
||||
[[ -t 1 ]] && echo -ne " ${BLUE}◎${NC} Checking $description with whitelist safety...\r"
|
||||
local temp_dir=$(mktemp -d)
|
||||
if [[ -t 1 ]]; then MOLE_SPINNER_PREFIX=" " start_inline_spinner "Checking items with whitelist safety..."; fi
|
||||
local temp_dir=$(create_temp_dir)
|
||||
|
||||
# Parallel processing (bash 3.2 compatible)
|
||||
local -a pids=()
|
||||
local idx=0
|
||||
for path in "${existing_paths[@]}"; do
|
||||
(
|
||||
local size=$(du -sk "$path" 2>/dev/null | awk '{print $1}' || echo "0")
|
||||
local count=$(find "$path" -type f 2>/dev/null | wc -l | tr -d ' ')
|
||||
echo "$size $count" > "$temp_dir/$(echo -n "$path" | shasum -a 256 | cut -d' ' -f1)"
|
||||
# Use index + PID for unique filename
|
||||
local tmp_file="$temp_dir/result_${idx}.$$"
|
||||
echo "$size $count" > "$tmp_file"
|
||||
mv "$tmp_file" "$temp_dir/result_${idx}" 2>/dev/null || true
|
||||
) &
|
||||
pids+=($!)
|
||||
((idx++))
|
||||
|
||||
if (( ${#pids[@]} >= 15 )); then
|
||||
if (( ${#pids[@]} >= MAX_PARALLEL_JOBS )); then
|
||||
wait "${pids[0]}" 2>/dev/null || true
|
||||
pids=("${pids[@]:1}")
|
||||
fi
|
||||
@@ -179,10 +253,12 @@ safe_clean() {
|
||||
wait "$pid" 2>/dev/null || true
|
||||
done
|
||||
|
||||
# Read results using same index
|
||||
idx=0
|
||||
for path in "${existing_paths[@]}"; do
|
||||
local hash=$(echo -n "$path" | shasum -a 256 | cut -d' ' -f1)
|
||||
if [[ -f "$temp_dir/$hash" ]]; then
|
||||
read -r size count < "$temp_dir/$hash"
|
||||
local result_file="$temp_dir/result_${idx}"
|
||||
if [[ -f "$result_file" ]]; then
|
||||
read -r size count < "$result_file" 2>/dev/null || true
|
||||
if [[ "$count" -gt 0 && "$size" -gt 0 ]]; then
|
||||
if [[ "$DRY_RUN" != "true" ]]; then
|
||||
rm -rf "$path" 2>/dev/null || true
|
||||
@@ -192,12 +268,13 @@ safe_clean() {
|
||||
removed_any=1
|
||||
fi
|
||||
fi
|
||||
((idx++))
|
||||
done
|
||||
|
||||
rm -rf "$temp_dir"
|
||||
# Temp dir will be auto-cleaned by cleanup_temp_files
|
||||
else
|
||||
# Show progress for small batches too (simpler jobs)
|
||||
[[ -t 1 ]] && echo -ne " ${BLUE}◎${NC} Checking $description with whitelist safety...\r"
|
||||
if [[ -t 1 ]]; then MOLE_SPINNER_PREFIX=" " start_inline_spinner "Checking items with whitelist safety..."; fi
|
||||
|
||||
for path in "${existing_paths[@]}"; do
|
||||
local size_bytes=$(du -sk "$path" 2>/dev/null | awk '{print $1}' || echo "0")
|
||||
@@ -214,18 +291,12 @@ safe_clean() {
|
||||
done
|
||||
fi
|
||||
|
||||
# Clear progress indicator before showing result
|
||||
[[ -t 1 ]] && echo -ne "\r\033[K"
|
||||
# Clear progress / stop spinner before showing result
|
||||
if [[ -t 1 ]]; then stop_inline_spinner; echo -ne "\r\033[K"; fi
|
||||
|
||||
if [[ $removed_any -eq 1 ]]; then
|
||||
local size_human
|
||||
if [[ $total_size_bytes -gt 1048576 ]]; then # > 1GB
|
||||
size_human=$(echo "$total_size_bytes" | awk '{printf "%.1fGB", $1/1024/1024}')
|
||||
elif [[ $total_size_bytes -gt 1024 ]]; then # > 1MB
|
||||
size_human=$(echo "$total_size_bytes" | awk '{printf "%.1fMB", $1/1024}')
|
||||
else
|
||||
size_human="${total_size_bytes}KB"
|
||||
fi
|
||||
# Convert KB to bytes for bytes_to_human()
|
||||
local size_human=$(bytes_to_human "$((total_size_bytes * 1024))")
|
||||
|
||||
local label="$description"
|
||||
if [[ ${#targets[@]} -gt 1 ]]; then
|
||||
@@ -250,7 +321,7 @@ safe_clean() {
|
||||
start_cleanup() {
|
||||
clear
|
||||
printf '\n'
|
||||
echo -e "${PURPLE}🧹 Clean Your Mac${NC}"
|
||||
echo -e "${PURPLE}Clean Your Mac${NC}"
|
||||
if [[ "$DRY_RUN" != "true" && -t 0 ]]; then
|
||||
printf '\n'
|
||||
echo -e "${YELLOW}Tip:${NC} Safety first—run 'mo clean --dry-run'. Important Macs should stop."
|
||||
@@ -258,50 +329,65 @@ start_cleanup() {
|
||||
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}🧪 Dry Run mode:${NC} showing what would be removed (no deletions)."
|
||||
echo -e "${YELLOW}Dry Run mode:${NC} showing what would be removed (no deletions)."
|
||||
echo ""
|
||||
SYSTEM_CLEAN=false
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -t 0 ]]; then
|
||||
printf '\n'
|
||||
echo -e "${BLUE}System cleanup? Password to include (Enter skips)${NC}"
|
||||
printf "${BLUE}> ${NC}"
|
||||
read -s password
|
||||
echo ""
|
||||
echo -ne "${BLUE}System cleanup? ${GRAY}Enter to continue, any key to skip${NC} "
|
||||
|
||||
# Use IFS= and read without -n to allow Ctrl+C to work properly
|
||||
IFS= read -r -s -n 1 choice
|
||||
local read_status=$?
|
||||
echo ""
|
||||
|
||||
if [[ -n "$password" ]] && echo "$password" | sudo -S true 2>/dev/null; then
|
||||
SYSTEM_CLEAN=true
|
||||
# Start sudo keepalive with error handling
|
||||
(
|
||||
local retry_count=0
|
||||
while true; do
|
||||
if ! sudo -n true 2>/dev/null; then
|
||||
((retry_count++))
|
||||
if [[ $retry_count -ge 3 ]]; then
|
||||
exit 1
|
||||
# If read was interrupted (Ctrl+C), exit cleanly
|
||||
if [[ $read_status -ne 0 ]]; then
|
||||
exit 130
|
||||
fi
|
||||
|
||||
# Enter or y = yes, do system cleanup
|
||||
if [[ -z "$choice" ]] || [[ "$choice" == $'\n' ]] || [[ "$choice" =~ ^[Yy]$ ]]; then
|
||||
echo ""
|
||||
if request_sudo_access "System cleanup requires admin access"; then
|
||||
SYSTEM_CLEAN=true
|
||||
echo -e "${GREEN}✓ Admin access granted${NC}"
|
||||
# Start sudo keepalive with error handling
|
||||
(
|
||||
local retry_count=0
|
||||
while true; do
|
||||
if ! sudo -n true 2>/dev/null; then
|
||||
((retry_count++))
|
||||
if [[ $retry_count -ge 3 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
sleep 5
|
||||
continue
|
||||
fi
|
||||
sleep 5
|
||||
continue
|
||||
fi
|
||||
retry_count=0
|
||||
sleep 30
|
||||
kill -0 "$$" 2>/dev/null || exit
|
||||
done
|
||||
) 2>/dev/null &
|
||||
SUDO_KEEPALIVE_PID=$!
|
||||
else
|
||||
SYSTEM_CLEAN=false
|
||||
if [[ -n "$password" ]]; then
|
||||
retry_count=0
|
||||
sleep 30
|
||||
kill -0 "$$" 2>/dev/null || exit
|
||||
done
|
||||
) 2>/dev/null &
|
||||
SUDO_KEEPALIVE_PID=$!
|
||||
else
|
||||
SYSTEM_CLEAN=false
|
||||
echo ""
|
||||
echo -e "${YELLOW}⚠️ Invalid password, continuing with user-level cleanup${NC}"
|
||||
echo -e "${YELLOW}Authentication failed, continuing with user-level cleanup${NC}"
|
||||
fi
|
||||
else
|
||||
# Any other key = no system cleanup
|
||||
SYSTEM_CLEAN=false
|
||||
echo ""
|
||||
echo -e "Skipped system cleanup, user-level only"
|
||||
fi
|
||||
else
|
||||
SYSTEM_CLEAN=false
|
||||
echo ""
|
||||
echo -e "${BLUE}ℹ${NC} Running in non-interactive mode"
|
||||
echo -e " Running in non-interactive mode"
|
||||
echo " • System-level cleanup skipped (requires interaction)"
|
||||
echo " • User-level cleanup will proceed automatically"
|
||||
echo ""
|
||||
@@ -310,7 +396,7 @@ start_cleanup() {
|
||||
|
||||
perform_cleanup() {
|
||||
echo ""
|
||||
echo "🍎 $(detect_architecture) | 💾 Free space: $(get_free_space)"
|
||||
echo "$(detect_architecture) | Free space: $(get_free_space)"
|
||||
|
||||
# Get initial space
|
||||
space_before=$(df / | tail -1 | awk '{print $4}')
|
||||
@@ -328,14 +414,34 @@ perform_cleanup() {
|
||||
sudo find /Library/Caches -name "*.cache" -delete 2>/dev/null || true
|
||||
sudo find /Library/Caches -name "*.tmp" -delete 2>/dev/null || true
|
||||
sudo find /Library/Caches -type f -name "*.log" -delete 2>/dev/null || true
|
||||
sudo rm -rf /tmp/* 2>/dev/null && log_success "System temp files" || true
|
||||
sudo rm -rf /var/tmp/* 2>/dev/null && log_success "System var temp" || true
|
||||
|
||||
# Clean old temp files only (avoid breaking running processes)
|
||||
local tmp_cleaned=0
|
||||
local tmp_count=$(sudo find /tmp -type f -mtime +${TEMP_FILE_AGE_DAYS} 2>/dev/null | wc -l | tr -d ' ')
|
||||
if [[ "$tmp_count" -gt 0 ]]; then
|
||||
sudo find /tmp -type f -mtime +${TEMP_FILE_AGE_DAYS} -delete 2>/dev/null || true
|
||||
tmp_cleaned=1
|
||||
fi
|
||||
local var_tmp_count=$(sudo find /var/tmp -type f -mtime +${TEMP_FILE_AGE_DAYS} 2>/dev/null | wc -l | tr -d ' ')
|
||||
if [[ "$var_tmp_count" -gt 0 ]]; then
|
||||
sudo find /var/tmp -type f -mtime +${TEMP_FILE_AGE_DAYS} -delete 2>/dev/null || true
|
||||
tmp_cleaned=1
|
||||
fi
|
||||
[[ $tmp_cleaned -eq 1 ]] && log_success "Old system temp files (${TEMP_FILE_AGE_DAYS}+ days)"
|
||||
|
||||
sudo rm -rf /Library/Updates/* 2>/dev/null || true
|
||||
log_success "System library caches and updates"
|
||||
|
||||
end_section
|
||||
fi
|
||||
|
||||
# Show whitelist warnings if any
|
||||
if [[ ${#WHITELIST_WARNINGS[@]} -gt 0 ]]; then
|
||||
echo ""
|
||||
for warning in "${WHITELIST_WARNINGS[@]}"; do
|
||||
echo -e " ${YELLOW}☼${NC} Whitelist: $warning"
|
||||
done
|
||||
fi
|
||||
|
||||
# ===== 2. User essentials =====
|
||||
start_section "System essentials"
|
||||
@@ -343,11 +449,22 @@ perform_cleanup() {
|
||||
safe_clean ~/Library/Logs/* "User app logs"
|
||||
safe_clean ~/.Trash/* "Trash"
|
||||
|
||||
# Empty the trash on all mounted volumes
|
||||
# Empty trash on mounted volumes (skip network/readonly volumes)
|
||||
if [[ -d "/Volumes" ]]; then
|
||||
for volume in /Volumes/*; do
|
||||
if [[ -d "$volume" && -d "$volume/.Trashes" ]]; then
|
||||
find "$volume/.Trashes" -mindepth 1 -maxdepth 1 -exec rm -rf {} \; 2>/dev/null || true
|
||||
[[ -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}')
|
||||
case "$fs_type" in
|
||||
nfs|smbfs|afpfs|cifs|webdav) continue ;;
|
||||
esac
|
||||
|
||||
# Verify volume is mounted
|
||||
if mount | grep -q "on $volume "; then
|
||||
if [[ "$DRY_RUN" != "true" ]]; then
|
||||
find "$volume/.Trashes" -mindepth 1 -maxdepth 1 -exec rm -rf {} \; 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
@@ -449,10 +566,11 @@ perform_cleanup() {
|
||||
start_section "Developer tools"
|
||||
# Node.js ecosystem
|
||||
if command -v npm >/dev/null 2>&1; then
|
||||
[[ -t 1 ]] && echo -ne " ${BLUE}◎${NC} Cleaning npm cache...\r"
|
||||
npm cache clean --force >/dev/null 2>&1 || true
|
||||
[[ -t 1 ]] && echo -ne "\r\033[K"
|
||||
echo -e " ${GREEN}✓${NC} npm cache cleaned"
|
||||
if [[ "$DRY_RUN" != "true" ]]; then
|
||||
clean_tool_cache "npm cache" npm cache clean --force
|
||||
else
|
||||
echo -e " ${YELLOW}→${NC} npm cache (would clean)"
|
||||
fi
|
||||
note_activity
|
||||
fi
|
||||
|
||||
@@ -463,10 +581,11 @@ perform_cleanup() {
|
||||
|
||||
# Python ecosystem
|
||||
if command -v pip3 >/dev/null 2>&1; then
|
||||
[[ -t 1 ]] && echo -ne " ${BLUE}◎${NC} Cleaning pip cache...\r"
|
||||
pip3 cache purge >/dev/null 2>&1 || true
|
||||
[[ -t 1 ]] && echo -ne "\r\033[K"
|
||||
echo -e " ${GREEN}✓${NC} pip cache cleaned"
|
||||
if [[ "$DRY_RUN" != "true" ]]; then
|
||||
clean_tool_cache "pip cache" pip3 cache purge
|
||||
else
|
||||
echo -e " ${YELLOW}→${NC} pip cache (would clean)"
|
||||
fi
|
||||
note_activity
|
||||
fi
|
||||
|
||||
@@ -476,11 +595,11 @@ perform_cleanup() {
|
||||
|
||||
# Go ecosystem
|
||||
if command -v go >/dev/null 2>&1; then
|
||||
[[ -t 1 ]] && echo -ne " ${BLUE}◎${NC} Cleaning Go cache...\r"
|
||||
go clean -modcache >/dev/null 2>&1 || true
|
||||
go clean -cache >/dev/null 2>&1 || true
|
||||
[[ -t 1 ]] && echo -ne "\r\033[K"
|
||||
echo -e " ${GREEN}✓${NC} Go cache cleaned"
|
||||
if [[ "$DRY_RUN" != "true" ]]; then
|
||||
clean_tool_cache "Go cache" bash -c 'go clean -modcache >/dev/null 2>&1 || true; go clean -cache >/dev/null 2>&1 || true'
|
||||
else
|
||||
echo -e " ${YELLOW}→${NC} Go cache (would clean)"
|
||||
fi
|
||||
note_activity
|
||||
fi
|
||||
|
||||
@@ -492,10 +611,11 @@ perform_cleanup() {
|
||||
|
||||
# Docker (only clean build cache, preserve images and volumes)
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
[[ -t 1 ]] && echo -ne " ${BLUE}◎${NC} Cleaning Docker build cache...\r"
|
||||
docker builder prune -af >/dev/null 2>&1 || true
|
||||
[[ -t 1 ]] && echo -ne "\r\033[K"
|
||||
echo -e " ${GREEN}✓${NC} Docker build cache cleaned"
|
||||
if [[ "$DRY_RUN" != "true" ]]; then
|
||||
clean_tool_cache "Docker build cache" docker builder prune -af
|
||||
else
|
||||
echo -e " ${YELLOW}→${NC} Docker build cache (would clean)"
|
||||
fi
|
||||
note_activity
|
||||
fi
|
||||
|
||||
@@ -513,10 +633,11 @@ perform_cleanup() {
|
||||
safe_clean /opt/homebrew/var/homebrew/locks/* "Homebrew lock files (M series)"
|
||||
safe_clean /usr/local/var/homebrew/locks/* "Homebrew lock files (Intel)"
|
||||
if command -v brew >/dev/null 2>&1; then
|
||||
[[ -t 1 ]] && echo -ne " ${BLUE}◎${NC} Cleaning Homebrew...\r"
|
||||
brew cleanup >/dev/null 2>&1 || true
|
||||
[[ -t 1 ]] && echo -ne "\r\033[K"
|
||||
echo -e " ${GREEN}✓${NC} Homebrew cache cleaned"
|
||||
if [[ "$DRY_RUN" != "true" ]]; then
|
||||
clean_tool_cache "Homebrew cleanup" brew cleanup
|
||||
else
|
||||
echo -e " ${YELLOW}→${NC} Homebrew (would cleanup)"
|
||||
fi
|
||||
note_activity
|
||||
fi
|
||||
|
||||
@@ -870,13 +991,13 @@ perform_cleanup() {
|
||||
# Safeguards: retains unusual locations in use, recent apps, and critical/licensed data
|
||||
start_section "Orphaned app data cleanup"
|
||||
|
||||
local -r ORPHAN_AGE_THRESHOLD=60
|
||||
local -r ORPHAN_AGE_THRESHOLD=$ORPHAN_AGE_DAYS
|
||||
|
||||
# Build a comprehensive list of installed application bundle identifiers
|
||||
echo -n " ${BLUE}◎${NC} Scanning installed applications..."
|
||||
local installed_bundles=$(mktemp)
|
||||
local running_bundles=$(mktemp)
|
||||
local launch_agents=$(mktemp)
|
||||
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Scanning installed applications..." # ensure spinner function exists above
|
||||
local installed_bundles=$(create_temp_file)
|
||||
local running_bundles=$(create_temp_file)
|
||||
local launch_agents=$(create_temp_file)
|
||||
|
||||
# Scan multiple possible application locations to avoid false positives
|
||||
local -a search_paths=(
|
||||
@@ -886,18 +1007,26 @@ perform_cleanup() {
|
||||
"/System/Library/CoreServices/Applications"
|
||||
"/Library/Application Support"
|
||||
"$HOME/Library/Application Support"
|
||||
"/Users/Shared/Applications"
|
||||
"/Applications/Utilities"
|
||||
)
|
||||
|
||||
# Add Homebrew paths if they exist
|
||||
[[ -d "/opt/homebrew/Caskroom" ]] && search_paths+=("/opt/homebrew/Caskroom")
|
||||
[[ -d "/usr/local/Caskroom" ]] && search_paths+=("/usr/local/Caskroom")
|
||||
[[ -d "/opt/homebrew/Cellar" ]] && search_paths+=("/opt/homebrew/Cellar")
|
||||
[[ -d "/usr/local/Cellar" ]] && search_paths+=("/usr/local/Cellar")
|
||||
|
||||
# Add common developer paths
|
||||
[[ -d "$HOME/Developer" ]] && search_paths+=("$HOME/Developer")
|
||||
[[ -d "$HOME/Projects" ]] && search_paths+=("$HOME/Projects")
|
||||
[[ -d "$HOME/Downloads" ]] && search_paths+=("$HOME/Downloads")
|
||||
|
||||
# Add other common third-party install locations
|
||||
[[ -d "/opt/apps" ]] && search_paths+=("/opt/apps")
|
||||
[[ -d "/opt/local/Applications" ]] && search_paths+=("/opt/local/Applications")
|
||||
[[ -d "/usr/local/apps" ]] && search_paths+=("/usr/local/apps")
|
||||
|
||||
# Scan for .app bundles in all search paths (with depth limit for performance)
|
||||
for search_path in "${search_paths[@]}"; do
|
||||
if [[ -d "$search_path" ]]; then
|
||||
@@ -905,10 +1034,20 @@ 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 < <(find "$search_path" -type d -name "*.app" 2>/dev/null || true)
|
||||
done < <(find "$search_path" -maxdepth 3 -type d -name "*.app" 2>/dev/null || true)
|
||||
fi
|
||||
done
|
||||
|
||||
# Use Spotlight as fallback to catch apps in unusual locations
|
||||
# This significantly reduces false positives
|
||||
if command -v mdfind >/dev/null 2>&1; then
|
||||
while IFS= read -r app; do
|
||||
[[ -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 < <(mdfind "kMDItemKind == 'Application'" 2>/dev/null | grep "\.app$" || true)
|
||||
fi
|
||||
|
||||
# Get running applications (if an app is running, it's definitely not orphaned)
|
||||
local running_apps=$(osascript -e 'tell application "System Events" to get bundle identifier of every application process' 2>/dev/null || echo "")
|
||||
echo "$running_apps" | tr ',' '\n' | sed 's/^ *//;s/ *$//' | grep -v '^$' > "$running_bundles"
|
||||
@@ -925,7 +1064,8 @@ perform_cleanup() {
|
||||
mv "${installed_bundles}.final" "$installed_bundles"
|
||||
|
||||
local app_count=$(wc -l < "$installed_bundles" | tr -d ' ')
|
||||
echo " ${GREEN}✓${NC} Found $app_count active/installed apps"
|
||||
stop_inline_spinner
|
||||
echo -e " ${GREEN}✓${NC} Found $app_count active/installed apps"
|
||||
|
||||
# Track statistics
|
||||
local orphaned_count=0
|
||||
@@ -983,7 +1123,7 @@ perform_cleanup() {
|
||||
}
|
||||
|
||||
# Clean orphaned caches
|
||||
echo -n " ${BLUE}◎${NC} Scanning orphaned caches..."
|
||||
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Scanning orphaned caches..."
|
||||
local cache_found=0
|
||||
if ls ~/Library/Caches/com.* >/dev/null 2>&1; then
|
||||
for cache_dir in ~/Library/Caches/com.* ~/Library/Caches/org.* ~/Library/Caches/net.* ~/Library/Caches/io.*; do
|
||||
@@ -999,10 +1139,11 @@ perform_cleanup() {
|
||||
fi
|
||||
done
|
||||
fi
|
||||
echo " ${GREEN}✓${NC} Found $cache_found orphaned caches"
|
||||
stop_inline_spinner
|
||||
echo -e " ${GREEN}✓${NC} Found $cache_found orphaned caches"
|
||||
|
||||
# Clean orphaned logs
|
||||
echo -n " ${BLUE}◎${NC} Scanning orphaned logs..."
|
||||
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Scanning orphaned logs..."
|
||||
local logs_found=0
|
||||
if ls ~/Library/Logs/com.* >/dev/null 2>&1; then
|
||||
for log_dir in ~/Library/Logs/com.* ~/Library/Logs/org.* ~/Library/Logs/net.* ~/Library/Logs/io.*; do
|
||||
@@ -1018,10 +1159,11 @@ perform_cleanup() {
|
||||
fi
|
||||
done
|
||||
fi
|
||||
echo " ${GREEN}✓${NC} Found $logs_found orphaned log directories"
|
||||
stop_inline_spinner
|
||||
echo -e " ${GREEN}✓${NC} Found $logs_found orphaned log directories"
|
||||
|
||||
# Clean orphaned saved states
|
||||
echo -n " ${BLUE}◎${NC} Scanning orphaned saved states..."
|
||||
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Scanning orphaned saved states..."
|
||||
local states_found=0
|
||||
if ls ~/Library/Saved\ Application\ State/*.savedState >/dev/null 2>&1; then
|
||||
for state_dir in ~/Library/Saved\ Application\ State/*.savedState; do
|
||||
@@ -1037,14 +1179,15 @@ perform_cleanup() {
|
||||
fi
|
||||
done
|
||||
fi
|
||||
echo " ${GREEN}✓${NC} Found $states_found orphaned saved states"
|
||||
stop_inline_spinner
|
||||
echo -e " ${GREEN}✓${NC} Found $states_found orphaned saved states"
|
||||
|
||||
# Clean orphaned containers
|
||||
# NOTE: Container cleanup is DISABLED by default due to naming mismatch issues
|
||||
# Some apps create containers with names that don't strictly match their Bundle ID,
|
||||
# especially when system extensions are registered. This can cause false positives.
|
||||
# To avoid deleting data from installed apps, we skip container cleanup.
|
||||
echo -n " ${BLUE}◎${NC} Scanning orphaned containers..."
|
||||
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Scanning orphaned containers..."
|
||||
local containers_found=0
|
||||
if ls ~/Library/Containers/com.* >/dev/null 2>&1; then
|
||||
# Count potential orphaned containers but don't delete them
|
||||
@@ -1061,10 +1204,11 @@ perform_cleanup() {
|
||||
fi
|
||||
done
|
||||
fi
|
||||
echo " ${BLUE}○${NC} Skipped $containers_found potential orphaned containers"
|
||||
stop_inline_spinner
|
||||
echo -e " ${BLUE}○${NC} Skipped $containers_found potential orphaned containers"
|
||||
|
||||
# Clean orphaned WebKit data
|
||||
echo -n " ${BLUE}◎${NC} Scanning orphaned WebKit data..."
|
||||
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Scanning orphaned WebKit data..."
|
||||
local webkit_found=0
|
||||
if ls ~/Library/WebKit/com.* >/dev/null 2>&1; then
|
||||
for webkit_dir in ~/Library/WebKit/com.* ~/Library/WebKit/org.* ~/Library/WebKit/net.* ~/Library/WebKit/io.*; do
|
||||
@@ -1080,10 +1224,11 @@ perform_cleanup() {
|
||||
fi
|
||||
done
|
||||
fi
|
||||
echo " ${GREEN}✓${NC} Found $webkit_found orphaned WebKit data"
|
||||
stop_inline_spinner
|
||||
echo -e " ${GREEN}✓${NC} Found $webkit_found orphaned WebKit data"
|
||||
|
||||
# Clean orphaned HTTP storages
|
||||
echo -n " ${BLUE}◎${NC} Scanning orphaned HTTP storages..."
|
||||
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Scanning orphaned HTTP storages..."
|
||||
local http_found=0
|
||||
if ls ~/Library/HTTPStorages/com.* >/dev/null 2>&1; then
|
||||
for http_dir in ~/Library/HTTPStorages/com.* ~/Library/HTTPStorages/org.* ~/Library/HTTPStorages/net.* ~/Library/HTTPStorages/io.*; do
|
||||
@@ -1099,10 +1244,11 @@ perform_cleanup() {
|
||||
fi
|
||||
done
|
||||
fi
|
||||
echo " ${GREEN}✓${NC} Found $http_found orphaned HTTP storages"
|
||||
stop_inline_spinner
|
||||
echo -e " ${GREEN}✓${NC} Found $http_found orphaned HTTP storages"
|
||||
|
||||
# Clean orphaned cookies
|
||||
echo -n " ${BLUE}◎${NC} Scanning orphaned cookies..."
|
||||
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Scanning orphaned cookies..."
|
||||
local cookies_found=0
|
||||
if ls ~/Library/Cookies/*.binarycookies >/dev/null 2>&1; then
|
||||
for cookie_file in ~/Library/Cookies/*.binarycookies; do
|
||||
@@ -1118,7 +1264,8 @@ perform_cleanup() {
|
||||
fi
|
||||
done
|
||||
fi
|
||||
echo " ${GREEN}✓${NC} Found $cookies_found orphaned cookie files"
|
||||
stop_inline_spinner
|
||||
echo -e " ${GREEN}✓${NC} Found $cookies_found orphaned cookie files"
|
||||
|
||||
# Calculate total
|
||||
orphaned_count=$((cache_found + logs_found + states_found + containers_found + webkit_found + http_found + cookies_found))
|
||||
@@ -1156,8 +1303,8 @@ perform_cleanup() {
|
||||
if [[ -n "${backup_kb:-}" && "$backup_kb" -gt 102400 ]]; then
|
||||
backup_human=$(du -sh "$backup_dir" 2>/dev/null | awk '{print $1}')
|
||||
note_activity
|
||||
echo -e " ${BLUE}💾${NC} Found ${GREEN}${backup_human}${NC} iOS backups"
|
||||
echo -e " ${YELLOW}💡${NC} You can delete them manually: ${backup_dir}"
|
||||
echo -e " Found ${GREEN}${backup_human}${NC} iOS backups"
|
||||
echo -e " You can delete them manually: ${backup_dir}"
|
||||
fi
|
||||
fi
|
||||
end_section
|
||||
@@ -1169,67 +1316,45 @@ perform_cleanup() {
|
||||
echo ""
|
||||
echo "===================================================================="
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
echo "🧪 DRY RUN COMPLETE!"
|
||||
echo "DRY RUN COMPLETE!"
|
||||
else
|
||||
echo "🎉 CLEANUP COMPLETE!"
|
||||
echo "CLEANUP COMPLETE!"
|
||||
fi
|
||||
|
||||
if [[ $total_size_cleaned -gt 0 ]]; then
|
||||
local freed_gb=$(echo "$total_size_cleaned" | awk '{printf "%.2f", $1/1024/1024}')
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
echo "💾 Potential reclaimable space: ${GREEN}${freed_gb}GB${NC} (no changes made) | Free space now: $(get_free_space)"
|
||||
echo "Potential reclaimable space: ${GREEN}${freed_gb}GB${NC} (no changes made) | Free space now: $(get_free_space)"
|
||||
else
|
||||
echo "💾 Space freed: ${GREEN}${freed_gb}GB${NC} | Free space now: $(get_free_space)"
|
||||
echo "Space freed: ${GREEN}${freed_gb}GB${NC} | Free space now: $(get_free_space)"
|
||||
fi
|
||||
|
||||
if [[ "$DRY_RUN" != "true" ]]; then
|
||||
if [[ $(echo "$freed_gb" | awk '{print ($1 >= 1) ? 1 : 0}') -eq 1 ]]; then
|
||||
local movies=$(echo "$freed_gb" | awk '{printf "%.0f", $1/4.5}')
|
||||
if [[ $movies -gt 0 ]]; then
|
||||
echo "🎬 That's like ~$movies 4K movies worth of space!"
|
||||
echo "That's like ~$movies 4K movies worth of space!"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
echo "💾 No significant reclaimable space detected (already clean) | Free space: $(get_free_space)"
|
||||
echo "No significant reclaimable space detected (already clean) | Free space: $(get_free_space)"
|
||||
else
|
||||
echo "💾 No significant space was freed (system was already clean) | Free space: $(get_free_space)"
|
||||
echo "No significant space was freed (system was already clean) | Free space: $(get_free_space)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $files_cleaned -gt 0 && $total_items -gt 0 ]]; then
|
||||
echo "📊 Files cleaned: $files_cleaned | Categories processed: $total_items"
|
||||
printf "Files cleaned: %s | Categories processed: %s\n" "$files_cleaned" "$total_items"
|
||||
elif [[ $files_cleaned -gt 0 ]]; then
|
||||
echo "📊 Files cleaned: $files_cleaned"
|
||||
printf "Files cleaned: %s\n" "$files_cleaned"
|
||||
elif [[ $total_items -gt 0 ]]; then
|
||||
echo "🗂️ Categories processed: $total_items"
|
||||
printf "Categories processed: %s\n" "$total_items"
|
||||
fi
|
||||
|
||||
if [[ "$SYSTEM_CLEAN" != "true" ]]; then
|
||||
echo ""
|
||||
echo -e "${BLUE}💡 For deeper cleanup, run with admin password next time${NC}"
|
||||
fi
|
||||
|
||||
echo "===================================================================="
|
||||
printf "====================================================================\n"
|
||||
}
|
||||
|
||||
# Cleanup function - restore cursor on exit
|
||||
cleanup() {
|
||||
# Restore cursor
|
||||
show_cursor
|
||||
# Kill any background processes
|
||||
if [[ -n "${SUDO_KEEPALIVE_PID:-}" ]]; then
|
||||
kill "$SUDO_KEEPALIVE_PID" 2>/dev/null || true
|
||||
fi
|
||||
if [[ -n "${SPINNER_PID:-}" ]]; then
|
||||
kill "$SPINNER_PID" 2>/dev/null || true
|
||||
fi
|
||||
exit "${1:-0}"
|
||||
}
|
||||
|
||||
# Set trap for cleanup on exit
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
main() {
|
||||
# Parse args (only dry-run and help for minimal impact)
|
||||
@@ -1259,8 +1384,8 @@ main() {
|
||||
esac
|
||||
done
|
||||
|
||||
hide_cursor
|
||||
start_cleanup
|
||||
hide_cursor
|
||||
perform_cleanup
|
||||
show_cursor
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user