mirror of
https://github.com/tw93/Mole.git
synced 2026-03-23 17:00:08 +00:00
Merge branch 'main' of github.com:tw93/Mole
This commit is contained in:
@@ -13,7 +13,7 @@ _MOLE_CORE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
[[ -z "${MOLE_BASE_LOADED:-}" ]] && source "$_MOLE_CORE_DIR/base.sh"
|
||||
|
||||
# Declare WHITELIST_PATTERNS if not already set (used by is_path_whitelisted)
|
||||
if ! declare -p WHITELIST_PATTERNS &>/dev/null; then
|
||||
if ! declare -p WHITELIST_PATTERNS &> /dev/null; then
|
||||
declare -a WHITELIST_PATTERNS=()
|
||||
fi
|
||||
|
||||
@@ -439,12 +439,12 @@ is_critical_system_component() {
|
||||
lower=$(echo "$token" | LC_ALL=C tr '[:upper:]' '[:lower:]')
|
||||
|
||||
case "$lower" in
|
||||
*backgroundtaskmanagement* | *loginitems* | *systempreferences* | *systemsettings* | *settings* | *preferences* | *controlcenter* | *biometrickit* | *sfl* | *tcc*)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
*backgroundtaskmanagement* | *loginitems* | *systempreferences* | *systemsettings* | *settings* | *preferences* | *controlcenter* | *biometrickit* | *sfl* | *tcc*)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
@@ -522,25 +522,25 @@ should_protect_path() {
|
||||
# 2. Protect caches critical for system UI rendering
|
||||
# These caches are essential for modern macOS (Sonoma/Sequoia) system UI rendering
|
||||
case "$path" in
|
||||
# System Settings and Control Center caches (CRITICAL - prevents blank panel bug)
|
||||
*com.apple.systempreferences.cache* | *com.apple.Settings.cache* | *com.apple.controlcenter.cache*)
|
||||
return 0
|
||||
;;
|
||||
# Finder and Dock (system essential)
|
||||
*com.apple.finder.cache* | *com.apple.dock.cache*)
|
||||
return 0
|
||||
;;
|
||||
# System XPC services and sandboxed containers
|
||||
*/Library/Containers/com.apple.Settings* | */Library/Containers/com.apple.SystemSettings* | */Library/Containers/com.apple.controlcenter*)
|
||||
return 0
|
||||
;;
|
||||
*/Library/Group\ Containers/com.apple.systempreferences* | */Library/Group\ Containers/com.apple.Settings*)
|
||||
return 0
|
||||
;;
|
||||
# Shared file lists for System Settings (macOS Sequoia) - Issue #136
|
||||
*/com.apple.sharedfilelist/*com.apple.Settings* | */com.apple.sharedfilelist/*com.apple.SystemSettings* | */com.apple.sharedfilelist/*systempreferences*)
|
||||
return 0
|
||||
;;
|
||||
# System Settings and Control Center caches (CRITICAL - prevents blank panel bug)
|
||||
*com.apple.systempreferences.cache* | *com.apple.Settings.cache* | *com.apple.controlcenter.cache*)
|
||||
return 0
|
||||
;;
|
||||
# Finder and Dock (system essential)
|
||||
*com.apple.finder.cache* | *com.apple.dock.cache*)
|
||||
return 0
|
||||
;;
|
||||
# System XPC services and sandboxed containers
|
||||
*/Library/Containers/com.apple.Settings* | */Library/Containers/com.apple.SystemSettings* | */Library/Containers/com.apple.controlcenter*)
|
||||
return 0
|
||||
;;
|
||||
*/Library/Group\ Containers/com.apple.systempreferences* | */Library/Group\ Containers/com.apple.Settings*)
|
||||
return 0
|
||||
;;
|
||||
# Shared file lists for System Settings (macOS Sequoia) - Issue #136
|
||||
*/com.apple.sharedfilelist/*com.apple.Settings* | */com.apple.sharedfilelist/*com.apple.SystemSettings* | */com.apple.sharedfilelist/*systempreferences*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# 3. Extract bundle ID from sandbox paths
|
||||
@@ -555,24 +555,24 @@ should_protect_path() {
|
||||
|
||||
# 4. Check for specific hardcoded critical patterns
|
||||
case "$path" in
|
||||
*com.apple.Settings* | *com.apple.SystemSettings* | *com.apple.controlcenter* | *com.apple.finder* | *com.apple.dock*)
|
||||
return 0
|
||||
;;
|
||||
*com.apple.Settings* | *com.apple.SystemSettings* | *com.apple.controlcenter* | *com.apple.finder* | *com.apple.dock*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# 5. Protect critical preference files and user data
|
||||
case "$path" in
|
||||
*/Library/Preferences/com.apple.dock.plist | */Library/Preferences/com.apple.finder.plist)
|
||||
return 0
|
||||
;;
|
||||
# Bluetooth and WiFi configurations
|
||||
*/ByHost/com.apple.bluetooth.* | */ByHost/com.apple.wifi.*)
|
||||
return 0
|
||||
;;
|
||||
# iCloud Drive - protect user's cloud synced data
|
||||
*/Library/Mobile\ Documents* | */Mobile\ Documents*)
|
||||
return 0
|
||||
;;
|
||||
*/Library/Preferences/com.apple.dock.plist | */Library/Preferences/com.apple.finder.plist)
|
||||
return 0
|
||||
;;
|
||||
# Bluetooth and WiFi configurations
|
||||
*/ByHost/com.apple.bluetooth.* | */ByHost/com.apple.wifi.*)
|
||||
return 0
|
||||
;;
|
||||
# iCloud Drive - protect user's cloud synced data
|
||||
*/Library/Mobile\ Documents* | */Mobile\ Documents*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# 6. Match full path against protected patterns
|
||||
@@ -611,9 +611,9 @@ is_path_whitelisted() {
|
||||
local check_pattern="${pattern%/}"
|
||||
local has_glob="false"
|
||||
case "$check_pattern" in
|
||||
*\** | *\?* | *\[*)
|
||||
has_glob="true"
|
||||
;;
|
||||
*\** | *\?* | *\[*)
|
||||
has_glob="true"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Check for exact match or glob pattern match
|
||||
@@ -716,17 +716,17 @@ find_app_files() {
|
||||
# Safety check: Skip if path ends with a common directory name (indicates empty app_name/bundle_id)
|
||||
# This prevents deletion of entire Library subdirectories when bundle_id is empty
|
||||
case "$expanded_path" in
|
||||
*/Library/Application\ Support | */Library/Application\ Support/ | \
|
||||
*/Library/Caches | */Library/Caches/ | \
|
||||
*/Library/Logs | */Library/Logs/ | \
|
||||
*/Library/Containers | */Library/Containers/ | \
|
||||
*/Library/WebKit | */Library/WebKit/ | \
|
||||
*/Library/HTTPStorages | */Library/HTTPStorages/ | \
|
||||
*/Library/Application\ Scripts | */Library/Application\ Scripts/ | \
|
||||
*/Library/Autosave\ Information | */Library/Autosave\ Information/ | \
|
||||
*/Library/Group\ Containers | */Library/Group\ Containers/)
|
||||
continue
|
||||
;;
|
||||
*/Library/Application\ Support | */Library/Application\ Support/ | \
|
||||
*/Library/Caches | */Library/Caches/ | \
|
||||
*/Library/Logs | */Library/Logs/ | \
|
||||
*/Library/Containers | */Library/Containers/ | \
|
||||
*/Library/WebKit | */Library/WebKit/ | \
|
||||
*/Library/HTTPStorages | */Library/HTTPStorages/ | \
|
||||
*/Library/Application\ Scripts | */Library/Application\ Scripts/ | \
|
||||
*/Library/Autosave\ Information | */Library/Autosave\ Information/ | \
|
||||
*/Library/Group\ Containers | */Library/Group\ Containers/)
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
|
||||
files_to_clean+=("$expanded_path")
|
||||
@@ -737,13 +737,13 @@ find_app_files() {
|
||||
[[ -f ~/Library/Preferences/"$bundle_id".plist ]] && files_to_clean+=("$HOME/Library/Preferences/$bundle_id.plist")
|
||||
[[ -d ~/Library/Preferences/ByHost ]] && while IFS= read -r -d '' pref; do
|
||||
files_to_clean+=("$pref")
|
||||
done < <(command find ~/Library/Preferences/ByHost -maxdepth 1 \( -name "$bundle_id*.plist" \) -print0 2>/dev/null)
|
||||
done < <(command find ~/Library/Preferences/ByHost -maxdepth 1 \( -name "$bundle_id*.plist" \) -print0 2> /dev/null)
|
||||
|
||||
# Group Containers (special handling)
|
||||
if [[ -d ~/Library/Group\ Containers ]]; then
|
||||
while IFS= read -r -d '' container; do
|
||||
files_to_clean+=("$container")
|
||||
done < <(command find ~/Library/Group\ Containers -maxdepth 1 \( -name "*$bundle_id*" \) -print0 2>/dev/null)
|
||||
done < <(command find ~/Library/Group\ Containers -maxdepth 1 \( -name "*$bundle_id*" \) -print0 2> /dev/null)
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -758,7 +758,7 @@ find_app_files() {
|
||||
continue
|
||||
fi
|
||||
files_to_clean+=("$plist")
|
||||
done < <(command find ~/Library/LaunchAgents -maxdepth 1 -name "*$app_name*.plist" -print0 2>/dev/null)
|
||||
done < <(command find ~/Library/LaunchAgents -maxdepth 1 -name "*$app_name*.plist" -print0 2> /dev/null)
|
||||
fi
|
||||
|
||||
# Handle specialized toolchains and development environments
|
||||
@@ -774,7 +774,7 @@ find_app_files() {
|
||||
for d in ~/AndroidStudioProjects ~/Library/Android ~/.android; do
|
||||
[[ -d "$d" ]] && files_to_clean+=("$d")
|
||||
done
|
||||
[[ -d ~/Library/Application\ Support/Google ]] && while IFS= read -r -d '' d; do files_to_clean+=("$d"); done < <(command find ~/Library/Application\ Support/Google -maxdepth 1 -name "AndroidStudio*" -print0 2>/dev/null)
|
||||
[[ -d ~/Library/Application\ Support/Google ]] && while IFS= read -r -d '' d; do files_to_clean+=("$d"); done < <(command find ~/Library/Application\ Support/Google -maxdepth 1 -name "AndroidStudio*" -print0 2> /dev/null)
|
||||
fi
|
||||
|
||||
# 3. Xcode (Apple)
|
||||
@@ -786,7 +786,7 @@ find_app_files() {
|
||||
# 4. JetBrains (IDE settings)
|
||||
if [[ "$bundle_id" =~ jetbrains ]] || [[ "$app_name" =~ IntelliJ|PyCharm|WebStorm|GoLand|RubyMine|PhpStorm|CLion|DataGrip|Rider ]]; then
|
||||
for base in ~/Library/Application\ Support/JetBrains ~/Library/Caches/JetBrains ~/Library/Logs/JetBrains; do
|
||||
[[ -d "$base" ]] && while IFS= read -r -d '' d; do files_to_clean+=("$d"); done < <(command find "$base" -maxdepth 1 -name "${app_name}*" -print0 2>/dev/null)
|
||||
[[ -d "$base" ]] && while IFS= read -r -d '' d; do files_to_clean+=("$d"); done < <(command find "$base" -maxdepth 1 -name "${app_name}*" -print0 2> /dev/null)
|
||||
done
|
||||
fi
|
||||
|
||||
@@ -853,11 +853,11 @@ find_app_system_files() {
|
||||
|
||||
# Safety check: Skip if path ends with a common directory name (indicates empty app_name/bundle_id)
|
||||
case "$p" in
|
||||
/Library/Application\ Support | /Library/Application\ Support/ | \
|
||||
/Library/Caches | /Library/Caches/ | \
|
||||
/Library/Logs | /Library/Logs/)
|
||||
continue
|
||||
;;
|
||||
/Library/Application\ Support | /Library/Application\ Support/ | \
|
||||
/Library/Caches | /Library/Caches/ | \
|
||||
/Library/Logs | /Library/Logs/)
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
|
||||
system_files+=("$p")
|
||||
@@ -868,7 +868,7 @@ find_app_system_files() {
|
||||
for base in /Library/LaunchAgents /Library/LaunchDaemons; do
|
||||
[[ -d "$base" ]] && while IFS= read -r -d '' plist; do
|
||||
system_files+=("$plist")
|
||||
done < <(command find "$base" -maxdepth 1 \( -name "*$app_name*.plist" \) -print0 2>/dev/null)
|
||||
done < <(command find "$base" -maxdepth 1 \( -name "*$app_name*.plist" \) -print0 2> /dev/null)
|
||||
done
|
||||
fi
|
||||
|
||||
@@ -877,11 +877,11 @@ find_app_system_files() {
|
||||
if [[ -n "$bundle_id" && "$bundle_id" != "unknown" && ${#bundle_id} -gt 3 ]]; then
|
||||
[[ -d /Library/PrivilegedHelperTools ]] && while IFS= read -r -d '' helper; do
|
||||
system_files+=("$helper")
|
||||
done < <(command find /Library/PrivilegedHelperTools -maxdepth 1 \( -name "$bundle_id*" \) -print0 2>/dev/null)
|
||||
done < <(command find /Library/PrivilegedHelperTools -maxdepth 1 \( -name "$bundle_id*" \) -print0 2> /dev/null)
|
||||
|
||||
[[ -d /private/var/db/receipts ]] && while IFS= read -r -d '' receipt; do
|
||||
system_files+=("$receipt")
|
||||
done < <(command find /private/var/db/receipts -maxdepth 1 \( -name "*$bundle_id*" \) -print0 2>/dev/null)
|
||||
done < <(command find /private/var/db/receipts -maxdepth 1 \( -name "*$bundle_id*" \) -print0 2> /dev/null)
|
||||
fi
|
||||
|
||||
local receipt_files=""
|
||||
@@ -926,7 +926,7 @@ find_app_receipt_files() {
|
||||
if [[ -d /private/var/db/receipts ]]; then
|
||||
while IFS= read -r -d '' bom; do
|
||||
bom_files+=("$bom")
|
||||
done < <(find /private/var/db/receipts -maxdepth 1 -name "${bundle_id}*.bom" -print0 2>/dev/null)
|
||||
done < <(find /private/var/db/receipts -maxdepth 1 -name "${bundle_id}*.bom" -print0 2> /dev/null)
|
||||
fi
|
||||
|
||||
# Process bom files if any found
|
||||
@@ -938,7 +938,7 @@ find_app_receipt_files() {
|
||||
# lsbom -f: file paths only
|
||||
# -s: suppress output (convert to text)
|
||||
local bom_content
|
||||
bom_content=$(lsbom -f -s "$bom_file" 2>/dev/null)
|
||||
bom_content=$(lsbom -f -s "$bom_file" 2> /dev/null)
|
||||
|
||||
while IFS= read -r file_path; do
|
||||
# Standardize path (remove leading dot)
|
||||
@@ -965,21 +965,21 @@ find_app_receipt_files() {
|
||||
|
||||
# Whitelisted prefixes (exclude /Users, /usr, /opt)
|
||||
case "$clean_path" in
|
||||
/Applications/*) is_safe=true ;;
|
||||
/Library/Application\ Support/*) is_safe=true ;;
|
||||
/Library/Caches/*) is_safe=true ;;
|
||||
/Library/Logs/*) is_safe=true ;;
|
||||
/Library/Preferences/*) is_safe=true ;;
|
||||
/Library/LaunchAgents/*) is_safe=true ;;
|
||||
/Library/LaunchDaemons/*) is_safe=true ;;
|
||||
/Library/PrivilegedHelperTools/*) is_safe=true ;;
|
||||
/Library/Extensions/*) is_safe=false ;;
|
||||
*) is_safe=false ;;
|
||||
/Applications/*) is_safe=true ;;
|
||||
/Library/Application\ Support/*) is_safe=true ;;
|
||||
/Library/Caches/*) is_safe=true ;;
|
||||
/Library/Logs/*) is_safe=true ;;
|
||||
/Library/Preferences/*) is_safe=true ;;
|
||||
/Library/LaunchAgents/*) is_safe=true ;;
|
||||
/Library/LaunchDaemons/*) is_safe=true ;;
|
||||
/Library/PrivilegedHelperTools/*) is_safe=true ;;
|
||||
/Library/Extensions/*) is_safe=false ;;
|
||||
*) is_safe=false ;;
|
||||
esac
|
||||
|
||||
# Hard blocks
|
||||
case "$clean_path" in
|
||||
/System/* | /usr/bin/* | /usr/lib/* | /bin/* | /sbin/* | /private/*) is_safe=false ;;
|
||||
/System/* | /usr/bin/* | /usr/lib/* | /bin/* | /sbin/* | /private/*) is_safe=false ;;
|
||||
esac
|
||||
|
||||
if [[ "$is_safe" == "true" && -e "$clean_path" ]]; then
|
||||
@@ -988,7 +988,7 @@ find_app_receipt_files() {
|
||||
continue
|
||||
fi
|
||||
|
||||
if declare -f should_protect_path >/dev/null 2>&1; then
|
||||
if declare -f should_protect_path > /dev/null 2>&1; then
|
||||
if should_protect_path "$clean_path"; then
|
||||
continue
|
||||
fi
|
||||
@@ -997,7 +997,7 @@ find_app_receipt_files() {
|
||||
receipt_files+=("$clean_path")
|
||||
fi
|
||||
|
||||
done <<<"$bom_content"
|
||||
done <<< "$bom_content"
|
||||
done
|
||||
fi
|
||||
if [[ ${#receipt_files[@]} -gt 0 ]]; then
|
||||
@@ -1014,34 +1014,34 @@ force_kill_app() {
|
||||
# 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 "")
|
||||
exec_name=$(defaults read "$app_path/Contents/Info.plist" CFBundleExecutable 2> /dev/null || echo "")
|
||||
fi
|
||||
|
||||
# Use executable name for precise matching, fallback to app name
|
||||
local match_pattern="${exec_name:-$app_name}"
|
||||
|
||||
# Check if process is running using exact match only
|
||||
if ! pgrep -x "$match_pattern" >/dev/null 2>&1; then
|
||||
if ! pgrep -x "$match_pattern" > /dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Try graceful termination first
|
||||
pkill -x "$match_pattern" 2>/dev/null || true
|
||||
pkill -x "$match_pattern" 2> /dev/null || true
|
||||
sleep 2
|
||||
|
||||
# Check again after graceful kill
|
||||
if ! pgrep -x "$match_pattern" >/dev/null 2>&1; then
|
||||
if ! pgrep -x "$match_pattern" > /dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Force kill if still running
|
||||
pkill -9 -x "$match_pattern" 2>/dev/null || true
|
||||
pkill -9 -x "$match_pattern" 2> /dev/null || true
|
||||
sleep 2
|
||||
|
||||
# If still running and sudo is available, try with sudo
|
||||
if pgrep -x "$match_pattern" >/dev/null 2>&1; then
|
||||
if sudo -n true 2>/dev/null; then
|
||||
sudo pkill -9 -x "$match_pattern" 2>/dev/null || true
|
||||
if pgrep -x "$match_pattern" > /dev/null 2>&1; then
|
||||
if sudo -n true 2> /dev/null; then
|
||||
sudo pkill -9 -x "$match_pattern" 2> /dev/null || true
|
||||
sleep 2
|
||||
fi
|
||||
fi
|
||||
@@ -1049,7 +1049,7 @@ force_kill_app() {
|
||||
# Final check with longer timeout for stubborn processes
|
||||
local retries=3
|
||||
while [[ $retries -gt 0 ]]; do
|
||||
if ! pgrep -x "$match_pattern" >/dev/null 2>&1; then
|
||||
if ! pgrep -x "$match_pattern" > /dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
@@ -1057,7 +1057,7 @@ force_kill_app() {
|
||||
done
|
||||
|
||||
# Still running after all attempts
|
||||
pgrep -x "$match_pattern" >/dev/null 2>&1 && return 1 || return 0
|
||||
pgrep -x "$match_pattern" > /dev/null 2>&1 && return 1 || return 0
|
||||
}
|
||||
|
||||
# Note: calculate_total_size() is defined in lib/core/file_ops.sh
|
||||
|
||||
@@ -43,9 +43,9 @@ update_via_homebrew() {
|
||||
echo "Updating Homebrew..."
|
||||
fi
|
||||
|
||||
brew update >"$temp_update" 2>&1 &
|
||||
brew update > "$temp_update" 2>&1 &
|
||||
local update_pid=$!
|
||||
wait $update_pid 2>/dev/null || true # Continue even if brew update fails
|
||||
wait $update_pid 2> /dev/null || true # Continue even if brew update fails
|
||||
|
||||
if [[ -t 1 ]]; then
|
||||
stop_inline_spinner
|
||||
@@ -58,9 +58,9 @@ update_via_homebrew() {
|
||||
echo "Upgrading Mole..."
|
||||
fi
|
||||
|
||||
brew upgrade mole >"$temp_upgrade" 2>&1 &
|
||||
brew upgrade mole > "$temp_upgrade" 2>&1 &
|
||||
local upgrade_pid=$!
|
||||
wait $upgrade_pid 2>/dev/null || true # Continue even if brew upgrade fails
|
||||
wait $upgrade_pid 2> /dev/null || true # Continue even if brew upgrade fails
|
||||
|
||||
local upgrade_output
|
||||
upgrade_output=$(cat "$temp_upgrade")
|
||||
@@ -78,7 +78,7 @@ update_via_homebrew() {
|
||||
|
||||
if echo "$upgrade_output" | grep -q "already installed"; then
|
||||
local installed_version
|
||||
installed_version=$(brew list --versions mole 2>/dev/null | awk '{print $2}')
|
||||
installed_version=$(brew list --versions mole 2> /dev/null | awk '{print $2}')
|
||||
echo ""
|
||||
echo -e "${GREEN}${ICON_SUCCESS}${NC} Already on latest version (${installed_version:-$current_version})"
|
||||
echo ""
|
||||
@@ -89,14 +89,14 @@ update_via_homebrew() {
|
||||
else
|
||||
echo "$upgrade_output" | grep -Ev "^(==>|Updating Homebrew|Warning:)" || true
|
||||
local new_version
|
||||
new_version=$(brew list --versions mole 2>/dev/null | awk '{print $2}')
|
||||
new_version=$(brew list --versions mole 2> /dev/null | awk '{print $2}')
|
||||
echo ""
|
||||
echo -e "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version (${new_version:-$current_version})"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Clear update cache (suppress errors if cache doesn't exist or is locked)
|
||||
rm -f "$HOME/.cache/mole/version_check" "$HOME/.cache/mole/update_message" 2>/dev/null || true
|
||||
rm -f "$HOME/.cache/mole/version_check" "$HOME/.cache/mole/update_message" 2> /dev/null || true
|
||||
}
|
||||
|
||||
# Remove applications from Dock
|
||||
@@ -133,16 +133,16 @@ remove_apps_from_dock() {
|
||||
fi
|
||||
|
||||
if [[ -e "$app_path" ]]; then
|
||||
if full_path=$(cd "$(dirname "$app_path")" 2>/dev/null && pwd); then
|
||||
if full_path=$(cd "$(dirname "$app_path")" 2> /dev/null && pwd); then
|
||||
full_path="$full_path/$(basename "$app_path")"
|
||||
else
|
||||
continue
|
||||
fi
|
||||
else
|
||||
case "$app_path" in
|
||||
~/*) full_path="$HOME/${app_path#~/}" ;;
|
||||
/*) full_path="$app_path" ;;
|
||||
*) continue ;;
|
||||
~/*) full_path="$HOME/${app_path#~/}" ;;
|
||||
/*) full_path="$app_path" ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
@@ -154,11 +154,11 @@ remove_apps_from_dock() {
|
||||
local i=0
|
||||
while true; do
|
||||
local label
|
||||
label=$(/usr/libexec/PlistBuddy -c "Print :persistent-apps:$i:tile-data:file-label" "$plist" 2>/dev/null || echo "")
|
||||
label=$(/usr/libexec/PlistBuddy -c "Print :persistent-apps:$i:tile-data:file-label" "$plist" 2> /dev/null || echo "")
|
||||
[[ -z "$label" ]] && break
|
||||
|
||||
local url
|
||||
url=$(/usr/libexec/PlistBuddy -c "Print :persistent-apps:$i:tile-data:file-data:_CFURLString" "$plist" 2>/dev/null || echo "")
|
||||
url=$(/usr/libexec/PlistBuddy -c "Print :persistent-apps:$i:tile-data:file-data:_CFURLString" "$plist" 2> /dev/null || echo "")
|
||||
[[ -z "$url" ]] && {
|
||||
((i++))
|
||||
continue
|
||||
@@ -166,7 +166,7 @@ remove_apps_from_dock() {
|
||||
|
||||
# Match by URL-encoded path to handle spaces in app names
|
||||
if [[ -n "$encoded_path" && "$url" == *"$encoded_path"* ]]; then
|
||||
if /usr/libexec/PlistBuddy -c "Delete :persistent-apps:$i" "$plist" 2>/dev/null; then
|
||||
if /usr/libexec/PlistBuddy -c "Delete :persistent-apps:$i" "$plist" 2> /dev/null; then
|
||||
changed=true
|
||||
# After deletion, current index i now points to the next item
|
||||
continue
|
||||
@@ -178,6 +178,6 @@ remove_apps_from_dock() {
|
||||
|
||||
if [[ "$changed" == "true" ]]; then
|
||||
# Restart Dock to apply changes from the plist
|
||||
killall Dock 2>/dev/null || true
|
||||
killall Dock 2> /dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ validate_path_for_deletion() {
|
||||
# Check symlink target if path is a symbolic link
|
||||
if [[ -L "$path" ]]; then
|
||||
local link_target
|
||||
link_target=$(readlink "$path" 2>/dev/null) || {
|
||||
link_target=$(readlink "$path" 2> /dev/null) || {
|
||||
log_error "Cannot read symlink: $path"
|
||||
return 1
|
||||
}
|
||||
@@ -52,16 +52,16 @@ validate_path_for_deletion() {
|
||||
if [[ "$link_target" != /* ]]; then
|
||||
local link_dir
|
||||
link_dir=$(dirname "$path")
|
||||
resolved_target=$(cd "$link_dir" 2>/dev/null && cd "$(dirname "$link_target")" 2>/dev/null && pwd)/$(basename "$link_target") || resolved_target=""
|
||||
resolved_target=$(cd "$link_dir" 2> /dev/null && cd "$(dirname "$link_target")" 2> /dev/null && pwd)/$(basename "$link_target") || resolved_target=""
|
||||
fi
|
||||
|
||||
# Validate resolved target against protected paths
|
||||
if [[ -n "$resolved_target" ]]; then
|
||||
case "$resolved_target" in
|
||||
/System/* | /usr/bin/* | /usr/lib/* | /bin/* | /sbin/* | /private/etc/*)
|
||||
log_error "Symlink points to protected system path: $path -> $resolved_target"
|
||||
return 1
|
||||
;;
|
||||
/System/* | /usr/bin/* | /usr/lib/* | /bin/* | /sbin/* | /private/etc/*)
|
||||
log_error "Symlink points to protected system path: $path -> $resolved_target"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
@@ -88,47 +88,47 @@ validate_path_for_deletion() {
|
||||
|
||||
# Allow deletion of coresymbolicationd cache (safe system cache that can be rebuilt)
|
||||
case "$path" in
|
||||
/System/Library/Caches/com.apple.coresymbolicationd/data | /System/Library/Caches/com.apple.coresymbolicationd/data/*)
|
||||
return 0
|
||||
;;
|
||||
/System/Library/Caches/com.apple.coresymbolicationd/data | /System/Library/Caches/com.apple.coresymbolicationd/data/*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# Allow known safe paths under /private
|
||||
case "$path" in
|
||||
/private/tmp | /private/tmp/* | \
|
||||
/private/var/tmp | /private/var/tmp/* | \
|
||||
/private/var/log | /private/var/log/* | \
|
||||
/private/var/folders | /private/var/folders/* | \
|
||||
/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/*)
|
||||
return 0
|
||||
;;
|
||||
/private/tmp | /private/tmp/* | \
|
||||
/private/var/tmp | /private/var/tmp/* | \
|
||||
/private/var/log | /private/var/log/* | \
|
||||
/private/var/folders | /private/var/folders/* | \
|
||||
/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/*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# Check path isn't critical system directory
|
||||
case "$path" in
|
||||
/ | /bin | /bin/* | /sbin | /sbin/* | /usr | /usr/bin | /usr/bin/* | /usr/sbin | /usr/sbin/* | /usr/lib | /usr/lib/* | /System | /System/* | /Library/Extensions)
|
||||
log_error "Path validation failed: critical system directory: $path"
|
||||
return 1
|
||||
;;
|
||||
/private)
|
||||
log_error "Path validation failed: critical system directory: $path"
|
||||
return 1
|
||||
;;
|
||||
/etc | /etc/* | /private/etc | /private/etc/*)
|
||||
log_error "Path validation failed: /etc contains critical system files: $path"
|
||||
return 1
|
||||
;;
|
||||
/var | /var/db | /var/db/* | /private/var | /private/var/db | /private/var/db/*)
|
||||
log_error "Path validation failed: /var/db contains system databases: $path"
|
||||
return 1
|
||||
;;
|
||||
/ | /bin | /bin/* | /sbin | /sbin/* | /usr | /usr/bin | /usr/bin/* | /usr/sbin | /usr/sbin/* | /usr/lib | /usr/lib/* | /System | /System/* | /Library/Extensions)
|
||||
log_error "Path validation failed: critical system directory: $path"
|
||||
return 1
|
||||
;;
|
||||
/private)
|
||||
log_error "Path validation failed: critical system directory: $path"
|
||||
return 1
|
||||
;;
|
||||
/etc | /etc/* | /private/etc | /private/etc/*)
|
||||
log_error "Path validation failed: /etc contains critical system files: $path"
|
||||
return 1
|
||||
;;
|
||||
/var | /var/db | /var/db/* | /private/var | /private/var/db | /private/var/db/*)
|
||||
log_error "Path validation failed: /var/db contains system databases: $path"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Check if path is protected (keychains, system settings, etc)
|
||||
if declare -f should_protect_path >/dev/null 2>&1; then
|
||||
if declare -f should_protect_path > /dev/null 2>&1; then
|
||||
if should_protect_path "$path"; then
|
||||
if [[ "${MO_DEBUG:-0}" == "1" ]]; then
|
||||
log_warning "Path validation: protected path skipped: $path"
|
||||
@@ -171,16 +171,16 @@ safe_remove() {
|
||||
|
||||
if [[ -e "$path" ]]; then
|
||||
local size_kb
|
||||
size_kb=$(get_path_size_kb "$path" 2>/dev/null || echo "0")
|
||||
size_kb=$(get_path_size_kb "$path" 2> /dev/null || echo "0")
|
||||
if [[ "$size_kb" -gt 0 ]]; then
|
||||
file_size=$(bytes_to_human "$((size_kb * 1024))")
|
||||
fi
|
||||
|
||||
if [[ -f "$path" || -d "$path" ]] && ! [[ -L "$path" ]]; then
|
||||
local mod_time
|
||||
mod_time=$(stat -f%m "$path" 2>/dev/null || echo "0")
|
||||
mod_time=$(stat -f%m "$path" 2> /dev/null || echo "0")
|
||||
local now
|
||||
now=$(date +%s 2>/dev/null || echo "0")
|
||||
now=$(date +%s 2> /dev/null || echo "0")
|
||||
if [[ "$mod_time" -gt 0 && "$now" -gt 0 ]]; then
|
||||
file_age=$(((now - mod_time) / 86400))
|
||||
fi
|
||||
@@ -248,18 +248,18 @@ safe_sudo_remove() {
|
||||
local file_size=""
|
||||
local file_age=""
|
||||
|
||||
if sudo test -e "$path" 2>/dev/null; then
|
||||
if sudo test -e "$path" 2> /dev/null; then
|
||||
local size_kb
|
||||
size_kb=$(sudo du -sk "$path" 2>/dev/null | awk '{print $1}' || echo "0")
|
||||
size_kb=$(sudo du -sk "$path" 2> /dev/null | awk '{print $1}' || echo "0")
|
||||
if [[ "$size_kb" -gt 0 ]]; then
|
||||
file_size=$(bytes_to_human "$((size_kb * 1024))")
|
||||
fi
|
||||
|
||||
if sudo test -f "$path" 2>/dev/null || sudo test -d "$path" 2>/dev/null; then
|
||||
if sudo test -f "$path" 2> /dev/null || sudo test -d "$path" 2> /dev/null; then
|
||||
local mod_time
|
||||
mod_time=$(sudo stat -f%m "$path" 2>/dev/null || echo "0")
|
||||
mod_time=$(sudo stat -f%m "$path" 2> /dev/null || echo "0")
|
||||
local now
|
||||
now=$(date +%s 2>/dev/null || echo "0")
|
||||
now=$(date +%s 2> /dev/null || echo "0")
|
||||
if [[ "$mod_time" -gt 0 && "$now" -gt 0 ]]; then
|
||||
file_age=$(((now - mod_time) / 86400))
|
||||
fi
|
||||
@@ -276,7 +276,7 @@ safe_sudo_remove() {
|
||||
debug_log "Removing (sudo): $path"
|
||||
|
||||
# Perform the deletion
|
||||
if sudo rm -rf "$path" 2>/dev/null; then # SAFE: safe_sudo_remove implementation
|
||||
if sudo rm -rf "$path" 2> /dev/null; then # SAFE: safe_sudo_remove implementation
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to remove (sudo): $path"
|
||||
@@ -325,7 +325,7 @@ safe_find_delete() {
|
||||
continue
|
||||
fi
|
||||
safe_remove "$match" true || true
|
||||
done < <(command find "$base_dir" "${find_args[@]}" -print0 2>/dev/null || true)
|
||||
done < <(command find "$base_dir" "${find_args[@]}" -print0 2> /dev/null || true)
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -338,12 +338,12 @@ safe_sudo_find_delete() {
|
||||
local type_filter="${4:-f}"
|
||||
|
||||
# Validate base directory (use sudo for permission-restricted dirs)
|
||||
if ! sudo test -d "$base_dir" 2>/dev/null; then
|
||||
if ! sudo test -d "$base_dir" 2> /dev/null; then
|
||||
debug_log "Directory does not exist (skipping): $base_dir"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if sudo test -L "$base_dir" 2>/dev/null; then
|
||||
if sudo test -L "$base_dir" 2> /dev/null; then
|
||||
log_error "Refusing to search symlinked directory: $base_dir"
|
||||
return 1
|
||||
fi
|
||||
@@ -367,7 +367,7 @@ safe_sudo_find_delete() {
|
||||
continue
|
||||
fi
|
||||
safe_sudo_remove "$match" || true
|
||||
done < <(sudo find "$base_dir" "${find_args[@]}" -print0 2>/dev/null || true)
|
||||
done < <(sudo find "$base_dir" "${find_args[@]}" -print0 2> /dev/null || true)
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -387,7 +387,7 @@ get_path_size_kb() {
|
||||
# Use || echo 0 to ensure failure in du (e.g. permission error) doesn't exit script under set -e
|
||||
# Pipefail would normally cause the pipeline to fail if du fails, but || handle catches it.
|
||||
local size
|
||||
size=$(command du -sk "$path" 2>/dev/null | awk 'NR==1 {print $1; exit}' || true)
|
||||
size=$(command du -sk "$path" 2> /dev/null | awk 'NR==1 {print $1; exit}' || true)
|
||||
|
||||
# Ensure size is a valid number (fix for non-numeric du output)
|
||||
if [[ "$size" =~ ^[0-9]+$ ]]; then
|
||||
@@ -408,7 +408,7 @@ calculate_total_size() {
|
||||
size_kb=$(get_path_size_kb "$file")
|
||||
((total_kb += size_kb))
|
||||
fi
|
||||
done <<<"$files"
|
||||
done <<< "$files"
|
||||
|
||||
echo "$total_kb"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user