mirror of
https://github.com/tw93/Mole.git
synced 2026-02-12 19:15:13 +00:00
✨ Adding more protection
This commit is contained in:
130
bin/clean.sh
130
bin/clean.sh
@@ -17,14 +17,12 @@ WHITELIST_PATTERNS=(
|
|||||||
"$HOME/Library/Caches/ms-playwright*"
|
"$HOME/Library/Caches/ms-playwright*"
|
||||||
"$HOME/.cache/huggingface*"
|
"$HOME/.cache/huggingface*"
|
||||||
)
|
)
|
||||||
# Load user-defined whitelist file if present (~/.config/mole/whitelist)
|
# Load user-defined whitelist
|
||||||
if [[ -f "$HOME/.config/mole/whitelist" ]]; then
|
if [[ -f "$HOME/.config/mole/whitelist" ]]; then
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
# Trim leading/trailing whitespace without relying on external tools
|
|
||||||
line="${line#${line%%[![:space:]]*}}"
|
line="${line#${line%%[![:space:]]*}}"
|
||||||
line="${line%${line##*[![:space:]]}}"
|
line="${line%${line##*[![:space:]]}}"
|
||||||
[[ -z "$line" || "$line" =~ ^# ]] && continue
|
[[ -z "$line" || "$line" =~ ^# ]] && continue
|
||||||
# Expand leading ~ for user convenience
|
|
||||||
[[ "$line" == ~* ]] && line="${line/#~/$HOME}"
|
[[ "$line" == ~* ]] && line="${line/#~/$HOME}"
|
||||||
WHITELIST_PATTERNS+=("$line")
|
WHITELIST_PATTERNS+=("$line")
|
||||||
done < "$HOME/.config/mole/whitelist"
|
done < "$HOME/.config/mole/whitelist"
|
||||||
@@ -64,17 +62,12 @@ SPINNER_PID=""
|
|||||||
start_spinner() {
|
start_spinner() {
|
||||||
local message="$1"
|
local message="$1"
|
||||||
|
|
||||||
# Check if we're in an interactive terminal
|
|
||||||
if [[ ! -t 1 ]]; then
|
if [[ ! -t 1 ]]; then
|
||||||
# Non-interactive, just show static message
|
|
||||||
echo -n " ${BLUE}🔍${NC} $message"
|
echo -n " ${BLUE}🔍${NC} $message"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Display message without newline
|
|
||||||
echo -n " ${BLUE}🔍${NC} $message"
|
echo -n " ${BLUE}🔍${NC} $message"
|
||||||
|
|
||||||
# Start simple dots animation for interactive terminals
|
|
||||||
(
|
(
|
||||||
local delay=0.5
|
local delay=0.5
|
||||||
while true; do
|
while true; do
|
||||||
@@ -95,7 +88,6 @@ stop_spinner() {
|
|||||||
local result_message="${1:-Done}"
|
local result_message="${1:-Done}"
|
||||||
|
|
||||||
if [[ ! -t 1 ]]; then
|
if [[ ! -t 1 ]]; then
|
||||||
# Non-interactive, just show result
|
|
||||||
echo " ✓ $result_message"
|
echo " ✓ $result_message"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
@@ -104,16 +96,12 @@ stop_spinner() {
|
|||||||
kill "$SPINNER_PID" 2>/dev/null
|
kill "$SPINNER_PID" 2>/dev/null
|
||||||
wait "$SPINNER_PID" 2>/dev/null
|
wait "$SPINNER_PID" 2>/dev/null
|
||||||
SPINNER_PID=""
|
SPINNER_PID=""
|
||||||
# Clear the line and show result
|
|
||||||
printf "\r ${GREEN}✓${NC} %s\n" "$result_message"
|
printf "\r ${GREEN}✓${NC} %s\n" "$result_message"
|
||||||
else
|
else
|
||||||
# No spinner was running, just show result
|
|
||||||
echo " ${GREEN}✓${NC} $result_message"
|
echo " ${GREEN}✓${NC} $result_message"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cleanup background processes on exit
|
|
||||||
|
|
||||||
start_section() {
|
start_section() {
|
||||||
TRACK_SECTION=1
|
TRACK_SECTION=1
|
||||||
SECTION_ACTIVITY=0
|
SECTION_ACTIVITY=0
|
||||||
@@ -148,11 +136,9 @@ safe_clean() {
|
|||||||
local total_size_bytes=0
|
local total_size_bytes=0
|
||||||
local total_count=0
|
local total_count=0
|
||||||
|
|
||||||
# Optimized: skip size calculation for empty checks, just try to delete
|
# Optimized parallel processing for better performance
|
||||||
# Size calculation is the slowest part - do it in parallel
|
|
||||||
local -a existing_paths=()
|
local -a existing_paths=()
|
||||||
for path in "${targets[@]}"; do
|
for path in "${targets[@]}"; do
|
||||||
# Skip if path matches whitelist
|
|
||||||
local skip=false
|
local skip=false
|
||||||
for w in "${WHITELIST_PATTERNS[@]}"; do
|
for w in "${WHITELIST_PATTERNS[@]}"; do
|
||||||
if [[ "$path" == $w ]]; then
|
if [[ "$path" == $w ]]; then
|
||||||
@@ -168,11 +154,10 @@ safe_clean() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Fast parallel processing for multiple targets
|
|
||||||
if [[ ${#existing_paths[@]} -gt 3 ]]; then
|
if [[ ${#existing_paths[@]} -gt 3 ]]; then
|
||||||
local temp_dir=$(mktemp -d)
|
local temp_dir=$(mktemp -d)
|
||||||
|
|
||||||
# Launch parallel du jobs (bash 3.2 compatible)
|
# Parallel processing (bash 3.2 compatible)
|
||||||
local -a pids=()
|
local -a pids=()
|
||||||
for path in "${existing_paths[@]}"; do
|
for path in "${existing_paths[@]}"; do
|
||||||
(
|
(
|
||||||
@@ -182,19 +167,15 @@ safe_clean() {
|
|||||||
) &
|
) &
|
||||||
pids+=($!)
|
pids+=($!)
|
||||||
|
|
||||||
# Limit to 15 parallel jobs (bash 3.2 compatible)
|
|
||||||
if (( ${#pids[@]} >= 15 )); then
|
if (( ${#pids[@]} >= 15 )); then
|
||||||
wait "${pids[0]}" 2>/dev/null || true
|
wait "${pids[0]}" 2>/dev/null || true
|
||||||
pids=("${pids[@]:1}")
|
pids=("${pids[@]:1}")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Wait for remaining jobs
|
|
||||||
for pid in "${pids[@]}"; do
|
for pid in "${pids[@]}"; do
|
||||||
wait "$pid" 2>/dev/null || true
|
wait "$pid" 2>/dev/null || true
|
||||||
done
|
done
|
||||||
|
|
||||||
# Collect results and delete
|
|
||||||
for path in "${existing_paths[@]}"; do
|
for path in "${existing_paths[@]}"; do
|
||||||
local hash=$(echo -n "$path" | shasum -a 256 | cut -d' ' -f1)
|
local hash=$(echo -n "$path" | shasum -a 256 | cut -d' ' -f1)
|
||||||
if [[ -f "$temp_dir/$hash" ]]; then
|
if [[ -f "$temp_dir/$hash" ]]; then
|
||||||
@@ -212,7 +193,6 @@ safe_clean() {
|
|||||||
|
|
||||||
rm -rf "$temp_dir"
|
rm -rf "$temp_dir"
|
||||||
else
|
else
|
||||||
# Serial processing for few targets (faster than parallel overhead)
|
|
||||||
for path in "${existing_paths[@]}"; do
|
for path in "${existing_paths[@]}"; do
|
||||||
local size_bytes=$(du -sk "$path" 2>/dev/null | awk '{print $1}' || echo "0")
|
local size_bytes=$(du -sk "$path" 2>/dev/null | awk '{print $1}' || echo "0")
|
||||||
local count=$(find "$path" -type f 2>/dev/null | wc -l | tr -d ' ')
|
local count=$(find "$path" -type f 2>/dev/null | wc -l | tr -d ' ')
|
||||||
@@ -228,7 +208,6 @@ safe_clean() {
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Only show output if something was actually cleaned
|
|
||||||
if [[ $removed_any -eq 1 ]]; then
|
if [[ $removed_any -eq 1 ]]; then
|
||||||
local size_human
|
local size_human
|
||||||
if [[ $total_size_bytes -gt 1048576 ]]; then # > 1GB
|
if [[ $total_size_bytes -gt 1048576 ]]; then # > 1GB
|
||||||
@@ -285,7 +264,7 @@ start_cleanup() {
|
|||||||
echo -en "${BLUE}Enter admin password to enable, or press Enter to skip: ${NC}"
|
echo -en "${BLUE}Enter admin password to enable, or press Enter to skip: ${NC}"
|
||||||
read -s password
|
read -s password
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if [[ -n "$password" ]] && echo "$password" | sudo -S true 2>/dev/null; then
|
if [[ -n "$password" ]] && echo "$password" | sudo -S true 2>/dev/null; then
|
||||||
SYSTEM_CLEAN=true
|
SYSTEM_CLEAN=true
|
||||||
# Start sudo keepalive with error handling
|
# Start sudo keepalive with error handling
|
||||||
@@ -640,7 +619,8 @@ perform_cleanup() {
|
|||||||
safe_clean ~/Library/Caches/SentryCrash/* "Sentry crash reports"
|
safe_clean ~/Library/Caches/SentryCrash/* "Sentry crash reports"
|
||||||
safe_clean ~/Library/Caches/KSCrash/* "KSCrash reports"
|
safe_clean ~/Library/Caches/KSCrash/* "KSCrash reports"
|
||||||
safe_clean ~/Library/Caches/com.crashlytics.data/* "Crashlytics data"
|
safe_clean ~/Library/Caches/com.crashlytics.data/* "Crashlytics data"
|
||||||
safe_clean ~/Library/HTTPStorages/* "HTTP storage cache"
|
# Note: HTTPStorages contains cookies and login sessions, NOT safe to delete
|
||||||
|
# safe_clean ~/Library/HTTPStorages/* "HTTP storage cache"
|
||||||
|
|
||||||
end_section
|
end_section
|
||||||
|
|
||||||
@@ -765,13 +745,15 @@ perform_cleanup() {
|
|||||||
end_section
|
end_section
|
||||||
|
|
||||||
|
|
||||||
# ===== 12. Orphaned leftovers =====
|
# ===== 12. Orphaned app caches =====
|
||||||
start_section "Orphaned app files"
|
# Note: We only clean orphaned caches (regenerable) but preserve:
|
||||||
|
# - Preferences (small, contain settings users may want when reinstalling)
|
||||||
|
# - Application Support data (may contain user documents/databases)
|
||||||
|
start_section "Orphaned app caches"
|
||||||
|
|
||||||
# Build a list of installed application bundle identifiers
|
# Build a list of installed application bundle identifiers
|
||||||
echo -n " ${BLUE}🔍${NC} Scanning installed applications..."
|
echo -n " ${BLUE}🔍${NC} Scanning installed applications..."
|
||||||
local installed_bundles=$(mktemp)
|
local installed_bundles=$(mktemp)
|
||||||
# More robust approach that won't hang
|
|
||||||
for app in /Applications/*.app; do
|
for app in /Applications/*.app; do
|
||||||
if [[ -d "$app" && -f "$app/Contents/Info.plist" ]]; then
|
if [[ -d "$app" && -f "$app/Contents/Info.plist" ]]; then
|
||||||
bundle_id=$(defaults read "$app/Contents/Info.plist" CFBundleIdentifier 2>/dev/null || echo "")
|
bundle_id=$(defaults read "$app/Contents/Info.plist" CFBundleIdentifier 2>/dev/null || echo "")
|
||||||
@@ -781,13 +763,10 @@ perform_cleanup() {
|
|||||||
local app_count=$(wc -l < "$installed_bundles" | tr -d ' ')
|
local app_count=$(wc -l < "$installed_bundles" | tr -d ' ')
|
||||||
echo " ${GREEN}✓${NC} Found $app_count apps"
|
echo " ${GREEN}✓${NC} Found $app_count apps"
|
||||||
|
|
||||||
local found_orphaned=false
|
|
||||||
local cache_count=0
|
local cache_count=0
|
||||||
local data_count=0
|
|
||||||
local pref_count=0
|
|
||||||
|
|
||||||
# Check for orphaned caches (with protection for critical system settings)
|
# Check for orphaned caches (safe to remove - caches are regenerable)
|
||||||
echo -n " ${BLUE}🔍${NC} Scanning cache directories..."
|
echo -n " ${BLUE}🔍${NC} Scanning orphaned cache directories..."
|
||||||
if ls ~/Library/Caches/com.* >/dev/null 2>&1; then
|
if ls ~/Library/Caches/com.* >/dev/null 2>&1; then
|
||||||
for cache_dir in ~/Library/Caches/com.*; do
|
for cache_dir in ~/Library/Caches/com.*; do
|
||||||
[[ -d "$cache_dir" ]] || continue
|
[[ -d "$cache_dir" ]] || continue
|
||||||
@@ -798,85 +777,15 @@ perform_cleanup() {
|
|||||||
fi
|
fi
|
||||||
if ! grep -q "$bundle_id" "$installed_bundles" 2>/dev/null; then
|
if ! grep -q "$bundle_id" "$installed_bundles" 2>/dev/null; then
|
||||||
safe_clean "$cache_dir" "Orphaned cache: $bundle_id"
|
safe_clean "$cache_dir" "Orphaned cache: $bundle_id"
|
||||||
found_orphaned=true
|
|
||||||
((cache_count++))
|
((cache_count++))
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
echo " ${GREEN}✓${NC} Complete ($cache_count removed)"
|
echo " ${GREEN}✓${NC} Complete ($cache_count removed)"
|
||||||
|
|
||||||
# Check for orphaned application support data (with protection for critical system settings)
|
|
||||||
echo -n " ${BLUE}🔍${NC} Scanning application data..."
|
|
||||||
if ls ~/Library/Application\ Support/com.* >/dev/null 2>&1; then
|
|
||||||
for support_dir in ~/Library/Application\ Support/com.*; do
|
|
||||||
[[ -d "$support_dir" ]] || continue
|
|
||||||
local bundle_id=$(basename "$support_dir")
|
|
||||||
# CRITICAL: Skip system-essential and protected app data
|
|
||||||
if should_protect_data "$bundle_id"; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
if ! grep -q "$bundle_id" "$installed_bundles" 2>/dev/null; then
|
|
||||||
safe_clean "$support_dir" "Orphaned data: $bundle_id"
|
|
||||||
found_orphaned=true
|
|
||||||
((data_count++))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
# Also check for non-com.* folders that may contain user data
|
|
||||||
for support_dir in ~/Library/Application\ Support/*; do
|
|
||||||
[[ -d "$support_dir" ]] || continue
|
|
||||||
local dir_name=$(basename "$support_dir")
|
|
||||||
# Skip if it starts with com. (already processed) or is in dot directories
|
|
||||||
[[ "$dir_name" == com.* || "$dir_name" == .* ]] && continue
|
|
||||||
# CRITICAL: Protect important data folders (JetBrains, database tools, etc.)
|
|
||||||
if should_protect_data "$dir_name"; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
# Only clean if significant size and looks like app data, but be conservative
|
|
||||||
# Skip common system/user folders
|
|
||||||
case "$dir_name" in
|
|
||||||
"CrashReporter"|"AddressBook"|"CallHistoryDB"|"CallHistoryTransactions"|\
|
|
||||||
"CloudDocs"|"icdd"|"IdentityServices"|"Mail"|"CallServices"|\
|
|
||||||
"com.apple."*|"Adobe"|"Google"|"Mozilla"|"Netscape"|"Yahoo"|\
|
|
||||||
"AddressBook"|"iCloud"|"iLifeMediaBrowser"|"MobileSync"|\
|
|
||||||
"CallHistory"|"FaceTime"|"Twitter")
|
|
||||||
# System or commonly used folders, skip
|
|
||||||
continue
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
echo " ${GREEN}✓${NC} Complete ($data_count removed)"
|
|
||||||
|
|
||||||
# Check for orphaned preferences (with protection for critical system settings)
|
|
||||||
echo -n " ${BLUE}🔍${NC} Scanning preference files..."
|
|
||||||
if ls ~/Library/Preferences/com.*.plist >/dev/null 2>&1; then
|
|
||||||
for pref_file in ~/Library/Preferences/com.*.plist; do
|
|
||||||
[[ -f "$pref_file" ]] || continue
|
|
||||||
local bundle_id=$(basename "$pref_file" .plist)
|
|
||||||
# CRITICAL: Skip system-essential and protected app preferences
|
|
||||||
if should_protect_data "$bundle_id"; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
if ! grep -q "$bundle_id" "$installed_bundles" 2>/dev/null; then
|
|
||||||
safe_clean "$pref_file" "Orphaned preference: $bundle_id"
|
|
||||||
found_orphaned=true
|
|
||||||
((pref_count++))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
echo " ${GREEN}✓${NC} Complete ($pref_count removed)"
|
|
||||||
|
|
||||||
# Clean up temp file
|
# Clean up temp file
|
||||||
rm -f "$installed_bundles"
|
rm -f "$installed_bundles"
|
||||||
|
|
||||||
# Clean test data
|
|
||||||
safe_clean ~/Library/Application\ Support/TestApp* "Test app data"
|
|
||||||
safe_clean ~/Library/Application\ Support/MyApp/* "Test app data"
|
|
||||||
safe_clean ~/Library/Application\ Support/GitHub*/* "GitHub test data"
|
|
||||||
safe_clean ~/Library/Application\ Support/Twitter*/* "Twitter test data"
|
|
||||||
safe_clean ~/Library/Application\ Support/TestNoValue/* "Test data"
|
|
||||||
safe_clean ~/Library/Application\ Support/Wk*/* "Test data"
|
|
||||||
|
|
||||||
end_section
|
end_section
|
||||||
|
|
||||||
# ===== 13. Apple Silicon optimizations =====
|
# ===== 13. Apple Silicon optimizations =====
|
||||||
@@ -889,14 +798,12 @@ perform_cleanup() {
|
|||||||
end_section
|
end_section
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# System cleanup was moved to the beginning (right after password verification)
|
|
||||||
|
|
||||||
# ===== 14. iOS device backups =====
|
# ===== 14. iOS device backups =====
|
||||||
start_section "iOS device backups"
|
start_section "iOS device backups"
|
||||||
backup_dir="$HOME/Library/Application Support/MobileSync/Backup"
|
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" ]] && find "$backup_dir" -mindepth 1 -maxdepth 1 | read -r _; then
|
||||||
backup_kb=$(du -sk "$backup_dir" 2>/dev/null | awk '{print $1}')
|
backup_kb=$(du -sk "$backup_dir" 2>/dev/null | awk '{print $1}')
|
||||||
if [[ -n "${backup_kb:-}" && "$backup_kb" -gt 102400 ]]; then # >100MB
|
if [[ -n "${backup_kb:-}" && "$backup_kb" -gt 102400 ]]; then
|
||||||
backup_human=$(du -sh "$backup_dir" 2>/dev/null | awk '{print $1}')
|
backup_human=$(du -sh "$backup_dir" 2>/dev/null | awk '{print $1}')
|
||||||
note_activity
|
note_activity
|
||||||
echo -e " ${BLUE}💾${NC} Found ${GREEN}${backup_human}${NC} iOS backups"
|
echo -e " ${BLUE}💾${NC} Found ${GREEN}${backup_human}${NC} iOS backups"
|
||||||
@@ -926,7 +833,6 @@ perform_cleanup() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$DRY_RUN" != "true" ]]; then
|
if [[ "$DRY_RUN" != "true" ]]; then
|
||||||
# Add some context when actually freed
|
|
||||||
if [[ $(echo "$freed_gb" | awk '{print ($1 >= 1) ? 1 : 0}') -eq 1 ]]; 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}')
|
local movies=$(echo "$freed_gb" | awk '{printf "%.0f", $1/4.5}')
|
||||||
if [[ $movies -gt 0 ]]; then
|
if [[ $movies -gt 0 ]]; then
|
||||||
@@ -941,7 +847,7 @@ perform_cleanup() {
|
|||||||
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
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $files_cleaned -gt 0 && $total_items -gt 0 ]]; then
|
if [[ $files_cleaned -gt 0 && $total_items -gt 0 ]]; then
|
||||||
echo "📊 Files cleaned: $files_cleaned | Categories processed: $total_items"
|
echo "📊 Files cleaned: $files_cleaned | Categories processed: $total_items"
|
||||||
elif [[ $files_cleaned -gt 0 ]]; then
|
elif [[ $files_cleaned -gt 0 ]]; then
|
||||||
@@ -983,7 +889,9 @@ main() {
|
|||||||
DRY_RUN=true
|
DRY_RUN=true
|
||||||
;;
|
;;
|
||||||
"--whitelist")
|
"--whitelist")
|
||||||
echo "Active whitelist patterns:"; for w in "${WHITELIST_PATTERNS[@]}"; do echo " $w"; done; exit 0
|
source "$SCRIPT_DIR/../lib/whitelist_manager.sh"
|
||||||
|
manage_whitelist
|
||||||
|
exit 0
|
||||||
;;
|
;;
|
||||||
"--help"|"-h")
|
"--help"|"-h")
|
||||||
echo "Mole - Deeper system cleanup"
|
echo "Mole - Deeper system cleanup"
|
||||||
@@ -992,7 +900,7 @@ main() {
|
|||||||
echo "Options:"
|
echo "Options:"
|
||||||
echo " --help, -h Show this help"
|
echo " --help, -h Show this help"
|
||||||
echo " --dry-run, -n Preview what would be cleaned without deleting"
|
echo " --dry-run, -n Preview what would be cleaned without deleting"
|
||||||
echo " --whitelist Show active whitelist patterns"
|
echo " --whitelist Manage protected caches"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Interactive cleanup with smart password handling"
|
echo "Interactive cleanup with smart password handling"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
Reference in New Issue
Block a user