mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 17:24:45 +00:00
docs: Improve clarity and conciseness of comments and documentation
This commit is contained in:
@@ -54,8 +54,8 @@ Config: `.editorconfig` and `.shellcheckrc`
|
|||||||
# Single file/directory
|
# Single file/directory
|
||||||
safe_remove "/path/to/file"
|
safe_remove "/path/to/file"
|
||||||
|
|
||||||
# Batch delete with find
|
# Purge files older than 7 days
|
||||||
safe_find_delete "$dir" "*.log" 7 "f" # files older than 7 days
|
safe_find_delete "$dir" "*.log" 7 "f"
|
||||||
|
|
||||||
# With sudo
|
# With sudo
|
||||||
safe_sudo_remove "/Library/Caches/com.example"
|
safe_sudo_remove "/Library/Caches/com.example"
|
||||||
@@ -137,7 +137,7 @@ Format: `[MODULE_NAME] message` output to stderr.
|
|||||||
- macOS 10.14 or newer, works on Intel and Apple Silicon
|
- macOS 10.14 or newer, works on Intel and Apple Silicon
|
||||||
- Default macOS Bash 3.2+ plus administrator privileges for cleanup tasks
|
- Default macOS Bash 3.2+ plus administrator privileges for cleanup tasks
|
||||||
- Install Command Line Tools with `xcode-select --install` for curl, tar, and related utilities
|
- Install Command Line Tools with `xcode-select --install` for curl, tar, and related utilities
|
||||||
- Go 1.24+ required when building the `mo status` or `mo analyze` TUI binaries locally
|
- Go 1.24+ is required to build the `mo status` or `mo analyze` TUI binaries locally.
|
||||||
|
|
||||||
## Go Components
|
## Go Components
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ Format: `[MODULE_NAME] message` output to stderr.
|
|||||||
- Keep files focused on single responsibility
|
- Keep files focused on single responsibility
|
||||||
- Extract constants instead of magic numbers
|
- Extract constants instead of magic numbers
|
||||||
- Use context for timeout control on external commands
|
- Use context for timeout control on external commands
|
||||||
- Add comments explaining why, not what
|
- Add comments explaining **why** something is done, not just **what** is being done.
|
||||||
|
|
||||||
## Pull Requests
|
## Pull Requests
|
||||||
|
|
||||||
|
|||||||
20
README.md
20
README.md
@@ -18,11 +18,11 @@
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **All-in-one toolkit** combining CleanMyMac, AppCleaner, DaisyDisk, Sensei, and iStat in one **trusted binary**
|
- **Unified toolkit**: Consolidated features of CleanMyMac, AppCleaner, DaisyDisk, and iStat into a **single binary**
|
||||||
- **Deep cleanup** scans and removes caches, logs, browser leftovers, and junk to **reclaim tens of gigabytes**
|
- **Deep cleaning**: Scans and removes caches, logs, and browser leftovers to **reclaim gigabytes of space**
|
||||||
- **Smart uninstall** completely removes apps including launch agents, preferences, caches, and **hidden leftovers**
|
- **Smart uninstaller**: Thoroughly removes apps along with launch agents, preferences, and **hidden remnants**
|
||||||
- **Disk insight + optimization** visualizes usage, handles large files, **rebuilds caches**, and refreshes services
|
- **Disk insights**: Visualizes usage, manages large files, **rebuilds caches**, and refreshes system services
|
||||||
- **Live status** monitors CPU, GPU, memory, disk, network, battery, and proxy stats to **diagnose issues**
|
- **Live monitoring**: Real-time stats for CPU, GPU, memory, disk, and network to **diagnose performance issues**
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
@@ -55,10 +55,10 @@ mo remove # Remove Mole from system
|
|||||||
mo --help # Show help
|
mo --help # Show help
|
||||||
mo --version # Show installed version
|
mo --version # Show installed version
|
||||||
|
|
||||||
mo clean --dry-run # Preview cleanup plan
|
mo clean --dry-run # Preview the cleanup plan
|
||||||
mo clean --whitelist # Adjust protected caches
|
mo clean --whitelist # Manage protected caches
|
||||||
mo uninstall --force-rescan # Rescan apps and refresh cache
|
mo uninstall --force-rescan # Rescan applications and refresh cache
|
||||||
mo optimize --whitelist # Adjust protected optimization items
|
mo optimize --whitelist # Manage protected optimization rules
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tips
|
## Tips
|
||||||
@@ -210,7 +210,7 @@ Launch Mole commands instantly from Raycast or Alfred:
|
|||||||
curl -fsSL https://raw.githubusercontent.com/tw93/Mole/main/scripts/setup-quick-launchers.sh | bash
|
curl -fsSL https://raw.githubusercontent.com/tw93/Mole/main/scripts/setup-quick-launchers.sh | bash
|
||||||
```
|
```
|
||||||
|
|
||||||
Adds 5 commands: `clean`, `uninstall`, `optimize`, `analyze`, `status`. Finds your terminal automatically or set `MO_LAUNCHER_APP=<name>` to override. For Raycast, search "Reload Script Directories" to load new commands.
|
Adds 5 commands: `clean`, `uninstall`, `optimize`, `analyze`, `status`. Mole automatically detects your terminal, or you can set `MO_LAUNCHER_APP=<name>` to override. For Raycast, run "Reload Script Directories" to load the new commands.
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,6 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Clean Xcode and iOS development tools
|
# Clean Xcode and iOS development tools
|
||||||
# Archives can be significant in size (app packaging files)
|
|
||||||
# DeviceSupport files for old iOS versions can accumulate
|
|
||||||
# Note: Skips critical files if Xcode is running
|
|
||||||
clean_xcode_tools() {
|
clean_xcode_tools() {
|
||||||
# Check if Xcode is running for safer cleanup of critical resources
|
# Check if Xcode is running for safer cleanup of critical resources
|
||||||
local xcode_running=false
|
local xcode_running=false
|
||||||
@@ -107,9 +104,7 @@ clean_productivity_apps() {
|
|||||||
safe_clean ~/Library/Caches/com.flomoapp.mac/* "Flomo cache"
|
safe_clean ~/Library/Caches/com.flomoapp.mac/* "Flomo cache"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Clean music and media players
|
# Clean music and media players (protects Spotify offline music)
|
||||||
# Note: Spotify cache is protected by default (may contain offline music)
|
|
||||||
# Users can override via whitelist settings
|
|
||||||
clean_media_players() {
|
clean_media_players() {
|
||||||
# Spotify cache protection: check for offline music indicators
|
# Spotify cache protection: check for offline music indicators
|
||||||
local spotify_cache="$HOME/Library/Caches/com.spotify.client"
|
local spotify_cache="$HOME/Library/Caches/com.spotify.client"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ clean_deep_system() {
|
|||||||
safe_sudo_find_delete "/Library/Caches" "*.tmp" "$MOLE_TEMP_FILE_AGE_DAYS" "f" || true
|
safe_sudo_find_delete "/Library/Caches" "*.tmp" "$MOLE_TEMP_FILE_AGE_DAYS" "f" || true
|
||||||
safe_sudo_find_delete "/Library/Caches" "*.log" "$MOLE_LOG_AGE_DAYS" "f" || true
|
safe_sudo_find_delete "/Library/Caches" "*.log" "$MOLE_LOG_AGE_DAYS" "f" || true
|
||||||
|
|
||||||
# Clean temp files - use real paths (macOS /tmp is symlink to /private/tmp)
|
# Clean temporary files (macOS /tmp is a symlink to /private/tmp)
|
||||||
local tmp_cleaned=0
|
local tmp_cleaned=0
|
||||||
safe_sudo_find_delete "/private/tmp" "*" "${MOLE_TEMP_FILE_AGE_DAYS}" "f" && tmp_cleaned=1 || true
|
safe_sudo_find_delete "/private/tmp" "*" "${MOLE_TEMP_FILE_AGE_DAYS}" "f" && tmp_cleaned=1 || true
|
||||||
safe_sudo_find_delete "/private/var/tmp" "*" "${MOLE_TEMP_FILE_AGE_DAYS}" "f" && tmp_cleaned=1 || true
|
safe_sudo_find_delete "/private/var/tmp" "*" "${MOLE_TEMP_FILE_AGE_DAYS}" "f" && tmp_cleaned=1 || true
|
||||||
@@ -21,13 +21,12 @@ clean_deep_system() {
|
|||||||
safe_sudo_find_delete "/Library/Logs/DiagnosticReports" "*" "$MOLE_CRASH_REPORT_AGE_DAYS" "f" || true
|
safe_sudo_find_delete "/Library/Logs/DiagnosticReports" "*" "$MOLE_CRASH_REPORT_AGE_DAYS" "f" || true
|
||||||
log_success "System crash reports"
|
log_success "System crash reports"
|
||||||
|
|
||||||
# Clean system logs - use real path (macOS /var is symlink to /private/var)
|
# Clean system logs (macOS /var is a symlink to /private/var)
|
||||||
safe_sudo_find_delete "/private/var/log" "*.log" "$MOLE_LOG_AGE_DAYS" "f" || true
|
safe_sudo_find_delete "/private/var/log" "*.log" "$MOLE_LOG_AGE_DAYS" "f" || true
|
||||||
safe_sudo_find_delete "/private/var/log" "*.gz" "$MOLE_LOG_AGE_DAYS" "f" || true
|
safe_sudo_find_delete "/private/var/log" "*.gz" "$MOLE_LOG_AGE_DAYS" "f" || true
|
||||||
log_success "System logs"
|
log_success "System logs"
|
||||||
|
|
||||||
# Clean Library Updates safely - skip if SIP is enabled to avoid error messages
|
# Clean Library Updates safely (skip if SIP is enabled)
|
||||||
# SIP-protected files in /Library/Updates cannot be deleted even with sudo
|
|
||||||
if [[ -d "/Library/Updates" && ! -L "/Library/Updates" ]]; then
|
if [[ -d "/Library/Updates" && ! -L "/Library/Updates" ]]; then
|
||||||
if ! is_sip_enabled; then
|
if ! is_sip_enabled; then
|
||||||
# SIP is disabled, attempt cleanup with restricted flag check
|
# SIP is disabled, attempt cleanup with restricted flag check
|
||||||
@@ -54,8 +53,7 @@ clean_deep_system() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Clean macOS Install Data (system upgrade leftovers)
|
# Clean macOS Install Data (legacy upgrade leftovers)
|
||||||
# Only remove if older than 30 days to ensure system stability
|
|
||||||
if [[ -d "/macOS Install Data" ]]; then
|
if [[ -d "/macOS Install Data" ]]; then
|
||||||
local mtime=$(get_file_mtime "/macOS Install Data")
|
local mtime=$(get_file_mtime "/macOS Install Data")
|
||||||
local age_days=$((($(date +%s) - mtime) / 86400))
|
local age_days=$((($(date +%s) - mtime) / 86400))
|
||||||
@@ -78,22 +76,20 @@ clean_deep_system() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Clean browser code signature caches
|
# Clean browser code signature caches
|
||||||
# These are regenerated automatically when needed
|
|
||||||
if [[ -t 1 ]]; then
|
if [[ -t 1 ]]; then
|
||||||
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Scanning system caches..."
|
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Scanning system caches..."
|
||||||
fi
|
fi
|
||||||
local code_sign_cleaned=0
|
local code_sign_cleaned=0
|
||||||
local found_count=0
|
local found_count=0
|
||||||
|
|
||||||
# Stream processing with progress updates (efficient for large directories)
|
# Efficient stream processing for large directories
|
||||||
# Reduce timeout to 5s for faster completion when no caches exist
|
|
||||||
while IFS= read -r -d '' cache_dir; do
|
while IFS= read -r -d '' cache_dir; do
|
||||||
if safe_remove "$cache_dir" true; then
|
if safe_remove "$cache_dir" true; then
|
||||||
((code_sign_cleaned++))
|
((code_sign_cleaned++))
|
||||||
fi
|
fi
|
||||||
((found_count++))
|
((found_count++))
|
||||||
|
|
||||||
# Update spinner every 50 items to show progress
|
# Update progress spinner periodically
|
||||||
if [[ -t 1 ]] && ((found_count % 50 == 0)); then
|
if [[ -t 1 ]] && ((found_count % 50 == 0)); then
|
||||||
stop_inline_spinner
|
stop_inline_spinner
|
||||||
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Scanning system caches... ($found_count found)"
|
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Scanning system caches... ($found_count found)"
|
||||||
@@ -115,7 +111,7 @@ clean_deep_system() {
|
|||||||
log_success "Power logs"
|
log_success "Power logs"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Clean Time Machine incomplete backups
|
# Clean incomplete Time Machine backups
|
||||||
clean_time_machine_failed_backups() {
|
clean_time_machine_failed_backups() {
|
||||||
local tm_cleaned=0
|
local tm_cleaned=0
|
||||||
|
|
||||||
@@ -324,7 +320,7 @@ clean_time_machine_failed_backups() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Clean local APFS snapshots (older than 24h)
|
# Clean local APFS snapshots (older than 24 hours)
|
||||||
clean_local_snapshots() {
|
clean_local_snapshots() {
|
||||||
# Check if tmutil is available
|
# Check if tmutil is available
|
||||||
if ! command -v tmutil > /dev/null 2>&1; then
|
if ! command -v tmutil > /dev/null 2>&1; then
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ _MOLE_CORE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
[[ -z "${MOLE_BASE_LOADED:-}" ]] && source "$_MOLE_CORE_DIR/base.sh"
|
[[ -z "${MOLE_BASE_LOADED:-}" ]] && source "$_MOLE_CORE_DIR/base.sh"
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# App Management Functions
|
# Application Management
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# System critical components that should NEVER be uninstalled
|
# Critical system components protected from uninstallation
|
||||||
readonly SYSTEM_CRITICAL_BUNDLES=(
|
readonly SYSTEM_CRITICAL_BUNDLES=(
|
||||||
"com.apple.*" # System essentials
|
"com.apple.*" # System essentials
|
||||||
"loginwindow"
|
"loginwindow"
|
||||||
@@ -68,7 +68,7 @@ readonly SYSTEM_CRITICAL_BUNDLES=(
|
|||||||
"com.apple.TextInputSwitcher"
|
"com.apple.TextInputSwitcher"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Apps with important data/licenses - protect during cleanup but allow uninstall
|
# Applications with sensitive data; protected during cleanup but removable
|
||||||
readonly DATA_PROTECTED_BUNDLES=(
|
readonly DATA_PROTECTED_BUNDLES=(
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# System Utilities & Cleanup Tools
|
# System Utilities & Cleanup Tools
|
||||||
@@ -165,7 +165,7 @@ readonly DATA_PROTECTED_BUNDLES=(
|
|||||||
"com.usebruno.app" # Bruno (API client)
|
"com.usebruno.app" # Bruno (API client)
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Network Proxy & VPN Tools (Broad Glob Protection)
|
# Network Proxy & VPN Tools (pattern-based protection)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Clash variants
|
# Clash variants
|
||||||
"*clash*" # All Clash variants (ClashX, ClashX Pro, Clash Verge, etc)
|
"*clash*" # All Clash variants (ClashX, ClashX Pro, Clash Verge, etc)
|
||||||
@@ -439,7 +439,7 @@ readonly DATA_PROTECTED_BUNDLES=(
|
|||||||
# Use should_protect_from_uninstall() or should_protect_data() instead
|
# Use should_protect_from_uninstall() or should_protect_data() instead
|
||||||
readonly PRESERVED_BUNDLE_PATTERNS=("${SYSTEM_CRITICAL_BUNDLES[@]}" "${DATA_PROTECTED_BUNDLES[@]}")
|
readonly PRESERVED_BUNDLE_PATTERNS=("${SYSTEM_CRITICAL_BUNDLES[@]}" "${DATA_PROTECTED_BUNDLES[@]}")
|
||||||
|
|
||||||
# Check whether a bundle ID matches a pattern (supports globs)
|
# Check if bundle ID matches pattern (glob support)
|
||||||
bundle_matches_pattern() {
|
bundle_matches_pattern() {
|
||||||
local bundle_id="$1"
|
local bundle_id="$1"
|
||||||
local pattern="$2"
|
local pattern="$2"
|
||||||
@@ -454,7 +454,7 @@ bundle_matches_pattern() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if app is a system component that should never be uninstalled
|
# Check if application is a protected system component
|
||||||
should_protect_from_uninstall() {
|
should_protect_from_uninstall() {
|
||||||
local bundle_id="$1"
|
local bundle_id="$1"
|
||||||
for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}"; do
|
for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}"; do
|
||||||
@@ -465,7 +465,7 @@ should_protect_from_uninstall() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if app data should be protected during cleanup (but app can be uninstalled)
|
# Check if application data should be protected during cleanup
|
||||||
should_protect_data() {
|
should_protect_data() {
|
||||||
local bundle_id="$1"
|
local bundle_id="$1"
|
||||||
# Protect both system critical and data protected bundles during cleanup
|
# Protect both system critical and data protected bundles during cleanup
|
||||||
@@ -477,7 +477,7 @@ should_protect_data() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if a specific path should be protected from deletion
|
# Check if a path is protected from deletion
|
||||||
# Centralized logic to protect system settings, control center, and critical apps
|
# Centralized logic to protect system settings, control center, and critical apps
|
||||||
#
|
#
|
||||||
# Args: $1 - path to check
|
# Args: $1 - path to check
|
||||||
@@ -489,7 +489,7 @@ should_protect_path() {
|
|||||||
local path_lower
|
local path_lower
|
||||||
path_lower=$(echo "$path" | tr '[:upper:]' '[:lower:]')
|
path_lower=$(echo "$path" | tr '[:upper:]' '[:lower:]')
|
||||||
|
|
||||||
# 1. Check for explicit critical system keywords in path (case-insensitive)
|
# 1. Keyword-based matching for system components
|
||||||
# Protect System Settings, Preferences, Control Center, and related XPC services
|
# Protect System Settings, Preferences, Control Center, and related XPC services
|
||||||
# Also protect "Settings" (used in macOS Sequoia) and savedState files
|
# Also protect "Settings" (used in macOS Sequoia) and savedState files
|
||||||
if [[ "$path_lower" =~ systemsettings || "$path_lower" =~ systempreferences || "$path_lower" =~ controlcenter ]]; then
|
if [[ "$path_lower" =~ systemsettings || "$path_lower" =~ systempreferences || "$path_lower" =~ controlcenter ]]; then
|
||||||
@@ -501,7 +501,7 @@ should_protect_path() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 2. Protect system-critical cache directories that cause UI corruption
|
# 2. Protect caches critical for system UI rendering
|
||||||
# These caches are essential for modern macOS (Sonoma/Sequoia) system UI rendering
|
# These caches are essential for modern macOS (Sonoma/Sequoia) system UI rendering
|
||||||
case "$path" in
|
case "$path" in
|
||||||
# System Settings and Control Center caches (CRITICAL - prevents blank panel bug)
|
# System Settings and Control Center caches (CRITICAL - prevents blank panel bug)
|
||||||
@@ -525,7 +525,7 @@ should_protect_path() {
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# 3. Extract bundle ID from app container/group container paths
|
# 3. Extract bundle ID from sandbox paths
|
||||||
# Matches: .../Library/Containers/bundle.id/...
|
# Matches: .../Library/Containers/bundle.id/...
|
||||||
# Matches: .../Library/Group Containers/group.id/...
|
# Matches: .../Library/Group Containers/group.id/...
|
||||||
if [[ "$path" =~ /Library/Containers/([^/]+) ]] || [[ "$path" =~ /Library/Group\ Containers/([^/]+) ]]; then
|
if [[ "$path" =~ /Library/Containers/([^/]+) ]] || [[ "$path" =~ /Library/Group\ Containers/([^/]+) ]]; then
|
||||||
@@ -553,7 +553,7 @@ should_protect_path() {
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# 6. Check the full path against protected patterns (Broad Glob Match)
|
# 6. Match full path against protected patterns
|
||||||
# This catches things like /Users/tw93/Library/Caches/Claude when pattern is *Claude*
|
# This catches things like /Users/tw93/Library/Caches/Claude when pattern is *Claude*
|
||||||
for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}" "${DATA_PROTECTED_BUNDLES[@]}"; do
|
for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}" "${DATA_PROTECTED_BUNDLES[@]}"; do
|
||||||
if bundle_matches_pattern "$path" "$pattern"; then
|
if bundle_matches_pattern "$path" "$pattern"; then
|
||||||
@@ -571,13 +571,13 @@ should_protect_path() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Find and list app-related files (consolidated from duplicates)
|
# Locate files associated with an application
|
||||||
find_app_files() {
|
find_app_files() {
|
||||||
local bundle_id="$1"
|
local bundle_id="$1"
|
||||||
local app_name="$2"
|
local app_name="$2"
|
||||||
local -a files_to_clean=()
|
local -a files_to_clean=()
|
||||||
|
|
||||||
# Sanitized App Name (remove spaces)
|
# Normalize app name for matching
|
||||||
local nospace_name="${app_name// /}"
|
local nospace_name="${app_name// /}"
|
||||||
local underscore_name="${app_name// /_}"
|
local underscore_name="${app_name// /_}"
|
||||||
|
|
||||||
@@ -635,7 +635,7 @@ find_app_files() {
|
|||||||
[[ -e "$expanded_path" ]] && files_to_clean+=("$expanded_path")
|
[[ -e "$expanded_path" ]] && files_to_clean+=("$expanded_path")
|
||||||
done
|
done
|
||||||
|
|
||||||
# Preferences and ByHost (special handling)
|
# Handle Preferences and ByHost variants
|
||||||
[[ -f ~/Library/Preferences/"$bundle_id".plist ]] && files_to_clean+=("$HOME/Library/Preferences/$bundle_id.plist")
|
[[ -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
|
[[ -d ~/Library/Preferences/ByHost ]] && while IFS= read -r -d '' pref; do
|
||||||
files_to_clean+=("$pref")
|
files_to_clean+=("$pref")
|
||||||
@@ -655,7 +655,7 @@ find_app_files() {
|
|||||||
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
|
fi
|
||||||
|
|
||||||
# Specialized toolchain cleanup (non-loopable or highly specific)
|
# Handle specialized toolchains and development environments
|
||||||
# 1. DevEco-Studio (Huawei)
|
# 1. DevEco-Studio (Huawei)
|
||||||
if [[ "$app_name" =~ DevEco|deveco ]] || [[ "$bundle_id" =~ huawei.*deveco ]]; then
|
if [[ "$app_name" =~ DevEco|deveco ]] || [[ "$bundle_id" =~ huawei.*deveco ]]; then
|
||||||
for d in ~/DevEcoStudioProjects ~/DevEco-Studio ~/Library/Application\ Support/Huawei ~/Library/Caches/Huawei ~/Library/Logs/Huawei ~/Library/Huawei ~/Huawei ~/HarmonyOS ~/.huawei ~/.ohos; do
|
for d in ~/DevEcoStudioProjects ~/DevEco-Studio ~/Library/Application\ Support/Huawei ~/Library/Caches/Huawei ~/Library/Logs/Huawei ~/Library/Huawei ~/Huawei ~/HarmonyOS ~/.huawei ~/.ohos; do
|
||||||
@@ -697,7 +697,7 @@ find_app_files() {
|
|||||||
[[ ${#files_to_clean[@]} -gt 0 ]] && printf '%s\n' "${files_to_clean[@]}"
|
[[ ${#files_to_clean[@]} -gt 0 ]] && printf '%s\n' "${files_to_clean[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Find system-level app files (requires sudo)
|
# Locate system-level application files
|
||||||
find_app_system_files() {
|
find_app_system_files() {
|
||||||
local bundle_id="$1"
|
local bundle_id="$1"
|
||||||
local app_name="$2"
|
local app_name="$2"
|
||||||
@@ -765,7 +765,7 @@ find_app_system_files() {
|
|||||||
find_app_receipt_files "$bundle_id"
|
find_app_receipt_files "$bundle_id"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Find files from installation receipts (Bom files)
|
# Locate files using installation receipts (BOM)
|
||||||
find_app_receipt_files() {
|
find_app_receipt_files() {
|
||||||
local bundle_id="$1"
|
local bundle_id="$1"
|
||||||
|
|
||||||
@@ -798,13 +798,13 @@ find_app_receipt_files() {
|
|||||||
# Standardize path (remove leading dot)
|
# Standardize path (remove leading dot)
|
||||||
local clean_path="${file_path#.}"
|
local clean_path="${file_path#.}"
|
||||||
|
|
||||||
# Ensure it starts with /
|
# Ensure absolute path
|
||||||
if [[ "$clean_path" != /* ]]; then
|
if [[ "$clean_path" != /* ]]; then
|
||||||
clean_path="/$clean_path"
|
clean_path="/$clean_path"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ------------------------------------------------------------------------
|
# ------------------------------------------------------------------------
|
||||||
# SAFETY FILTER: Only allow specific removal paths
|
# Safety check: restrict removal to trusted paths
|
||||||
# ------------------------------------------------------------------------
|
# ------------------------------------------------------------------------
|
||||||
local is_safe=false
|
local is_safe=false
|
||||||
|
|
||||||
@@ -839,15 +839,7 @@ find_app_receipt_files() {
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
if [[ "$is_safe" == "true" && -e "$clean_path" ]]; then
|
if [[ "$is_safe" == "true" && -e "$clean_path" ]]; then
|
||||||
# Only valid files
|
# If lsbom lists /Applications, skip to avoid system damage.
|
||||||
# Don't delete directories if they are non-empty parents?
|
|
||||||
# lsbom lists directories too.
|
|
||||||
# If we return a directory, `safe_remove` logic handles it.
|
|
||||||
# `uninstall.sh` uses `remove_file_list`.
|
|
||||||
# If `lsbom` lists `/Applications` (it shouldn't, only contents), we must be careful.
|
|
||||||
# `lsbom` usually lists `./Applications/MyApp.app`.
|
|
||||||
# If it lists `./Applications`, we must skip it.
|
|
||||||
|
|
||||||
# Extra check: path must be deep enough?
|
# Extra check: path must be deep enough?
|
||||||
# If path is just "/Applications", skip.
|
# If path is just "/Applications", skip.
|
||||||
if [[ "$clean_path" == "/Applications" || "$clean_path" == "/Library" || "$clean_path" == "/usr/local" ]]; then
|
if [[ "$clean_path" == "/Applications" || "$clean_path" == "/Library" || "$clean_path" == "/usr/local" ]]; then
|
||||||
@@ -865,9 +857,9 @@ find_app_receipt_files() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Force quit an application
|
# Terminate a running application
|
||||||
force_kill_app() {
|
force_kill_app() {
|
||||||
# Args: app_name [app_path]; tries graceful then force kill; returns 0 if stopped, 1 otherwise
|
# Gracefully terminates or force-kills an application
|
||||||
local app_name="$1"
|
local app_name="$1"
|
||||||
local app_path="${2:-""}"
|
local app_path="${2:-""}"
|
||||||
|
|
||||||
|
|||||||
@@ -44,15 +44,15 @@ readonly ICON_NAV_RIGHT="→"
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Global Configuration Constants
|
# Global Configuration Constants
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
readonly MOLE_TEMP_FILE_AGE_DAYS=7 # Temp file cleanup threshold
|
readonly MOLE_TEMP_FILE_AGE_DAYS=7 # Temp file retention (days)
|
||||||
readonly MOLE_ORPHAN_AGE_DAYS=60 # Orphaned data threshold
|
readonly MOLE_ORPHAN_AGE_DAYS=60 # Orphaned data retention (days)
|
||||||
readonly MOLE_MAX_PARALLEL_JOBS=15 # Parallel job limit
|
readonly MOLE_MAX_PARALLEL_JOBS=15 # Parallel job limit
|
||||||
readonly MOLE_MAIL_DOWNLOADS_MIN_KB=5120 # Mail attachments size threshold
|
readonly MOLE_MAIL_DOWNLOADS_MIN_KB=5120 # Mail attachment size threshold
|
||||||
readonly MOLE_MAIL_AGE_DAYS=30 # Mail attachment cleanup threshold (30+ days)
|
readonly MOLE_MAIL_AGE_DAYS=30 # Mail attachment retention (days)
|
||||||
readonly MOLE_LOG_AGE_DAYS=7 # System log retention
|
readonly MOLE_LOG_AGE_DAYS=7 # Log retention (days)
|
||||||
readonly MOLE_CRASH_REPORT_AGE_DAYS=7 # Crash report retention
|
readonly MOLE_CRASH_REPORT_AGE_DAYS=7 # Crash report retention (days)
|
||||||
readonly MOLE_SAVED_STATE_AGE_DAYS=7 # App saved state retention
|
readonly MOLE_SAVED_STATE_AGE_DAYS=7 # Saved state retention (days)
|
||||||
readonly MOLE_TM_BACKUP_SAFE_HOURS=48 # Time Machine failed backup safety window
|
readonly MOLE_TM_BACKUP_SAFE_HOURS=48 # TM backup safety window (hours)
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Seasonal Functions
|
# Seasonal Functions
|
||||||
@@ -99,7 +99,7 @@ declare -a DEFAULT_OPTIMIZE_WHITELIST_PATTERNS=(
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
readonly STAT_BSD="/usr/bin/stat"
|
readonly STAT_BSD="/usr/bin/stat"
|
||||||
|
|
||||||
# Get file size in bytes using BSD stat
|
# Get file size in bytes
|
||||||
get_file_size() {
|
get_file_size() {
|
||||||
local file="$1"
|
local file="$1"
|
||||||
local result
|
local result
|
||||||
@@ -107,8 +107,7 @@ get_file_size() {
|
|||||||
echo "${result:-0}"
|
echo "${result:-0}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get file modification time using BSD stat
|
# Get file modification time in epoch seconds
|
||||||
# Returns: epoch seconds
|
|
||||||
get_file_mtime() {
|
get_file_mtime() {
|
||||||
local file="$1"
|
local file="$1"
|
||||||
[[ -z "$file" ]] && {
|
[[ -z "$file" ]] && {
|
||||||
@@ -120,7 +119,7 @@ get_file_mtime() {
|
|||||||
echo "${result:-0}"
|
echo "${result:-0}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get file owner username using BSD stat
|
# Get file owner username
|
||||||
get_file_owner() {
|
get_file_owner() {
|
||||||
local file="$1"
|
local file="$1"
|
||||||
$STAT_BSD -f%Su "$file" 2> /dev/null || echo ""
|
$STAT_BSD -f%Su "$file" 2> /dev/null || echo ""
|
||||||
@@ -147,8 +146,7 @@ is_sip_enabled() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if running in interactive terminal
|
# Check if running in an interactive terminal
|
||||||
# Returns: 0 if interactive, 1 otherwise
|
|
||||||
is_interactive() {
|
is_interactive() {
|
||||||
[[ -t 1 ]]
|
[[ -t 1 ]]
|
||||||
}
|
}
|
||||||
@@ -169,9 +167,7 @@ get_free_space() {
|
|||||||
command df -h / | awk 'NR==2 {print $4}'
|
command df -h / | awk 'NR==2 {print $4}'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get optimal number of parallel jobs for a given operation type
|
# Get optimal parallel jobs for operation type (scan|io|compute|default)
|
||||||
# Args: $1 - operation type (scan|io|compute|default)
|
|
||||||
# Returns: number of jobs
|
|
||||||
get_optimal_parallel_jobs() {
|
get_optimal_parallel_jobs() {
|
||||||
local operation_type="${1:-default}"
|
local operation_type="${1:-default}"
|
||||||
local cpu_cores
|
local cpu_cores
|
||||||
@@ -193,9 +189,7 @@ get_optimal_parallel_jobs() {
|
|||||||
# Formatting Utilities
|
# Formatting Utilities
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# Convert bytes to human-readable format
|
# Convert bytes to human-readable format (e.g., 1.5GB)
|
||||||
# Args: $1 - size in bytes
|
|
||||||
# Returns: formatted string (e.g., "1.50GB", "256MB", "4KB")
|
|
||||||
bytes_to_human() {
|
bytes_to_human() {
|
||||||
local bytes="$1"
|
local bytes="$1"
|
||||||
if [[ ! "$bytes" =~ ^[0-9]+$ ]]; then
|
if [[ ! "$bytes" =~ ^[0-9]+$ ]]; then
|
||||||
@@ -245,9 +239,7 @@ bytes_to_human_kb() {
|
|||||||
bytes_to_human "$((${1:-0} * 1024))"
|
bytes_to_human "$((${1:-0} * 1024))"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get brand-friendly name for an application
|
# Get brand-friendly localized name for an application
|
||||||
# Args: $1 - application name
|
|
||||||
# Returns: localized name based on system language preference
|
|
||||||
get_brand_name() {
|
get_brand_name() {
|
||||||
local name="$1"
|
local name="$1"
|
||||||
|
|
||||||
@@ -305,7 +297,6 @@ declare -a MOLE_TEMP_FILES=()
|
|||||||
declare -a MOLE_TEMP_DIRS=()
|
declare -a MOLE_TEMP_DIRS=()
|
||||||
|
|
||||||
# Create tracked temporary file
|
# Create tracked temporary file
|
||||||
# Returns: temp file path
|
|
||||||
create_temp_file() {
|
create_temp_file() {
|
||||||
local temp
|
local temp
|
||||||
temp=$(mktemp) || return 1
|
temp=$(mktemp) || return 1
|
||||||
@@ -314,7 +305,6 @@ create_temp_file() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Create tracked temporary directory
|
# Create tracked temporary directory
|
||||||
# Returns: temp directory path
|
|
||||||
create_temp_dir() {
|
create_temp_dir() {
|
||||||
local temp
|
local temp
|
||||||
temp=$(mktemp -d) || return 1
|
temp=$(mktemp -d) || return 1
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ readonly MOLE_COMMON_LOADED=1
|
|||||||
|
|
||||||
_MOLE_CORE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
_MOLE_CORE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
# Load core modules in dependency order
|
# Load core modules
|
||||||
source "$_MOLE_CORE_DIR/base.sh"
|
source "$_MOLE_CORE_DIR/base.sh"
|
||||||
source "$_MOLE_CORE_DIR/log.sh"
|
source "$_MOLE_CORE_DIR/log.sh"
|
||||||
|
|
||||||
@@ -26,8 +26,7 @@ if [[ -f "$_MOLE_CORE_DIR/sudo.sh" ]]; then
|
|||||||
source "$_MOLE_CORE_DIR/sudo.sh"
|
source "$_MOLE_CORE_DIR/sudo.sh"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Update Mole via Homebrew
|
# Update via Homebrew
|
||||||
# Args: $1 = current version
|
|
||||||
update_via_homebrew() {
|
update_via_homebrew() {
|
||||||
local current_version="$1"
|
local current_version="$1"
|
||||||
|
|
||||||
@@ -75,8 +74,7 @@ update_via_homebrew() {
|
|||||||
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 apps from Dock
|
# Remove applications from Dock
|
||||||
# Args: app paths to remove
|
|
||||||
remove_apps_from_dock() {
|
remove_apps_from_dock() {
|
||||||
if [[ $# -eq 0 ]]; then
|
if [[ $# -eq 0 ]]; then
|
||||||
return 0
|
return 0
|
||||||
@@ -89,7 +87,7 @@ remove_apps_from_dock() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Execute Python helper to prune dock entries for the given app paths
|
# Prune dock entries using Python helper
|
||||||
python3 - "$@" << 'PY' 2> /dev/null || return 0
|
python3 - "$@" << 'PY' 2> /dev/null || return 0
|
||||||
import os
|
import os
|
||||||
import plistlib
|
import plistlib
|
||||||
|
|||||||
@@ -29,11 +29,7 @@ fi
|
|||||||
# Path Validation
|
# Path Validation
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# Validate path for deletion operations
|
# Validate path for deletion (absolute, no traversal, not system dir)
|
||||||
# Checks: non-empty, absolute, no traversal, no control chars, not system dir
|
|
||||||
#
|
|
||||||
# Args: $1 - path to validate
|
|
||||||
# Returns: 0 if safe, 1 if unsafe
|
|
||||||
validate_path_for_deletion() {
|
validate_path_for_deletion() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
|
|
||||||
@@ -76,13 +72,7 @@ validate_path_for_deletion() {
|
|||||||
# Safe Removal Operations
|
# Safe Removal Operations
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# Safe wrapper around rm -rf with path validation
|
# Safe wrapper around rm -rf with validation
|
||||||
#
|
|
||||||
# Args:
|
|
||||||
# $1 - path to remove
|
|
||||||
# $2 - silent mode (optional, default: false)
|
|
||||||
#
|
|
||||||
# Returns: 0 on success, 1 on failure
|
|
||||||
safe_remove() {
|
safe_remove() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
local silent="${2:-false}"
|
local silent="${2:-false}"
|
||||||
@@ -108,10 +98,7 @@ safe_remove() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Safe sudo remove with additional symlink protection
|
# Safe sudo removal with symlink protection
|
||||||
#
|
|
||||||
# Args: $1 - path to remove
|
|
||||||
# Returns: 0 on success, 1 on failure
|
|
||||||
safe_sudo_remove() {
|
safe_sudo_remove() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
|
|
||||||
@@ -147,15 +134,7 @@ safe_sudo_remove() {
|
|||||||
# Safe Find and Delete Operations
|
# Safe Find and Delete Operations
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# Safe find delete with depth limit and validation
|
# Safe file discovery and deletion with depth and age limits
|
||||||
#
|
|
||||||
# Args:
|
|
||||||
# $1 - base directory
|
|
||||||
# $2 - file pattern (e.g., "*.log")
|
|
||||||
# $3 - age in days (0 = all files, default: 7)
|
|
||||||
# $4 - type filter ("f" or "d", default: "f")
|
|
||||||
#
|
|
||||||
# Returns: 0 on success, 1 on failure
|
|
||||||
safe_find_delete() {
|
safe_find_delete() {
|
||||||
local base_dir="$1"
|
local base_dir="$1"
|
||||||
local pattern="$2"
|
local pattern="$2"
|
||||||
@@ -202,10 +181,7 @@ safe_find_delete() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Safe sudo find delete (same as safe_find_delete but with sudo)
|
# Safe sudo discovery and deletion
|
||||||
#
|
|
||||||
# Args: same as safe_find_delete
|
|
||||||
# Returns: 0 on success, 1 on failure
|
|
||||||
safe_sudo_find_delete() {
|
safe_sudo_find_delete() {
|
||||||
local base_dir="$1"
|
local base_dir="$1"
|
||||||
local pattern="$2"
|
local pattern="$2"
|
||||||
@@ -254,11 +230,7 @@ safe_sudo_find_delete() {
|
|||||||
# Size Calculation
|
# Size Calculation
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# Get path size in kilobytes
|
# Get path size in KB (returns 0 if not found)
|
||||||
# Uses timeout protection to prevent du from hanging on large directories
|
|
||||||
#
|
|
||||||
# Args: $1 - path
|
|
||||||
# Returns: size in KB (0 if path doesn't exist)
|
|
||||||
get_path_size_kb() {
|
get_path_size_kb() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
[[ -z "$path" || ! -e "$path" ]] && {
|
[[ -z "$path" || ! -e "$path" ]] && {
|
||||||
@@ -271,10 +243,7 @@ get_path_size_kb() {
|
|||||||
echo "${size:-0}"
|
echo "${size:-0}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Calculate total size of multiple paths
|
# Calculate total size for multiple paths
|
||||||
#
|
|
||||||
# Args: $1 - newline-separated list of paths
|
|
||||||
# Returns: total size in KB
|
|
||||||
calculate_total_size() {
|
calculate_total_size() {
|
||||||
local files="$1"
|
local files="$1"
|
||||||
local total_kb=0
|
local total_kb=0
|
||||||
|
|||||||
@@ -32,8 +32,7 @@ mkdir -p "$(dirname "$LOG_FILE")" 2> /dev/null || true
|
|||||||
# Log Rotation
|
# Log Rotation
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# Rotate log file if it exceeds max size
|
# Rotate log file if it exceeds maximum size
|
||||||
# Called once at module load, not per log entry
|
|
||||||
rotate_log_once() {
|
rotate_log_once() {
|
||||||
# Skip if already checked this session
|
# Skip if already checked this session
|
||||||
[[ -n "${MOLE_LOG_ROTATED:-}" ]] && return 0
|
[[ -n "${MOLE_LOG_ROTATED:-}" ]] && return 0
|
||||||
@@ -51,7 +50,6 @@ rotate_log_once() {
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# Log informational message
|
# Log informational message
|
||||||
# Args: $1 - message
|
|
||||||
log_info() {
|
log_info() {
|
||||||
echo -e "${BLUE}$1${NC}"
|
echo -e "${BLUE}$1${NC}"
|
||||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
@@ -62,7 +60,6 @@ log_info() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Log success message
|
# Log success message
|
||||||
# Args: $1 - message
|
|
||||||
log_success() {
|
log_success() {
|
||||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $1"
|
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $1"
|
||||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
@@ -73,7 +70,6 @@ log_success() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Log warning message
|
# Log warning message
|
||||||
# Args: $1 - message
|
|
||||||
log_warning() {
|
log_warning() {
|
||||||
echo -e "${YELLOW}$1${NC}"
|
echo -e "${YELLOW}$1${NC}"
|
||||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
@@ -84,7 +80,6 @@ log_warning() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Log error message
|
# Log error message
|
||||||
# Args: $1 - message
|
|
||||||
log_error() {
|
log_error() {
|
||||||
echo -e "${RED}${ICON_ERROR}${NC} $1" >&2
|
echo -e "${RED}${ICON_ERROR}${NC} $1" >&2
|
||||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
@@ -94,8 +89,7 @@ log_error() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Debug logging - only shown when MO_DEBUG=1
|
# Debug logging (active when MO_DEBUG=1)
|
||||||
# Args: $@ - debug message components
|
|
||||||
debug_log() {
|
debug_log() {
|
||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
echo -e "${GRAY}[DEBUG]${NC} $*" >&2
|
echo -e "${GRAY}[DEBUG]${NC} $*" >&2
|
||||||
@@ -143,15 +137,12 @@ log_system_info() {
|
|||||||
# Command Execution Wrappers
|
# Command Execution Wrappers
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# Run command silently, ignore errors
|
# Run command silently (ignore errors)
|
||||||
# Args: $@ - command and arguments
|
|
||||||
run_silent() {
|
run_silent() {
|
||||||
"$@" > /dev/null 2>&1 || true
|
"$@" > /dev/null 2>&1 || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Run command with error logging
|
# Run command with error logging
|
||||||
# Args: $@ - command and arguments
|
|
||||||
# Returns: command exit code
|
|
||||||
run_logged() {
|
run_logged() {
|
||||||
local cmd="$1"
|
local cmd="$1"
|
||||||
# Log to main file, and also to debug file if enabled
|
# Log to main file, and also to debug file if enabled
|
||||||
@@ -173,8 +164,7 @@ run_logged() {
|
|||||||
# Formatted Output
|
# Formatted Output
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# Print formatted summary block with heading and details
|
# Print formatted summary block
|
||||||
# Args: $1=status (ignored), $2=heading, $@=details
|
|
||||||
print_summary_block() {
|
print_summary_block() {
|
||||||
local heading=""
|
local heading=""
|
||||||
local -a details=()
|
local -a details=()
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ check_touchid_support() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Detect clamshell mode (lid closed)
|
||||||
is_clamshell_mode() {
|
is_clamshell_mode() {
|
||||||
# ioreg is missing (not macOS) -> treat as lid open
|
# ioreg is missing (not macOS) -> treat as lid open
|
||||||
if ! command -v ioreg > /dev/null 2>&1; then
|
if ! command -v ioreg > /dev/null 2>&1; then
|
||||||
@@ -182,8 +183,7 @@ request_sudo_access() {
|
|||||||
MOLE_SUDO_KEEPALIVE_PID=""
|
MOLE_SUDO_KEEPALIVE_PID=""
|
||||||
MOLE_SUDO_ESTABLISHED="false"
|
MOLE_SUDO_ESTABLISHED="false"
|
||||||
|
|
||||||
# Start sudo keepalive background process
|
# Start sudo keepalive
|
||||||
# Returns: PID of keepalive process
|
|
||||||
_start_sudo_keepalive() {
|
_start_sudo_keepalive() {
|
||||||
# Start background keepalive process with all outputs redirected
|
# Start background keepalive process with all outputs redirected
|
||||||
# This is critical: command substitution waits for all file descriptors to close
|
# This is critical: command substitution waits for all file descriptors to close
|
||||||
@@ -212,8 +212,7 @@ _start_sudo_keepalive() {
|
|||||||
echo $pid
|
echo $pid
|
||||||
}
|
}
|
||||||
|
|
||||||
# Stop sudo keepalive process
|
# Stop sudo keepalive
|
||||||
# Args: $1 - PID of keepalive process
|
|
||||||
_stop_sudo_keepalive() {
|
_stop_sudo_keepalive() {
|
||||||
local pid="${1:-}"
|
local pid="${1:-}"
|
||||||
if [[ -n "$pid" ]]; then
|
if [[ -n "$pid" ]]; then
|
||||||
@@ -227,8 +226,7 @@ has_sudo_session() {
|
|||||||
sudo -n true 2> /dev/null
|
sudo -n true 2> /dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
# Request sudo access (wrapper for common.sh function)
|
# Request administrative access
|
||||||
# Args: $1 - prompt message
|
|
||||||
request_sudo() {
|
request_sudo() {
|
||||||
local prompt_msg="${1:-Admin access required}"
|
local prompt_msg="${1:-Admin access required}"
|
||||||
|
|
||||||
@@ -244,8 +242,7 @@ request_sudo() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Ensure sudo session is established with keepalive
|
# Maintain active sudo session with keepalive
|
||||||
# Args: $1 - prompt message
|
|
||||||
ensure_sudo_session() {
|
ensure_sudo_session() {
|
||||||
local prompt="${1:-Admin access required}"
|
local prompt="${1:-Admin access required}"
|
||||||
|
|
||||||
@@ -287,8 +284,7 @@ register_sudo_cleanup() {
|
|||||||
trap stop_sudo_session EXIT INT TERM
|
trap stop_sudo_session EXIT INT TERM
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if sudo is likely needed for given operations
|
# Predict if operation requires administrative access
|
||||||
# Args: $@ - list of operations to check
|
|
||||||
will_need_sudo() {
|
will_need_sudo() {
|
||||||
local -a operations=("$@")
|
local -a operations=("$@")
|
||||||
for op in "${operations[@]}"; do
|
for op in "${operations[@]}"; do
|
||||||
|
|||||||
@@ -17,10 +17,7 @@ clear_screen() { printf '\033[2J\033[H'; }
|
|||||||
hide_cursor() { [[ -t 1 ]] && printf '\033[?25l' >&2 || true; }
|
hide_cursor() { [[ -t 1 ]] && printf '\033[?25l' >&2 || true; }
|
||||||
show_cursor() { [[ -t 1 ]] && printf '\033[?25h' >&2 || true; }
|
show_cursor() { [[ -t 1 ]] && printf '\033[?25h' >&2 || true; }
|
||||||
|
|
||||||
# Calculate display width of a string (CJK characters count as 2)
|
# Calculate display width (CJK characters count as 2)
|
||||||
# Args: $1 - string to measure
|
|
||||||
# Returns: display width
|
|
||||||
# Note: Works correctly even when LC_ALL=C is set
|
|
||||||
get_display_width() {
|
get_display_width() {
|
||||||
local str="$1"
|
local str="$1"
|
||||||
|
|
||||||
@@ -66,8 +63,7 @@ get_display_width() {
|
|||||||
echo "$width"
|
echo "$width"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Truncate string by display width (handles CJK correctly)
|
# Truncate string by display width (handles CJK)
|
||||||
# Args: $1 - string, $2 - max display width
|
|
||||||
truncate_by_display_width() {
|
truncate_by_display_width() {
|
||||||
local str="$1"
|
local str="$1"
|
||||||
local max_width="$2"
|
local max_width="$2"
|
||||||
@@ -140,7 +136,7 @@ truncate_by_display_width() {
|
|||||||
echo "${truncated}..."
|
echo "${truncated}..."
|
||||||
}
|
}
|
||||||
|
|
||||||
# Keyboard input - read single keypress
|
# Read single keyboard input
|
||||||
read_key() {
|
read_key() {
|
||||||
local key rest read_status
|
local key rest read_status
|
||||||
IFS= read -r -s -n 1 key
|
IFS= read -r -s -n 1 key
|
||||||
@@ -222,7 +218,7 @@ drain_pending_input() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Menu display
|
# Format menu option display
|
||||||
show_menu_option() {
|
show_menu_option() {
|
||||||
local number="$1"
|
local number="$1"
|
||||||
local text="$2"
|
local text="$2"
|
||||||
@@ -235,7 +231,7 @@ show_menu_option() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Inline spinner
|
# Background spinner implementation
|
||||||
INLINE_SPINNER_PID=""
|
INLINE_SPINNER_PID=""
|
||||||
start_inline_spinner() {
|
start_inline_spinner() {
|
||||||
stop_inline_spinner 2> /dev/null || true
|
stop_inline_spinner 2> /dev/null || true
|
||||||
@@ -281,7 +277,7 @@ stop_inline_spinner() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Wrapper for running commands with spinner
|
# Run command with a terminal spinner
|
||||||
with_spinner() {
|
with_spinner() {
|
||||||
local msg="$1"
|
local msg="$1"
|
||||||
shift || true
|
shift || true
|
||||||
@@ -302,9 +298,7 @@ mo_spinner_chars() {
|
|||||||
printf "%s" "$chars"
|
printf "%s" "$chars"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Format last used time for display
|
# Format relative time for compact display (e.g., 3d ago)
|
||||||
# Args: $1 = last used string (e.g., "3 days ago", "Today", "Never")
|
|
||||||
# Returns: Compact version (e.g., "3d ago", "Today", "Never")
|
|
||||||
format_last_used_summary() {
|
format_last_used_summary() {
|
||||||
local value="$1"
|
local value="$1"
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ set -euo pipefail
|
|||||||
# Find and remove corrupted .plist files
|
# Find and remove corrupted .plist files
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# Clean broken preference files
|
# Clean corrupted preference files
|
||||||
# Uses plutil -lint to validate plist files
|
|
||||||
# Returns: count of broken files fixed
|
|
||||||
fix_broken_preferences() {
|
fix_broken_preferences() {
|
||||||
local prefs_dir="$HOME/Library/Preferences"
|
local prefs_dir="$HOME/Library/Preferences"
|
||||||
[[ -d "$prefs_dir" ]] || return 0
|
[[ -d "$prefs_dir" ]] || return 0
|
||||||
@@ -68,8 +66,7 @@ fix_broken_preferences() {
|
|||||||
# Find and remove login items pointing to non-existent files
|
# Find and remove login items pointing to non-existent files
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# Clean broken login items (LaunchAgents pointing to missing executables)
|
# Clean login items with missing executables
|
||||||
# Returns: count of broken items fixed
|
|
||||||
fix_broken_login_items() {
|
fix_broken_login_items() {
|
||||||
local launch_agents_dir="$HOME/Library/LaunchAgents"
|
local launch_agents_dir="$HOME/Library/LaunchAgents"
|
||||||
[[ -d "$launch_agents_dir" ]] || return 0
|
[[ -d "$launch_agents_dir" ]] || return 0
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ flush_dns_cache() {
|
|||||||
sudo dscacheutil -flushcache 2> /dev/null && sudo killall -HUP mDNSResponder 2> /dev/null
|
sudo dscacheutil -flushcache 2> /dev/null && sudo killall -HUP mDNSResponder 2> /dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
# System maintenance: rebuild databases and flush caches
|
# Rebuild databases and flush caches
|
||||||
opt_system_maintenance() {
|
opt_system_maintenance() {
|
||||||
echo -e "${BLUE}${ICON_ARROW}${NC} Rebuilding LaunchServices database..."
|
echo -e "${BLUE}${ICON_ARROW}${NC} Rebuilding LaunchServices database..."
|
||||||
run_with_timeout 10 /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill -r -domain local -domain system -domain user > /dev/null 2>&1 || true
|
run_with_timeout 10 /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill -r -domain local -domain system -domain user > /dev/null 2>&1 || true
|
||||||
@@ -39,7 +39,7 @@ opt_system_maintenance() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cache refresh: update Finder/Safari caches
|
# Reset Finder and Safari caches
|
||||||
opt_cache_refresh() {
|
opt_cache_refresh() {
|
||||||
echo -e "${BLUE}${ICON_ARROW}${NC} Resetting Quick Look cache..."
|
echo -e "${BLUE}${ICON_ARROW}${NC} Resetting Quick Look cache..."
|
||||||
qlmanage -r cache > /dev/null 2>&1 || true
|
qlmanage -r cache > /dev/null 2>&1 || true
|
||||||
@@ -61,7 +61,7 @@ opt_cache_refresh() {
|
|||||||
echo -e "${GREEN}${ICON_SUCCESS}${NC} Finder and Safari caches updated"
|
echo -e "${GREEN}${ICON_SUCCESS}${NC} Finder and Safari caches updated"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Maintenance scripts: run periodic tasks
|
# Run periodic maintenance scripts
|
||||||
opt_maintenance_scripts() {
|
opt_maintenance_scripts() {
|
||||||
# Run newsyslog to rotate system logs
|
# Run newsyslog to rotate system logs
|
||||||
echo -e "${BLUE}${ICON_ARROW}${NC} Rotating system logs..."
|
echo -e "${BLUE}${ICON_ARROW}${NC} Rotating system logs..."
|
||||||
@@ -72,7 +72,7 @@ opt_maintenance_scripts() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Log cleanup: remove diagnostic and crash logs
|
# Remove diagnostic and crash logs
|
||||||
opt_log_cleanup() {
|
opt_log_cleanup() {
|
||||||
echo -e "${BLUE}${ICON_ARROW}${NC} Clearing diagnostic & crash logs..."
|
echo -e "${BLUE}${ICON_ARROW}${NC} Clearing diagnostic & crash logs..."
|
||||||
local -a user_logs=(
|
local -a user_logs=(
|
||||||
@@ -92,7 +92,7 @@ opt_log_cleanup() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Recent items: clear recent file lists
|
# Clear recent file lists
|
||||||
opt_recent_items() {
|
opt_recent_items() {
|
||||||
echo -e "${BLUE}${ICON_ARROW}${NC} Clearing recent items lists..."
|
echo -e "${BLUE}${ICON_ARROW}${NC} Clearing recent items lists..."
|
||||||
local shared_dir="$HOME/Library/Application Support/com.apple.sharedfilelist"
|
local shared_dir="$HOME/Library/Application Support/com.apple.sharedfilelist"
|
||||||
|
|||||||
11
mole
11
mole
@@ -25,7 +25,7 @@ source "$SCRIPT_DIR/lib/core/common.sh"
|
|||||||
VERSION="1.13.10"
|
VERSION="1.13.10"
|
||||||
MOLE_TAGLINE="Deep clean and optimize your Mac."
|
MOLE_TAGLINE="Deep clean and optimize your Mac."
|
||||||
|
|
||||||
# Check if Touch ID is already configured
|
# Check TouchID configuration
|
||||||
is_touchid_configured() {
|
is_touchid_configured() {
|
||||||
local pam_sudo_file="/etc/pam.d/sudo"
|
local pam_sudo_file="/etc/pam.d/sudo"
|
||||||
[[ -f "$pam_sudo_file" ]] && grep -q "pam_tid.so" "$pam_sudo_file" 2> /dev/null
|
[[ -f "$pam_sudo_file" ]] && grep -q "pam_tid.so" "$pam_sudo_file" 2> /dev/null
|
||||||
@@ -38,8 +38,7 @@ get_latest_version() {
|
|||||||
grep '^VERSION=' | head -1 | sed 's/VERSION="\(.*\)"/\1/'
|
grep '^VERSION=' | head -1 | sed 's/VERSION="\(.*\)"/\1/'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get latest version from GitHub API
|
# Get latest version from GitHub API (works for both Homebrew and manual installations)
|
||||||
# This works for both Homebrew and manual installs since versions are synced
|
|
||||||
get_latest_version_from_github() {
|
get_latest_version_from_github() {
|
||||||
local version
|
local version
|
||||||
version=$(curl -fsSL --connect-timeout 2 --max-time 3 \
|
version=$(curl -fsSL --connect-timeout 2 --max-time 3 \
|
||||||
@@ -61,7 +60,7 @@ check_for_updates() {
|
|||||||
local msg_cache="$HOME/.cache/mole/update_message"
|
local msg_cache="$HOME/.cache/mole/update_message"
|
||||||
mkdir -p "$(dirname "$msg_cache")" 2> /dev/null
|
mkdir -p "$(dirname "$msg_cache")" 2> /dev/null
|
||||||
|
|
||||||
# Background version check (save to file, don't output)
|
# Background version check
|
||||||
# Always check in background, display result from previous check
|
# Always check in background, display result from previous check
|
||||||
(
|
(
|
||||||
local latest
|
local latest
|
||||||
@@ -104,7 +103,7 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
animate_mole_intro() {
|
animate_mole_intro() {
|
||||||
# Skip animation if stdout isn't a TTY (non-interactive)
|
# Non-interactive: skip animation
|
||||||
if [[ ! -t 1 ]]; then
|
if [[ ! -t 1 ]]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
@@ -481,7 +480,7 @@ remove_mole() {
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Show what will be removed
|
# List items for removal
|
||||||
echo -e "${YELLOW}Remove Mole${NC} - will delete the following:"
|
echo -e "${YELLOW}Remove Mole${NC} - will delete the following:"
|
||||||
if [[ "$is_homebrew" == "true" ]]; then
|
if [[ "$is_homebrew" == "true" ]]; then
|
||||||
echo " - Mole via Homebrew"
|
echo " - Mole via Homebrew"
|
||||||
|
|||||||
Reference in New Issue
Block a user