mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 12:41:46 +00:00
When 'mole remove' is used and Mole was installed via Homebrew, the script now provides more explicit feedback if the 'brew uninstall --force mole' command fails. Previously, errors were silently ignored. This change ensures that if Homebrew uninstallation encounters an issue, the user is informed with the error output and instructed on how to manually complete the uninstallation, preventing inconsistencies where Homebrew still believes Mole is installed. Additionally, a minor improvement to config_dir resolution in update_mole was included for robustness.
888 lines
28 KiB
Bash
Executable File
888 lines
28 KiB
Bash
Executable File
#!/bin/bash
|
|
# Mole - Main Entry Point
|
|
# A comprehensive macOS maintenance tool
|
|
#
|
|
# Clean - Remove junk files and optimize system
|
|
# Uninstall - Remove applications completely
|
|
# Analyze - Interactive disk space explorer
|
|
#
|
|
# Usage:
|
|
# ./mole # Interactive main menu
|
|
# ./mole clean # Direct clean mode
|
|
# ./mole uninstall # Direct uninstall mode
|
|
# ./mole analyze # Disk space explorer
|
|
# ./mole --help # Show help
|
|
|
|
set -euo pipefail
|
|
|
|
# Get script directory
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
# Source common functions
|
|
source "$SCRIPT_DIR/lib/core/common.sh"
|
|
|
|
# Set up cleanup trap for temporary files
|
|
trap cleanup_temp_files EXIT INT TERM
|
|
|
|
# Version info
|
|
VERSION="1.17.0"
|
|
MOLE_TAGLINE="Deep clean and optimize your Mac."
|
|
|
|
# Check TouchID configuration
|
|
is_touchid_configured() {
|
|
local pam_sudo_file="/etc/pam.d/sudo"
|
|
[[ -f "$pam_sudo_file" ]] && grep -q "pam_tid.so" "$pam_sudo_file" 2> /dev/null
|
|
}
|
|
|
|
# Get latest version from remote repository
|
|
get_latest_version() {
|
|
curl -fsSL --connect-timeout 2 --max-time 3 -H "Cache-Control: no-cache" \
|
|
"https://raw.githubusercontent.com/tw93/mole/main/mole" 2> /dev/null |
|
|
grep '^VERSION=' | head -1 | sed 's/VERSION="\(.*\)"/\1/'
|
|
}
|
|
|
|
# Get latest version from GitHub API (works for both Homebrew and manual installations)
|
|
get_latest_version_from_github() {
|
|
local version
|
|
version=$(curl -fsSL --connect-timeout 2 --max-time 3 \
|
|
"https://api.github.com/repos/tw93/mole/releases/latest" 2> /dev/null |
|
|
grep '"tag_name"' | head -1 | sed -E 's/.*"([^"]+)".*/\1/')
|
|
# Remove 'v' or 'V' prefix if present
|
|
version="${version#v}"
|
|
version="${version#V}"
|
|
echo "$version"
|
|
}
|
|
|
|
# Check if installed via Homebrew
|
|
is_homebrew_install() {
|
|
# Fast path: check if mole binary is a Homebrew symlink
|
|
local mole_path
|
|
mole_path=$(command -v mole 2> /dev/null) || return 1
|
|
|
|
# Check if mole is a symlink pointing to Homebrew Cellar
|
|
if [[ -L "$mole_path" ]] && readlink "$mole_path" | grep -q "Cellar/mole"; then
|
|
# Symlink looks good, but verify brew actually manages it
|
|
if command -v brew > /dev/null 2>&1; then
|
|
# Use fast brew list check
|
|
brew list --formula 2> /dev/null | grep -q "^mole$" && return 0
|
|
else
|
|
# brew not available - cannot update/remove via Homebrew
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# Fallback: check common Homebrew paths and verify with Cellar
|
|
if [[ -f "$mole_path" ]]; then
|
|
case "$mole_path" in
|
|
/opt/homebrew/bin/mole | /usr/local/bin/mole)
|
|
# Verify Cellar directory exists
|
|
if [[ -d /opt/homebrew/Cellar/mole ]] || [[ -d /usr/local/Cellar/mole ]]; then
|
|
# Double-check with brew if available
|
|
if command -v brew > /dev/null 2>&1; then
|
|
brew list --formula 2> /dev/null | grep -q "^mole$" && return 0
|
|
else
|
|
return 0 # Cellar exists, probably Homebrew install
|
|
fi
|
|
fi
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# Last resort: check custom Homebrew prefix
|
|
if command -v brew > /dev/null 2>&1; then
|
|
local brew_prefix
|
|
brew_prefix=$(brew --prefix 2> /dev/null)
|
|
if [[ -n "$brew_prefix" && "$mole_path" == "$brew_prefix/bin/mole" && -d "$brew_prefix/Cellar/mole" ]]; then
|
|
brew list --formula 2> /dev/null | grep -q "^mole$" && return 0
|
|
fi
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# Check for updates (non-blocking, always check in background)
|
|
check_for_updates() {
|
|
local msg_cache="$HOME/.cache/mole/update_message"
|
|
ensure_user_dir "$(dirname "$msg_cache")"
|
|
ensure_user_file "$msg_cache"
|
|
|
|
# Background version check
|
|
# Always check in background, display result from previous check
|
|
(
|
|
local latest
|
|
|
|
# Use GitHub API for version check (works for both Homebrew and manual installs)
|
|
# Try API first (faster and more reliable)
|
|
latest=$(get_latest_version_from_github)
|
|
if [[ -z "$latest" ]]; then
|
|
# Fallback to parsing mole script from raw GitHub
|
|
latest=$(get_latest_version)
|
|
fi
|
|
|
|
if [[ -n "$latest" && "$VERSION" != "$latest" && "$(printf '%s\n' "$VERSION" "$latest" | sort -V | head -1)" == "$VERSION" ]]; then
|
|
printf "\nUpdate available: %s → %s, run %smo update%s\n\n" "$VERSION" "$latest" "$GREEN" "$NC" > "$msg_cache"
|
|
else
|
|
echo -n > "$msg_cache"
|
|
fi
|
|
) &
|
|
disown 2> /dev/null || true
|
|
}
|
|
|
|
# Show update notification if available
|
|
show_update_notification() {
|
|
local msg_cache="$HOME/.cache/mole/update_message"
|
|
if [[ -f "$msg_cache" && -s "$msg_cache" ]]; then
|
|
cat "$msg_cache"
|
|
echo
|
|
fi
|
|
}
|
|
|
|
show_brand_banner() {
|
|
cat << EOF
|
|
${GREEN} __ __ _ ${NC}
|
|
${GREEN}| \/ | ___ | | ___ ${NC}
|
|
${GREEN}| |\/| |/ _ \| |/ _ \\${NC}
|
|
${GREEN}| | | | (_) | | __/${NC} ${BLUE}https://github.com/tw93/mole${NC}
|
|
${GREEN}|_| |_|\___/|_|\___|${NC} ${GREEN}${MOLE_TAGLINE}${NC}
|
|
|
|
EOF
|
|
}
|
|
|
|
animate_mole_intro() {
|
|
# Non-interactive: skip animation
|
|
if [[ ! -t 1 ]]; then
|
|
return
|
|
fi
|
|
|
|
clear_screen
|
|
printf '\n'
|
|
hide_cursor
|
|
|
|
local -a mole_lines=()
|
|
|
|
if is_christmas_season; then
|
|
while IFS= read -r line; do
|
|
mole_lines+=("$line")
|
|
done << 'EOF'
|
|
*
|
|
/o\
|
|
{/\_/\}
|
|
____/ o o \
|
|
/~____ =o= /
|
|
(______)__m_m)
|
|
/ \
|
|
__/ /\ \__
|
|
/__/ \__\_
|
|
EOF
|
|
else
|
|
while IFS= read -r line; do
|
|
mole_lines+=("$line")
|
|
done << 'EOF'
|
|
/\_/\
|
|
____/ o o \
|
|
/~____ =o= /
|
|
(______)__m_m)
|
|
/ \
|
|
__/ /\ \__
|
|
/__/ \__\_
|
|
EOF
|
|
fi
|
|
|
|
local idx
|
|
local hat_color="${RED}"
|
|
local body_cutoff
|
|
local body_color="${PURPLE}"
|
|
local ground_color="${GREEN}"
|
|
|
|
if is_christmas_season; then
|
|
body_cutoff=6
|
|
for idx in "${!mole_lines[@]}"; do
|
|
if ((idx < 3)); then
|
|
printf "%s\n" "${hat_color}${mole_lines[$idx]}${NC}"
|
|
elif ((idx < body_cutoff)); then
|
|
printf "%s\n" "${body_color}${mole_lines[$idx]}${NC}"
|
|
else
|
|
printf "%s\n" "${ground_color}${mole_lines[$idx]}${NC}"
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
else
|
|
body_cutoff=4
|
|
for idx in "${!mole_lines[@]}"; do
|
|
if ((idx < body_cutoff)); then
|
|
printf "%s\n" "${body_color}${mole_lines[$idx]}${NC}"
|
|
else
|
|
printf "%s\n" "${ground_color}${mole_lines[$idx]}${NC}"
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
fi
|
|
|
|
printf '\n'
|
|
sleep 0.5
|
|
|
|
printf '\033[2J\033[H'
|
|
show_cursor
|
|
}
|
|
|
|
show_version() {
|
|
local os_ver
|
|
if command -v sw_vers > /dev/null; then
|
|
os_ver=$(sw_vers -productVersion)
|
|
else
|
|
os_ver="Unknown"
|
|
fi
|
|
|
|
local arch
|
|
arch=$(uname -m)
|
|
|
|
local kernel
|
|
kernel=$(uname -r)
|
|
|
|
local sip_status
|
|
if command -v csrutil > /dev/null; then
|
|
sip_status=$(csrutil status 2> /dev/null | grep -o "enabled\|disabled" || echo "Unknown")
|
|
# Capitalize first letter
|
|
sip_status="$(tr '[:lower:]' '[:upper:]' <<< "${sip_status:0:1}")${sip_status:1}"
|
|
else
|
|
sip_status="Unknown"
|
|
fi
|
|
|
|
local disk_free
|
|
disk_free=$(df -h / 2> /dev/null | awk 'NR==2 {print $4}' || echo "Unknown")
|
|
|
|
local install_method="Manual"
|
|
if is_homebrew_install; then
|
|
install_method="Homebrew"
|
|
fi
|
|
|
|
printf '\nMole version %s\n' "$VERSION"
|
|
printf 'macOS: %s\n' "$os_ver"
|
|
printf 'Architecture: %s\n' "$arch"
|
|
printf 'Kernel: %s\n' "$kernel"
|
|
printf 'SIP: %s\n' "$sip_status"
|
|
printf 'Disk Free: %s\n' "$disk_free"
|
|
printf 'Install: %s\n' "$install_method"
|
|
printf 'Shell: %s\n\n' "${SHELL:-Unknown}"
|
|
}
|
|
|
|
show_help() {
|
|
show_brand_banner
|
|
echo
|
|
printf "%s%s%s\n" "$BLUE" "COMMANDS" "$NC"
|
|
printf " %s%-28s%s %s\n" "$GREEN" "mo" "$NC" "Main menu"
|
|
printf " %s%-28s%s %s\n" "$GREEN" "mo clean" "$NC" "Free up disk space"
|
|
printf " %s%-28s%s %s\n" "$GREEN" "mo uninstall" "$NC" "Remove apps completely"
|
|
printf " %s%-28s%s %s\n" "$GREEN" "mo optimize" "$NC" "Check and maintain system"
|
|
printf " %s%-28s%s %s\n" "$GREEN" "mo analyze" "$NC" "Explore disk usage"
|
|
printf " %s%-28s%s %s\n" "$GREEN" "mo status" "$NC" "Monitor system health"
|
|
printf " %s%-28s%s %s\n" "$GREEN" "mo purge" "$NC" "Remove old project artifacts"
|
|
printf " %s%-28s%s %s\n" "$GREEN" "mo touchid" "$NC" "Configure Touch ID for sudo"
|
|
printf " %s%-28s%s %s\n" "$GREEN" "mo update" "$NC" "Update to latest version"
|
|
printf " %s%-28s%s %s\n" "$GREEN" "mo remove" "$NC" "Remove Mole from system"
|
|
printf " %s%-28s%s %s\n" "$GREEN" "mo --help" "$NC" "Show help"
|
|
printf " %s%-28s%s %s\n" "$GREEN" "mo --version" "$NC" "Show version"
|
|
echo
|
|
printf " %s%-28s%s %s\n" "$GREEN" "mo clean --dry-run" "$NC" "Preview cleanup"
|
|
printf " %s%-28s%s %s\n" "$GREEN" "mo clean --whitelist" "$NC" "Manage protected caches"
|
|
|
|
printf " %s%-28s%s %s\n" "$GREEN" "mo optimize --dry-run" "$NC" "Preview optimization"
|
|
printf " %s%-28s%s %s\n" "$GREEN" "mo optimize --whitelist" "$NC" "Manage protected items"
|
|
printf " %s%-28s%s %s\n" "$GREEN" "mo purge --paths" "$NC" "Configure scan directories"
|
|
echo
|
|
printf "%s%s%s\n" "$BLUE" "OPTIONS" "$NC"
|
|
printf " %s%-28s%s %s\n" "$GREEN" "--debug" "$NC" "Show detailed operation logs"
|
|
echo
|
|
}
|
|
|
|
# Simple update function
|
|
update_mole() {
|
|
# Set up cleanup trap for update process
|
|
local update_interrupted=false
|
|
trap 'update_interrupted=true; echo ""; exit 130' INT TERM
|
|
|
|
# Check if installed via Homebrew
|
|
if is_homebrew_install; then
|
|
update_via_homebrew "$VERSION"
|
|
exit 0
|
|
fi
|
|
|
|
# Check for updates
|
|
local latest
|
|
latest=$(get_latest_version_from_github)
|
|
# Fallback to raw GitHub if API fails
|
|
[[ -z "$latest" ]] && latest=$(get_latest_version)
|
|
|
|
if [[ -z "$latest" ]]; then
|
|
log_error "Unable to check for updates. Check network connection."
|
|
echo -e "${YELLOW}Tip:${NC} Check if you can access GitHub (https://github.com)"
|
|
echo -e "${YELLOW}Tip:${NC} Try again with: ${GRAY}mo update${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$VERSION" == "$latest" ]]; then
|
|
echo ""
|
|
echo -e "${GREEN}${ICON_SUCCESS}${NC} Already on latest version (${VERSION})"
|
|
echo ""
|
|
exit 0
|
|
fi
|
|
|
|
# Download and run installer with progress
|
|
if [[ -t 1 ]]; then
|
|
start_inline_spinner "Downloading latest version..."
|
|
else
|
|
echo "Downloading latest version..."
|
|
fi
|
|
|
|
local installer_url="https://raw.githubusercontent.com/tw93/mole/main/install.sh"
|
|
local tmp_installer
|
|
tmp_installer="$(mktemp_file)" || {
|
|
log_error "Update failed"
|
|
exit 1
|
|
}
|
|
|
|
# Download installer with progress and better error handling
|
|
local download_error=""
|
|
if command -v curl > /dev/null 2>&1; then
|
|
download_error=$(curl -fsSL --connect-timeout 10 --max-time 60 "$installer_url" -o "$tmp_installer" 2>&1) || {
|
|
local curl_exit=$?
|
|
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
|
rm -f "$tmp_installer"
|
|
log_error "Update failed (curl error: $curl_exit)"
|
|
|
|
# Provide helpful error messages based on curl exit codes
|
|
case $curl_exit in
|
|
6) echo -e "${YELLOW}Tip:${NC} Could not resolve host. Check DNS or network connection." ;;
|
|
7) echo -e "${YELLOW}Tip:${NC} Failed to connect. Check network or proxy settings." ;;
|
|
22) echo -e "${YELLOW}Tip:${NC} HTTP 404 Not Found. The installer may have moved." ;;
|
|
28) echo -e "${YELLOW}Tip:${NC} Connection timed out. Try again or check firewall." ;;
|
|
*) echo -e "${YELLOW}Tip:${NC} Check network connection and try again." ;;
|
|
esac
|
|
echo -e "${YELLOW}Tip:${NC} URL: $installer_url"
|
|
exit 1
|
|
}
|
|
elif command -v wget > /dev/null 2>&1; then
|
|
download_error=$(wget --timeout=10 --tries=3 -qO "$tmp_installer" "$installer_url" 2>&1) || {
|
|
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
|
rm -f "$tmp_installer"
|
|
log_error "Update failed (wget error)"
|
|
echo -e "${YELLOW}Tip:${NC} Check network connection and try again."
|
|
echo -e "${YELLOW}Tip:${NC} URL: $installer_url"
|
|
exit 1
|
|
}
|
|
else
|
|
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
|
rm -f "$tmp_installer"
|
|
log_error "curl or wget required"
|
|
echo -e "${YELLOW}Tip:${NC} Install curl with: ${GRAY}brew install curl${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
|
chmod +x "$tmp_installer"
|
|
|
|
# Determine install directory
|
|
local mole_path
|
|
mole_path="$(command -v mole 2> /dev/null || echo "$0")"
|
|
local install_dir
|
|
install_dir="$(cd "$(dirname "$mole_path")" && pwd)"
|
|
|
|
local requires_sudo="false"
|
|
if [[ ! -w "$install_dir" ]]; then
|
|
requires_sudo="true"
|
|
elif [[ -e "$install_dir/mole" && ! -w "$install_dir/mole" ]]; then
|
|
requires_sudo="true"
|
|
fi
|
|
|
|
if [[ "$requires_sudo" == "true" ]]; then
|
|
if ! request_sudo_access "Mole update requires admin access"; then
|
|
log_error "Update aborted (admin access denied)"
|
|
rm -f "$tmp_installer"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [[ -t 1 ]]; then
|
|
start_inline_spinner "Installing update..."
|
|
else
|
|
echo "Installing update..."
|
|
fi
|
|
|
|
# Helper function to process installer output
|
|
process_install_output() {
|
|
local output="$1"
|
|
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
|
|
|
local filtered_output
|
|
filtered_output=$(printf '%s\n' "$output" | sed '/^$/d')
|
|
if [[ -n "$filtered_output" ]]; then
|
|
printf '\n%s\n' "$filtered_output"
|
|
fi
|
|
|
|
# Only show success message if installer didn't already do so
|
|
if ! printf '%s\n' "$output" | grep -Eq "Updated to latest version|Already on latest version"; then
|
|
local new_version
|
|
new_version=$("$mole_path" --version 2> /dev/null | awk 'NF {print $NF}' || echo "")
|
|
printf '\n%s\n\n' "${GREEN}${ICON_SUCCESS}${NC} Updated to latest version (${new_version:-unknown})"
|
|
else
|
|
printf '\n'
|
|
fi
|
|
}
|
|
|
|
# Run installer with visible output (but capture for error handling)
|
|
local install_output
|
|
local update_tag="V${latest#V}"
|
|
local config_dir="${MOLE_CONFIG_DIR:-$SCRIPT_DIR}"
|
|
if [[ ! -f "$config_dir/lib/core/common.sh" ]]; then
|
|
config_dir="$HOME/.config/mole"
|
|
fi
|
|
if install_output=$(MOLE_VERSION="$update_tag" "$tmp_installer" --prefix "$install_dir" --config "$config_dir" --update 2>&1); then
|
|
process_install_output "$install_output"
|
|
else
|
|
# Retry without --update flag
|
|
if install_output=$(MOLE_VERSION="$update_tag" "$tmp_installer" --prefix "$install_dir" --config "$config_dir" 2>&1); then
|
|
process_install_output "$install_output"
|
|
else
|
|
if [[ -t 1 ]]; then stop_inline_spinner; fi
|
|
rm -f "$tmp_installer"
|
|
log_error "Update failed"
|
|
echo "$install_output" | tail -10 >&2 # Show last 10 lines of error
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
rm -f "$tmp_installer"
|
|
rm -f "$HOME/.cache/mole/update_message"
|
|
}
|
|
|
|
# Remove Mole from system
|
|
remove_mole() {
|
|
# Detect all installations with loading
|
|
if [[ -t 1 ]]; then
|
|
start_inline_spinner "Detecting Mole installations..."
|
|
else
|
|
echo "Detecting installations..."
|
|
fi
|
|
|
|
local is_homebrew=false
|
|
local brew_cmd=""
|
|
local brew_has_mole="false"
|
|
local -a manual_installs=()
|
|
local -a alias_installs=()
|
|
|
|
if command -v brew > /dev/null 2>&1; then
|
|
brew_cmd="brew"
|
|
elif [[ -x "/opt/homebrew/bin/brew" ]]; then
|
|
brew_cmd="/opt/homebrew/bin/brew"
|
|
elif [[ -x "/usr/local/bin/brew" ]]; then
|
|
brew_cmd="/usr/local/bin/brew"
|
|
fi
|
|
|
|
if [[ -n "$brew_cmd" ]]; then
|
|
if "$brew_cmd" list --formula 2> /dev/null | grep -q "^mole$"; then
|
|
brew_has_mole="true"
|
|
fi
|
|
fi
|
|
|
|
# Check Homebrew
|
|
if [[ "$brew_has_mole" == "true" ]] || is_homebrew_install; then
|
|
is_homebrew=true
|
|
fi
|
|
|
|
# Find mole installations using which/command
|
|
local found_mole
|
|
found_mole=$(command -v mole 2> /dev/null || true)
|
|
if [[ -n "$found_mole" && -f "$found_mole" ]]; then
|
|
# Check if it's not a Homebrew symlink
|
|
if [[ ! -L "$found_mole" ]] || ! readlink "$found_mole" | grep -q "Cellar/mole"; then
|
|
manual_installs+=("$found_mole")
|
|
fi
|
|
fi
|
|
|
|
# Also check common locations as fallback
|
|
local -a fallback_paths=(
|
|
"/usr/local/bin/mole"
|
|
"$HOME/.local/bin/mole"
|
|
"/opt/local/bin/mole"
|
|
)
|
|
|
|
for path in "${fallback_paths[@]}"; do
|
|
if [[ -f "$path" && "$path" != "$found_mole" ]]; then
|
|
# Check if it's not a Homebrew symlink
|
|
if [[ ! -L "$path" ]] || ! readlink "$path" | grep -q "Cellar/mole"; then
|
|
manual_installs+=("$path")
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Find mo alias
|
|
local found_mo
|
|
found_mo=$(command -v mo 2> /dev/null || true)
|
|
if [[ -n "$found_mo" && -f "$found_mo" ]]; then
|
|
alias_installs+=("$found_mo")
|
|
fi
|
|
|
|
# Also check common locations for mo
|
|
local -a alias_fallback=(
|
|
"/usr/local/bin/mo"
|
|
"$HOME/.local/bin/mo"
|
|
"/opt/local/bin/mo"
|
|
)
|
|
|
|
for alias in "${alias_fallback[@]}"; do
|
|
if [[ -f "$alias" && "$alias" != "$found_mo" ]]; then
|
|
alias_installs+=("$alias")
|
|
fi
|
|
done
|
|
|
|
if [[ -t 1 ]]; then
|
|
stop_inline_spinner
|
|
fi
|
|
|
|
printf '\n'
|
|
|
|
# Check if anything to remove
|
|
local manual_count=${#manual_installs[@]}
|
|
local alias_count=${#alias_installs[@]}
|
|
if [[ "$is_homebrew" == "false" && ${manual_count:-0} -eq 0 && ${alias_count:-0} -eq 0 ]]; then
|
|
printf '%s\n\n' "${YELLOW}No Mole installation detected${NC}"
|
|
exit 0
|
|
fi
|
|
|
|
# List items for removal
|
|
echo -e "${YELLOW}Remove Mole${NC} - will delete the following:"
|
|
if [[ "$is_homebrew" == "true" ]]; then
|
|
echo " - Mole via Homebrew"
|
|
fi
|
|
for install in ${manual_installs[@]+"${manual_installs[@]}"} ${alias_installs[@]+"${alias_installs[@]}"}; do
|
|
echo " - $install"
|
|
done
|
|
echo " - ~/.config/mole"
|
|
echo " - ~/.cache/mole"
|
|
echo -ne "${PURPLE}${ICON_ARROW}${NC} Press ${GREEN}Enter${NC} to confirm, ${GRAY}ESC${NC} to cancel: "
|
|
|
|
# Read single key
|
|
IFS= read -r -s -n1 key || key=""
|
|
drain_pending_input # Clean up any escape sequence remnants
|
|
case "$key" in
|
|
$'\e')
|
|
exit 0
|
|
;;
|
|
"" | $'\n' | $'\r')
|
|
printf "\r\033[K" # Clear the prompt line
|
|
# Continue with removal
|
|
;;
|
|
*)
|
|
exit 0
|
|
;;
|
|
esac
|
|
|
|
# Remove Homebrew installation
|
|
local has_error=false
|
|
if [[ "$is_homebrew" == "true" ]]; then
|
|
if [[ -z "$brew_cmd" ]]; then
|
|
log_error "Homebrew command not found. Please ensure Homebrew is installed and in your PATH."
|
|
log_warning "You may need to manually run: brew uninstall --force mole"
|
|
exit 1
|
|
fi
|
|
|
|
log_admin "Attempting to uninstall Mole via Homebrew..."
|
|
local brew_uninstall_output
|
|
if ! brew_uninstall_output=$("$brew_cmd" uninstall --force mole 2>&1); then
|
|
has_error=true
|
|
log_error "Homebrew uninstallation failed:"
|
|
printf "%s\n" "$brew_uninstall_output" | sed "s/^/${RED} | ${NC}/" >&2
|
|
log_warning "Please manually run: ${YELLOW}brew uninstall --force mole${NC}"
|
|
echo "" # Add a blank line for readability
|
|
else
|
|
log_success "Mole uninstalled via Homebrew."
|
|
fi
|
|
fi
|
|
# Remove manual installations
|
|
if [[ ${manual_count:-0} -gt 0 ]]; then
|
|
for install in "${manual_installs[@]}"; do
|
|
if [[ -f "$install" ]]; then
|
|
# Check if directory requires sudo (deletion is a directory operation)
|
|
if [[ ! -w "$(dirname "$install")" ]]; then
|
|
# Requires sudo
|
|
if ! sudo rm -f "$install" 2> /dev/null; then
|
|
has_error=true
|
|
fi
|
|
else
|
|
# Regular user permission
|
|
if ! rm -f "$install" 2> /dev/null; then
|
|
has_error=true
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
if [[ ${alias_count:-0} -gt 0 ]]; then
|
|
for alias in "${alias_installs[@]}"; do
|
|
if [[ -f "$alias" ]]; then
|
|
# Check if directory requires sudo
|
|
if [[ ! -w "$(dirname "$alias")" ]]; then
|
|
sudo rm -f "$alias" 2> /dev/null || true
|
|
else
|
|
rm -f "$alias" 2> /dev/null || true
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
# Clean up cache first (silent)
|
|
if [[ -d "$HOME/.cache/mole" ]]; then
|
|
rm -rf "$HOME/.cache/mole" 2> /dev/null || true
|
|
fi
|
|
# Clean up configuration last (silent)
|
|
if [[ -d "$HOME/.config/mole" ]]; then
|
|
rm -rf "$HOME/.config/mole" 2> /dev/null || true
|
|
fi
|
|
|
|
# Show final result
|
|
local final_message
|
|
if [[ "$has_error" == "true" ]]; then
|
|
final_message="${YELLOW}${ICON_ERROR} Mole uninstalled with some errors, thank you for using Mole!${NC}"
|
|
else
|
|
final_message="${GREEN}${ICON_SUCCESS} Mole uninstalled successfully, thank you for using Mole!${NC}"
|
|
fi
|
|
printf '\n%s\n\n' "$final_message"
|
|
|
|
exit 0
|
|
}
|
|
|
|
# Display main menu options with minimal refresh to avoid flicker
|
|
show_main_menu() {
|
|
local selected="${1:-1}"
|
|
local _full_draw="${2:-true}" # Kept for compatibility (unused)
|
|
local banner="${MAIN_MENU_BANNER:-}"
|
|
local update_message="${MAIN_MENU_UPDATE_MESSAGE:-}"
|
|
|
|
# Fallback if globals missing (should not happen)
|
|
if [[ -z "$banner" ]]; then
|
|
banner="$(show_brand_banner)"
|
|
MAIN_MENU_BANNER="$banner"
|
|
fi
|
|
|
|
printf '\033[H' # Move cursor to home
|
|
|
|
local line=""
|
|
# Leading spacer
|
|
printf '\r\033[2K\n'
|
|
|
|
# Brand banner
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
printf '\r\033[2K%s\n' "$line"
|
|
done <<< "$banner"
|
|
|
|
# Update notification block (if present)
|
|
if [[ -n "$update_message" ]]; then
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
printf '\r\033[2K%s\n' "$line"
|
|
done <<< "$update_message"
|
|
fi
|
|
|
|
# Spacer before menu options
|
|
printf '\r\033[2K\n'
|
|
|
|
printf '\r\033[2K%s\n' "$(show_menu_option 1 "Clean Free up disk space" "$([[ $selected -eq 1 ]] && echo true || echo false)")"
|
|
printf '\r\033[2K%s\n' "$(show_menu_option 2 "Uninstall Remove apps completely" "$([[ $selected -eq 2 ]] && echo true || echo false)")"
|
|
printf '\r\033[2K%s\n' "$(show_menu_option 3 "Optimize Check and maintain system" "$([[ $selected -eq 3 ]] && echo true || echo false)")"
|
|
printf '\r\033[2K%s\n' "$(show_menu_option 4 "Analyze Explore disk usage" "$([[ $selected -eq 4 ]] && echo true || echo false)")"
|
|
printf '\r\033[2K%s\n' "$(show_menu_option 5 "Status Monitor system health" "$([[ $selected -eq 5 ]] && echo true || echo false)")"
|
|
|
|
if [[ -t 0 ]]; then
|
|
printf '\r\033[2K\n'
|
|
# Show TouchID if not configured, otherwise show Update
|
|
local controls="${GRAY}↑↓ | Enter | M More | "
|
|
if ! is_touchid_configured; then
|
|
controls="${controls}T TouchID"
|
|
else
|
|
controls="${controls}U Update"
|
|
fi
|
|
controls="${controls} | Q Quit${NC}"
|
|
printf '\r\033[2K%s\n' "$controls"
|
|
printf '\r\033[2K\n'
|
|
fi
|
|
|
|
# Clear any remaining content below without full screen wipe
|
|
printf '\033[J'
|
|
}
|
|
|
|
# Interactive main menu loop
|
|
interactive_main_menu() {
|
|
# Show intro animation only once per terminal tab
|
|
if [[ -t 1 ]]; then
|
|
local tty_name
|
|
tty_name=$(tty 2> /dev/null || echo "")
|
|
if [[ -n "$tty_name" ]]; then
|
|
local flag_file
|
|
local cache_dir="$HOME/.cache/mole"
|
|
ensure_user_dir "$cache_dir"
|
|
flag_file="$cache_dir/intro_$(echo "$tty_name" | tr -c '[:alnum:]_' '_')"
|
|
if [[ ! -f "$flag_file" ]]; then
|
|
animate_mole_intro
|
|
ensure_user_file "$flag_file"
|
|
fi
|
|
fi
|
|
fi
|
|
local current_option=1
|
|
local first_draw=true
|
|
local brand_banner=""
|
|
local msg_cache="$HOME/.cache/mole/update_message"
|
|
local update_message=""
|
|
|
|
brand_banner="$(show_brand_banner)"
|
|
MAIN_MENU_BANNER="$brand_banner"
|
|
|
|
if [[ -f "$msg_cache" && -s "$msg_cache" ]]; then
|
|
update_message="$(cat "$msg_cache" 2> /dev/null || echo "")"
|
|
fi
|
|
MAIN_MENU_UPDATE_MESSAGE="$update_message"
|
|
|
|
cleanup_and_exit() {
|
|
show_cursor
|
|
exit 0
|
|
}
|
|
|
|
trap cleanup_and_exit INT
|
|
hide_cursor
|
|
|
|
while true; do
|
|
show_main_menu $current_option "$first_draw"
|
|
if [[ "$first_draw" == "true" ]]; then
|
|
first_draw=false
|
|
fi
|
|
|
|
local key
|
|
if ! key=$(read_key); then
|
|
continue
|
|
fi
|
|
|
|
case "$key" in
|
|
"UP") ((current_option > 1)) && ((current_option--)) ;;
|
|
"DOWN") ((current_option < 5)) && ((current_option++)) ;;
|
|
"ENTER")
|
|
show_cursor
|
|
case $current_option in
|
|
1) exec "$SCRIPT_DIR/bin/clean.sh" ;;
|
|
2) exec "$SCRIPT_DIR/bin/uninstall.sh" ;;
|
|
3) exec "$SCRIPT_DIR/bin/optimize.sh" ;;
|
|
4) exec "$SCRIPT_DIR/bin/analyze.sh" ;;
|
|
5) exec "$SCRIPT_DIR/bin/status.sh" ;;
|
|
esac
|
|
;;
|
|
"CHAR:1")
|
|
show_cursor
|
|
exec "$SCRIPT_DIR/bin/clean.sh"
|
|
;;
|
|
"CHAR:2")
|
|
show_cursor
|
|
exec "$SCRIPT_DIR/bin/uninstall.sh"
|
|
;;
|
|
"CHAR:3")
|
|
show_cursor
|
|
exec "$SCRIPT_DIR/bin/optimize.sh"
|
|
;;
|
|
"CHAR:4")
|
|
show_cursor
|
|
exec "$SCRIPT_DIR/bin/analyze.sh"
|
|
;;
|
|
"CHAR:5")
|
|
show_cursor
|
|
exec "$SCRIPT_DIR/bin/status.sh"
|
|
;;
|
|
"MORE")
|
|
show_cursor
|
|
clear
|
|
show_help
|
|
exit 0
|
|
;;
|
|
"VERSION")
|
|
show_cursor
|
|
clear
|
|
show_version
|
|
exit 0
|
|
;;
|
|
"TOUCHID")
|
|
show_cursor
|
|
exec "$SCRIPT_DIR/bin/touchid.sh"
|
|
;;
|
|
"UPDATE")
|
|
show_cursor
|
|
clear
|
|
update_mole
|
|
exit 0
|
|
;;
|
|
"QUIT") cleanup_and_exit ;;
|
|
esac
|
|
|
|
# Drain any accumulated input after processing (e.g., touchpad scroll events)
|
|
drain_pending_input
|
|
done
|
|
}
|
|
|
|
main() {
|
|
# Parse global flags
|
|
local -a args=()
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--debug)
|
|
export MO_DEBUG=1
|
|
;;
|
|
*)
|
|
args+=("$arg")
|
|
;;
|
|
esac
|
|
done
|
|
|
|
case "${args[0]:-""}" in
|
|
"optimize")
|
|
exec "$SCRIPT_DIR/bin/optimize.sh" "${args[@]:1}"
|
|
;;
|
|
"clean")
|
|
exec "$SCRIPT_DIR/bin/clean.sh" "${args[@]:1}"
|
|
;;
|
|
"uninstall")
|
|
exec "$SCRIPT_DIR/bin/uninstall.sh" "${args[@]:1}"
|
|
;;
|
|
"analyze")
|
|
exec "$SCRIPT_DIR/bin/analyze.sh" "${args[@]:1}"
|
|
;;
|
|
"status")
|
|
exec "$SCRIPT_DIR/bin/status.sh" "${args[@]:1}"
|
|
;;
|
|
"purge")
|
|
exec "$SCRIPT_DIR/bin/purge.sh" "${args[@]:1}"
|
|
;;
|
|
"touchid")
|
|
exec "$SCRIPT_DIR/bin/touchid.sh" "${args[@]:1}"
|
|
;;
|
|
"update")
|
|
update_mole
|
|
exit 0
|
|
;;
|
|
"remove")
|
|
remove_mole
|
|
;;
|
|
"help" | "--help" | "-h")
|
|
show_help
|
|
exit 0
|
|
;;
|
|
"version" | "--version" | "-V")
|
|
show_version
|
|
exit 0
|
|
;;
|
|
"")
|
|
check_for_updates
|
|
interactive_main_menu
|
|
;;
|
|
*)
|
|
echo "Unknown command: ${args[0]}"
|
|
echo "Use 'mole --help' for usage information."
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|