mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 16:49:41 +00:00
- Replace parentheses with commas for supplementary info - Use commas instead of em-dashes for separators - Update bullet points from - to * in some contexts - Improve version extraction regex with fallback logic
431 lines
16 KiB
Bash
Executable File
431 lines
16 KiB
Bash
Executable File
#!/bin/bash
|
|
# Whitelist management functionality
|
|
# Shows actual files that would be deleted by dry-run
|
|
|
|
set -euo pipefail
|
|
|
|
# Get script directory and source dependencies
|
|
_MOLE_MANAGE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "$_MOLE_MANAGE_DIR/../core/common.sh"
|
|
source "$_MOLE_MANAGE_DIR/../ui/menu_simple.sh"
|
|
|
|
# Config file paths
|
|
readonly WHITELIST_CONFIG_CLEAN="$HOME/.config/mole/whitelist"
|
|
readonly WHITELIST_CONFIG_OPTIMIZE="$HOME/.config/mole/whitelist_optimize"
|
|
readonly WHITELIST_CONFIG_OPTIMIZE_LEGACY="$HOME/.config/mole/whitelist_checks"
|
|
|
|
# Default whitelist patterns defined in lib/core/common.sh:
|
|
# - DEFAULT_WHITELIST_PATTERNS
|
|
# - FINDER_METADATA_SENTINEL
|
|
|
|
# Save whitelist patterns to config (defaults to "clean" for legacy callers)
|
|
save_whitelist_patterns() {
|
|
local mode="clean"
|
|
if [[ $# -gt 0 ]]; then
|
|
case "$1" in
|
|
clean | optimize)
|
|
mode="$1"
|
|
shift
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
local -a patterns
|
|
patterns=("$@")
|
|
|
|
local config_file
|
|
local header_text
|
|
|
|
if [[ "$mode" == "optimize" ]]; then
|
|
config_file="$WHITELIST_CONFIG_OPTIMIZE"
|
|
header_text="# Mole Optimization Whitelist - These checks will be skipped during optimization"
|
|
else
|
|
config_file="$WHITELIST_CONFIG_CLEAN"
|
|
header_text="# Mole Whitelist - Protected paths won't be deleted\n# Default protections: Playwright browsers, HuggingFace models, Maven repo, Ollama models, Surge Mac, R renv, Finder metadata\n# Add one pattern per line to keep items safe."
|
|
fi
|
|
|
|
ensure_user_file "$config_file"
|
|
|
|
echo -e "$header_text" > "$config_file"
|
|
|
|
if [[ ${#patterns[@]} -gt 0 ]]; then
|
|
local -a unique_patterns=()
|
|
for pattern in "${patterns[@]}"; do
|
|
local duplicate="false"
|
|
if [[ ${#unique_patterns[@]} -gt 0 ]]; then
|
|
for existing in "${unique_patterns[@]}"; do
|
|
if patterns_equivalent "$pattern" "$existing"; then
|
|
duplicate="true"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
[[ "$duplicate" == "true" ]] && continue
|
|
unique_patterns+=("$pattern")
|
|
done
|
|
|
|
if [[ ${#unique_patterns[@]} -gt 0 ]]; then
|
|
printf '\n' >> "$config_file"
|
|
for pattern in "${unique_patterns[@]}"; do
|
|
echo "$pattern" >> "$config_file"
|
|
done
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Get all cache items with their patterns
|
|
get_all_cache_items() {
|
|
# Format: "display_name|pattern|category"
|
|
cat << 'EOF'
|
|
Apple Mail cache|$HOME/Library/Caches/com.apple.mail/*|system_cache
|
|
Gradle build cache (Android Studio, Gradle projects)|$HOME/.gradle/caches/*|ide_cache
|
|
Gradle daemon processes cache|$HOME/.gradle/daemon/*|ide_cache
|
|
Xcode DerivedData (build outputs, indexes)|$HOME/Library/Developer/Xcode/DerivedData/*|ide_cache
|
|
Xcode archives (built app packages)|$HOME/Library/Developer/Xcode/Archives/*|ide_cache
|
|
Xcode internal cache files|$HOME/Library/Caches/com.apple.dt.Xcode/*|ide_cache
|
|
Xcode iOS device support symbols|$HOME/Library/Developer/Xcode/iOS DeviceSupport/*/Symbols/System/Library/Caches/*|ide_cache
|
|
Maven local repository (Java dependencies)|$HOME/.m2/repository/*|ide_cache
|
|
JetBrains IDEs data (IntelliJ, PyCharm, WebStorm, GoLand)|$HOME/Library/Application Support/JetBrains/*|ide_cache
|
|
JetBrains IDEs cache|$HOME/Library/Caches/JetBrains/*|ide_cache
|
|
Android Studio cache and indexes|$HOME/Library/Caches/Google/AndroidStudio*/*|ide_cache
|
|
Android build cache|$HOME/.android/build-cache/*|ide_cache
|
|
VS Code runtime cache|$HOME/Library/Application Support/Code/Cache/*|ide_cache
|
|
VS Code extension and update cache|$HOME/Library/Application Support/Code/CachedData/*|ide_cache
|
|
VS Code system cache (Cursor, VSCodium)|$HOME/Library/Caches/com.microsoft.VSCode/*|ide_cache
|
|
Cursor editor cache|$HOME/Library/Caches/com.todesktop.230313mzl4w4u92/*|ide_cache
|
|
Bazel build cache|$HOME/.cache/bazel/*|compiler_cache
|
|
Go build cache and module cache|$HOME/Library/Caches/go-build/*|compiler_cache
|
|
Go module cache|$HOME/go/pkg/mod/cache/*|compiler_cache
|
|
Rust Cargo registry cache|$HOME/.cargo/registry/cache/*|compiler_cache
|
|
Rust documentation cache|$HOME/.rustup/toolchains/*/share/doc/*|compiler_cache
|
|
Rustup toolchain downloads|$HOME/.rustup/downloads/*|compiler_cache
|
|
ccache compiler cache|$HOME/.ccache/*|compiler_cache
|
|
sccache distributed compiler cache|$HOME/.cache/sccache/*|compiler_cache
|
|
SBT Scala build cache|$HOME/.sbt/*|compiler_cache
|
|
Ivy dependency cache|$HOME/.ivy2/cache/*|compiler_cache
|
|
Turbo monorepo build cache|$HOME/.turbo/*|compiler_cache
|
|
Next.js build cache|$HOME/.next/*|compiler_cache
|
|
Vite build cache|$HOME/.vite/*|compiler_cache
|
|
Parcel bundler cache|$HOME/.parcel-cache/*|compiler_cache
|
|
pre-commit hooks cache|$HOME/.cache/pre-commit/*|compiler_cache
|
|
Ruff Python linter cache|$HOME/.cache/ruff/*|compiler_cache
|
|
MyPy type checker cache|$HOME/.cache/mypy/*|compiler_cache
|
|
Pytest test cache|$HOME/.pytest_cache/*|compiler_cache
|
|
Flutter SDK cache|$HOME/.cache/flutter/*|compiler_cache
|
|
Swift Package Manager cache|$HOME/.cache/swift-package-manager/*|compiler_cache
|
|
Zig compiler cache|$HOME/.cache/zig/*|compiler_cache
|
|
Deno cache|$HOME/Library/Caches/deno/*|compiler_cache
|
|
CocoaPods cache (iOS dependencies)|$HOME/Library/Caches/CocoaPods/*|package_manager
|
|
npm package cache|$HOME/.npm/_cacache/*|package_manager
|
|
pip Python package cache|$HOME/.cache/pip/*|package_manager
|
|
uv Python package cache|$HOME/.cache/uv/*|package_manager
|
|
R renv global cache (virtual environments)|$HOME/Library/Caches/org.R-project.R/R/renv/*|package_manager
|
|
Homebrew downloaded packages|$HOME/Library/Caches/Homebrew/*|package_manager
|
|
Yarn package manager cache|$HOME/.cache/yarn/*|package_manager
|
|
pnpm package store|$HOME/Library/pnpm/store/*|package_manager
|
|
Composer PHP dependencies cache|$HOME/.composer/cache/*|package_manager
|
|
RubyGems cache|$HOME/.gem/cache/*|package_manager
|
|
Conda packages cache|$HOME/.conda/pkgs/*|package_manager
|
|
Anaconda packages cache|$HOME/anaconda3/pkgs/*|package_manager
|
|
PyTorch model cache|$HOME/.cache/torch/*|ai_ml_cache
|
|
TensorFlow model and dataset cache|$HOME/.cache/tensorflow/*|ai_ml_cache
|
|
HuggingFace models and datasets|$HOME/.cache/huggingface/*|ai_ml_cache
|
|
Playwright browser binaries|$HOME/Library/Caches/ms-playwright*|ai_ml_cache
|
|
Selenium WebDriver binaries|$HOME/.cache/selenium/*|ai_ml_cache
|
|
Ollama local AI models|$HOME/.ollama/models/*|ai_ml_cache
|
|
Weights & Biases ML experiments cache|$HOME/.cache/wandb/*|ai_ml_cache
|
|
Safari web browser cache|$HOME/Library/Caches/com.apple.Safari/*|browser_cache
|
|
Chrome browser cache|$HOME/Library/Caches/Google/Chrome/*|browser_cache
|
|
Firefox browser cache|$HOME/Library/Caches/Firefox/*|browser_cache
|
|
Brave browser cache|$HOME/Library/Caches/BraveSoftware/Brave-Browser/*|browser_cache
|
|
Surge proxy cache|$HOME/Library/Caches/com.nssurge.surge-mac/*|network_tools
|
|
Surge configuration and data|$HOME/Library/Application Support/com.nssurge.surge-mac/*|network_tools
|
|
Docker Desktop image cache|$HOME/Library/Containers/com.docker.docker/Data/*|container_cache
|
|
Podman container cache|$HOME/.local/share/containers/cache/*|container_cache
|
|
Font cache|$HOME/Library/Caches/com.apple.FontRegistry/*|system_cache
|
|
Spotlight metadata cache|$HOME/Library/Caches/com.apple.spotlight/*|system_cache
|
|
CloudKit cache|$HOME/Library/Caches/CloudKit/*|system_cache
|
|
Trash|$HOME/.Trash|system_cache
|
|
EOF
|
|
# Add FINDER_METADATA with constant reference
|
|
echo "Finder metadata, .DS_Store|$FINDER_METADATA_SENTINEL|system_cache"
|
|
}
|
|
|
|
# Get all optimize items with their patterns
|
|
get_optimize_whitelist_items() {
|
|
# Format: "display_name|pattern|category"
|
|
cat << 'EOF'
|
|
macOS Firewall check|firewall|security_check
|
|
Gatekeeper check|gatekeeper|security_check
|
|
macOS system updates check|check_macos_updates|update_check
|
|
Mole updates check|check_mole_update|update_check
|
|
Homebrew health check (doctor)|check_brew_health|health_check
|
|
SIP status check|check_sip|security_check
|
|
FileVault status check|check_filevault|security_check
|
|
TouchID sudo check|check_touchid|config_check
|
|
Rosetta 2 check|check_rosetta|config_check
|
|
Git configuration check|check_git_config|config_check
|
|
Login items check|check_login_items|config_check
|
|
EOF
|
|
}
|
|
|
|
patterns_equivalent() {
|
|
local first="${1/#~/$HOME}"
|
|
local second="${2/#~/$HOME}"
|
|
|
|
# Only exact string match, no glob expansion
|
|
[[ "$first" == "$second" ]] && return 0
|
|
return 1
|
|
}
|
|
|
|
load_whitelist() {
|
|
local mode="${1:-clean}"
|
|
local -a patterns=()
|
|
local config_file
|
|
local legacy_file=""
|
|
|
|
if [[ "$mode" == "optimize" ]]; then
|
|
config_file="$WHITELIST_CONFIG_OPTIMIZE"
|
|
legacy_file="$WHITELIST_CONFIG_OPTIMIZE_LEGACY"
|
|
else
|
|
config_file="$WHITELIST_CONFIG_CLEAN"
|
|
fi
|
|
|
|
local using_legacy="false"
|
|
if [[ ! -f "$config_file" && -n "$legacy_file" && -f "$legacy_file" ]]; then
|
|
config_file="$legacy_file"
|
|
using_legacy="true"
|
|
fi
|
|
|
|
if [[ -f "$config_file" ]]; then
|
|
while IFS= read -r line; do
|
|
# shellcheck disable=SC2295
|
|
line="${line#"${line%%[![:space:]]*}"}"
|
|
# shellcheck disable=SC2295
|
|
line="${line%"${line##*[![:space:]]}"}"
|
|
[[ -z "$line" || "$line" =~ ^# ]] && continue
|
|
patterns+=("$line")
|
|
done < "$config_file"
|
|
else
|
|
if [[ "$mode" == "clean" ]]; then
|
|
patterns=("${DEFAULT_WHITELIST_PATTERNS[@]}")
|
|
elif [[ "$mode" == "optimize" ]]; then
|
|
patterns=("${DEFAULT_OPTIMIZE_WHITELIST_PATTERNS[@]}")
|
|
fi
|
|
fi
|
|
|
|
if [[ ${#patterns[@]} -gt 0 ]]; then
|
|
local -a unique_patterns=()
|
|
for pattern in "${patterns[@]}"; do
|
|
local duplicate="false"
|
|
if [[ ${#unique_patterns[@]} -gt 0 ]]; then
|
|
for existing in "${unique_patterns[@]}"; do
|
|
if patterns_equivalent "$pattern" "$existing"; then
|
|
duplicate="true"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
[[ "$duplicate" == "true" ]] && continue
|
|
unique_patterns+=("$pattern")
|
|
done
|
|
CURRENT_WHITELIST_PATTERNS=("${unique_patterns[@]}")
|
|
|
|
# Migrate legacy optimize config to the new path automatically
|
|
if [[ "$mode" == "optimize" && "$using_legacy" == "true" && "$config_file" != "$WHITELIST_CONFIG_OPTIMIZE" ]]; then
|
|
save_whitelist_patterns "$mode" "${CURRENT_WHITELIST_PATTERNS[@]}"
|
|
fi
|
|
else
|
|
CURRENT_WHITELIST_PATTERNS=()
|
|
fi
|
|
}
|
|
|
|
is_whitelisted() {
|
|
local pattern="$1"
|
|
local check_pattern="${pattern/#\~/$HOME}"
|
|
|
|
if [[ ${#CURRENT_WHITELIST_PATTERNS[@]} -eq 0 ]]; then
|
|
return 1
|
|
fi
|
|
|
|
for existing in "${CURRENT_WHITELIST_PATTERNS[@]}"; do
|
|
local existing_expanded="${existing/#\~/$HOME}"
|
|
# Only use exact string match to prevent glob expansion security issues
|
|
if [[ "$check_pattern" == "$existing_expanded" ]]; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
manage_whitelist() {
|
|
local mode="${1:-clean}"
|
|
manage_whitelist_categories "$mode"
|
|
}
|
|
|
|
manage_whitelist_categories() {
|
|
local mode="$1"
|
|
|
|
# Load currently enabled patterns from both sources
|
|
load_whitelist "$mode"
|
|
|
|
# Build cache items list
|
|
local -a cache_items=()
|
|
local -a cache_patterns=()
|
|
local -a menu_options=()
|
|
local index=0
|
|
|
|
# Choose source based on mode
|
|
local items_source
|
|
local menu_title
|
|
local active_config_file
|
|
|
|
if [[ "$mode" == "optimize" ]]; then
|
|
items_source=$(get_optimize_whitelist_items)
|
|
active_config_file="$WHITELIST_CONFIG_OPTIMIZE"
|
|
local display_config="${active_config_file/#$HOME/~}"
|
|
menu_title="Whitelist Manager, Select system checks to ignore
|
|
${GRAY}Edit: ${display_config}${NC}"
|
|
else
|
|
items_source=$(get_all_cache_items)
|
|
active_config_file="$WHITELIST_CONFIG_CLEAN"
|
|
local display_config="${active_config_file/#$HOME/~}"
|
|
menu_title="Whitelist Manager, Select caches to protect
|
|
${GRAY}Edit: ${display_config}${NC}"
|
|
fi
|
|
|
|
while IFS='|' read -r display_name pattern _; do
|
|
# Expand $HOME in pattern
|
|
pattern="${pattern/\$HOME/$HOME}"
|
|
|
|
cache_items+=("$display_name")
|
|
cache_patterns+=("$pattern")
|
|
menu_options+=("$display_name")
|
|
|
|
((index++)) || true
|
|
done <<< "$items_source"
|
|
|
|
# Identify custom patterns (not in predefined list)
|
|
local -a custom_patterns=()
|
|
if [[ ${#CURRENT_WHITELIST_PATTERNS[@]} -gt 0 ]]; then
|
|
for current_pattern in "${CURRENT_WHITELIST_PATTERNS[@]}"; do
|
|
local is_predefined=false
|
|
for predefined_pattern in "${cache_patterns[@]}"; do
|
|
if patterns_equivalent "$current_pattern" "$predefined_pattern"; then
|
|
is_predefined=true
|
|
break
|
|
fi
|
|
done
|
|
if [[ "$is_predefined" == "false" ]]; then
|
|
custom_patterns+=("$current_pattern")
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Prioritize already-selected items to appear first
|
|
local -a selected_cache_items=()
|
|
local -a selected_cache_patterns=()
|
|
local -a selected_menu_options=()
|
|
local -a remaining_cache_items=()
|
|
local -a remaining_cache_patterns=()
|
|
local -a remaining_menu_options=()
|
|
|
|
for ((i = 0; i < ${#cache_patterns[@]}; i++)); do
|
|
if is_whitelisted "${cache_patterns[i]}"; then
|
|
selected_cache_items+=("${cache_items[i]}")
|
|
selected_cache_patterns+=("${cache_patterns[i]}")
|
|
selected_menu_options+=("${menu_options[i]}")
|
|
else
|
|
remaining_cache_items+=("${cache_items[i]}")
|
|
remaining_cache_patterns+=("${cache_patterns[i]}")
|
|
remaining_menu_options+=("${menu_options[i]}")
|
|
fi
|
|
done
|
|
|
|
cache_items=()
|
|
cache_patterns=()
|
|
menu_options=()
|
|
if [[ ${#selected_cache_items[@]} -gt 0 ]]; then
|
|
cache_items=("${selected_cache_items[@]}")
|
|
cache_patterns=("${selected_cache_patterns[@]}")
|
|
menu_options=("${selected_menu_options[@]}")
|
|
fi
|
|
if [[ ${#remaining_cache_items[@]} -gt 0 ]]; then
|
|
cache_items+=("${remaining_cache_items[@]}")
|
|
cache_patterns+=("${remaining_cache_patterns[@]}")
|
|
menu_options+=("${remaining_menu_options[@]}")
|
|
fi
|
|
|
|
if [[ ${#selected_cache_patterns[@]} -gt 0 ]]; then
|
|
local -a preselected_indices=()
|
|
for ((i = 0; i < ${#selected_cache_patterns[@]}; i++)); do
|
|
preselected_indices+=("$i")
|
|
done
|
|
local IFS=','
|
|
export MOLE_PRESELECTED_INDICES="${preselected_indices[*]}"
|
|
else
|
|
unset MOLE_PRESELECTED_INDICES
|
|
fi
|
|
|
|
MOLE_SELECTION_RESULT=""
|
|
paginated_multi_select "$menu_title" "${menu_options[@]}"
|
|
unset MOLE_PRESELECTED_INDICES
|
|
local exit_code=$?
|
|
|
|
# Normal exit or cancel
|
|
if [[ $exit_code -ne 0 ]]; then
|
|
return 1
|
|
fi
|
|
|
|
# Convert selected indices to patterns
|
|
local -a selected_patterns=()
|
|
if [[ -n "$MOLE_SELECTION_RESULT" ]]; then
|
|
local -a selected_indices
|
|
IFS=',' read -ra selected_indices <<< "$MOLE_SELECTION_RESULT"
|
|
for idx in "${selected_indices[@]}"; do
|
|
if [[ $idx -ge 0 && $idx -lt ${#cache_patterns[@]} ]]; then
|
|
local pattern="${cache_patterns[$idx]}"
|
|
# Convert back to portable format with ~
|
|
pattern="${pattern/#$HOME/~}"
|
|
selected_patterns+=("$pattern")
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Merge custom patterns with selected patterns
|
|
local -a all_patterns=()
|
|
if [[ ${#selected_patterns[@]} -gt 0 ]]; then
|
|
all_patterns=("${selected_patterns[@]}")
|
|
fi
|
|
if [[ ${#custom_patterns[@]} -gt 0 ]]; then
|
|
for custom_pattern in "${custom_patterns[@]}"; do
|
|
all_patterns+=("$custom_pattern")
|
|
done
|
|
fi
|
|
|
|
# Save to whitelist config (bash 3.2 + set -u safe)
|
|
if [[ ${#all_patterns[@]} -gt 0 ]]; then
|
|
save_whitelist_patterns "$mode" "${all_patterns[@]}"
|
|
else
|
|
save_whitelist_patterns "$mode"
|
|
fi
|
|
|
|
local total_protected=$((${#selected_patterns[@]} + ${#custom_patterns[@]}))
|
|
local -a summary_lines=()
|
|
summary_lines+=("Whitelist Updated")
|
|
if [[ ${#custom_patterns[@]} -gt 0 ]]; then
|
|
summary_lines+=("Protected ${#selected_patterns[@]} predefined + ${#custom_patterns[@]} custom patterns")
|
|
else
|
|
summary_lines+=("Protected ${total_protected} caches")
|
|
fi
|
|
local display_config="${active_config_file/#$HOME/~}"
|
|
summary_lines+=("Config: ${GRAY}${display_config}${NC}")
|
|
|
|
print_summary_block "${summary_lines[@]}"
|
|
printf '\n'
|
|
}
|
|
|
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
manage_whitelist
|
|
fi
|