From d044b2876e2154ca8ab183481d3638575f34bbf3 Mon Sep 17 00:00:00 2001 From: Tw93 Date: Tue, 20 Jan 2026 11:53:45 +0800 Subject: [PATCH] Fix unable to uninstall data-protected apps like Clash Party Previously, apps matching DATA_PROTECTED_BUNDLES patterns (VPNs, dev tools, etc.) could not be uninstalled because should_protect_path blocked their deletion. Now use MOLE_UNINSTALL_MODE to distinguish between cleanup and explicit uninstall, allowing users to remove these apps when they choose to while still protecting their data during normal cleanup operations. Also allow deletion of installer receipts in /private/var/db/receipts/. --- lib/core/app_protection.sh | 35 ++++++++++++++++++++++++++--------- lib/core/file_ops.sh | 3 ++- lib/uninstall/batch.sh | 11 +++++++++-- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/lib/core/app_protection.sh b/lib/core/app_protection.sh index bcd1d97..1b754b8 100755 --- a/lib/core/app_protection.sh +++ b/lib/core/app_protection.sh @@ -493,6 +493,9 @@ should_protect_data() { # Check if a path is protected from deletion # Centralized logic to protect system settings, control center, and critical apps # +# In uninstall mode (MOLE_UNINSTALL_MODE=1), only system-critical components are protected. +# Data-protected apps (VPNs, dev tools, etc.) can be uninstalled when user explicitly chooses to. +# # Args: $1 - path to check # Returns: 0 if protected, 1 if safe to delete should_protect_path() { @@ -577,17 +580,31 @@ should_protect_path() { # 6. Match full path against protected patterns # This catches things like /Users/tw93/Library/Caches/Claude when pattern is *Claude* - for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}" "${DATA_PROTECTED_BUNDLES[@]}"; do - if bundle_matches_pattern "$path" "$pattern"; then - return 0 - fi - done + # In uninstall mode, only check system-critical bundles (user explicitly chose to uninstall) + if [[ "${MOLE_UNINSTALL_MODE:-0}" == "1" ]]; then + # Uninstall mode: only protect system-critical components + for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}"; do + if bundle_matches_pattern "$path" "$pattern"; then + return 0 + fi + done + else + # Normal mode (cleanup): protect both system-critical and data-protected bundles + for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}" "${DATA_PROTECTED_BUNDLES[@]}"; do + if bundle_matches_pattern "$path" "$pattern"; then + return 0 + fi + done + fi # 7. Check if the filename itself matches any protected patterns - local filename - filename=$(basename "$path") - if should_protect_data "$filename"; then - return 0 + # Skip in uninstall mode - user explicitly chose to remove this app + if [[ "${MOLE_UNINSTALL_MODE:-0}" != "1" ]]; then + local filename + filename=$(basename "$path") + if should_protect_data "$filename"; then + return 0 + fi fi return 1 diff --git a/lib/core/file_ops.sh b/lib/core/file_ops.sh index 9044dd6..24075ec 100644 --- a/lib/core/file_ops.sh +++ b/lib/core/file_ops.sh @@ -102,7 +102,8 @@ validate_path_for_deletion() { /private/var/db/diagnostics | /private/var/db/diagnostics/* | \ /private/var/db/DiagnosticPipeline | /private/var/db/DiagnosticPipeline/* | \ /private/var/db/powerlog | /private/var/db/powerlog/* | \ - /private/var/db/reportmemoryexception | /private/var/db/reportmemoryexception/*) + /private/var/db/reportmemoryexception | /private/var/db/reportmemoryexception/* | \ + /private/var/db/receipts/*.bom | /private/var/db/receipts/*.plist) return 0 ;; esac diff --git a/lib/uninstall/batch.sh b/lib/uninstall/batch.sh index b808367..cf8ba56 100755 --- a/lib/uninstall/batch.sh +++ b/lib/uninstall/batch.sh @@ -169,8 +169,8 @@ remove_file_list() { batch_uninstall_applications() { local total_size_freed=0 - # Trap to clean up spinner on interrupt - trap 'stop_inline_spinner 2>/dev/null; echo ""; return 130' INT TERM + # Trap to clean up spinner and uninstall mode on interrupt + trap 'stop_inline_spinner 2>/dev/null; unset MOLE_UNINSTALL_MODE; echo ""; return 130' INT TERM # shellcheck disable=SC2154 if [[ ${#selected_apps[@]} -eq 0 ]]; then @@ -360,6 +360,10 @@ batch_uninstall_applications() { ;; esac + # Enable uninstall mode - allows deletion of data-protected apps (VPNs, dev tools, etc.) + # that user explicitly chose to uninstall. System-critical components remain protected. + export MOLE_UNINSTALL_MODE=1 + # Request sudo if needed. if [[ ${#sudo_apps[@]} -gt 0 ]]; then if ! sudo -n true 2> /dev/null; then @@ -635,6 +639,9 @@ batch_uninstall_applications() { sudo_keepalive_pid="" fi + # Disable uninstall mode + unset MOLE_UNINSTALL_MODE + # Invalidate cache if any apps were successfully uninstalled. if [[ $success_count -gt 0 ]]; then local cache_file="$HOME/.cache/mole/app_scan_cache"