1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-11 12:59:16 +00:00

refactor: centralize subcommand help handlers

This commit is contained in:
tw93
2026-02-10 14:59:10 +08:00
parent 3820bf2be7
commit 172afa83af
7 changed files with 432 additions and 411 deletions

View File

@@ -69,10 +69,10 @@ if [[ -f "$HOME/.config/mole/whitelist" ]]; then
fi
case "$line" in
/ | /System | /System/* | /bin | /bin/* | /sbin | /sbin/* | /usr/bin | /usr/bin/* | /usr/sbin | /usr/sbin/* | /etc | /etc/* | /var/db | /var/db/*)
WHITELIST_WARNINGS+=("Protected system path: $line")
continue
;;
/ | /System | /System/* | /bin | /bin/* | /sbin | /sbin/* | /usr/bin | /usr/bin/* | /usr/sbin | /usr/sbin/* | /etc | /etc/* | /var/db | /var/db/*)
WHITELIST_WARNINGS+=("Protected system path: $line")
continue
;;
esac
duplicate="false"
@@ -86,7 +86,7 @@ if [[ -f "$HOME/.config/mole/whitelist" ]]; then
fi
[[ "$duplicate" == "true" ]] && continue
WHITELIST_PATTERNS+=("$line")
done < "$HOME/.config/mole/whitelist"
done <"$HOME/.config/mole/whitelist"
else
WHITELIST_PATTERNS=("${DEFAULT_WHITELIST_PATTERNS[@]}")
fi
@@ -140,7 +140,7 @@ cleanup() {
fi
CLEANUP_DONE=true
stop_inline_spinner 2> /dev/null || true
stop_inline_spinner 2>/dev/null || true
cleanup_temp_files
@@ -162,8 +162,8 @@ start_section() {
if [[ "$DRY_RUN" == "true" ]]; then
ensure_user_file "$EXPORT_LIST_FILE"
echo "" >> "$EXPORT_LIST_FILE"
echo "=== $1 ===" >> "$EXPORT_LIST_FILE"
echo "" >>"$EXPORT_LIST_FILE"
echo "=== $1 ===" >>"$EXPORT_LIST_FILE"
fi
}
@@ -216,7 +216,7 @@ normalize_paths_for_cleanup() {
done
fi
[[ "$is_child" == "true" ]] || result_paths+=("$path")
done <<< "$sorted_paths"
done <<<"$sorted_paths"
if [[ ${#result_paths[@]} -gt 0 ]]; then
printf '%s\n' "${result_paths[@]}"
@@ -228,9 +228,9 @@ get_cleanup_path_size_kb() {
local path="$1"
if [[ -f "$path" && ! -L "$path" ]]; then
if command -v stat > /dev/null 2>&1; then
if command -v stat >/dev/null 2>&1; then
local bytes
bytes=$(stat -f%z "$path" 2> /dev/null || echo "0")
bytes=$(stat -f%z "$path" 2>/dev/null || echo "0")
if [[ "$bytes" =~ ^[0-9]+$ && "$bytes" -gt 0 ]]; then
echo $(((bytes + 1023) / 1024))
return 0
@@ -239,9 +239,9 @@ get_cleanup_path_size_kb() {
fi
if [[ -L "$path" ]]; then
if command -v stat > /dev/null 2>&1; then
if command -v stat >/dev/null 2>&1; then
local bytes
bytes=$(stat -f%z "$path" 2> /dev/null || echo "0")
bytes=$(stat -f%z "$path" 2>/dev/null || echo "0")
if [[ "$bytes" =~ ^[0-9]+$ && "$bytes" -gt 0 ]]; then
echo $(((bytes + 1023) / 1024))
else
@@ -461,9 +461,9 @@ safe_clean() {
[[ ! "$size" =~ ^[0-9]+$ ]] && size=0
if [[ "$size" -gt 0 ]]; then
echo "$size 1" > "$temp_dir/result_${idx}"
echo "$size 1" >"$temp_dir/result_${idx}"
else
echo "0 0" > "$temp_dir/result_${idx}"
echo "0 0" >"$temp_dir/result_${idx}"
fi
((idx++))
@@ -488,17 +488,17 @@ safe_clean() {
[[ ! "$size" =~ ^[0-9]+$ ]] && size=0
local tmp_file="$temp_dir/result_${idx}.$$"
if [[ "$size" -gt 0 ]]; then
echo "$size 1" > "$tmp_file"
echo "$size 1" >"$tmp_file"
else
echo "0 0" > "$tmp_file"
echo "0 0" >"$tmp_file"
fi
mv "$tmp_file" "$temp_dir/result_${idx}" 2> /dev/null || true
mv "$tmp_file" "$temp_dir/result_${idx}" 2>/dev/null || true
) &
pids+=($!)
((idx++))
if ((${#pids[@]} >= MOLE_MAX_PARALLEL_JOBS)); then
wait "${pids[0]}" 2> /dev/null || true
wait "${pids[0]}" 2>/dev/null || true
pids=("${pids[@]:1}")
((completed++))
@@ -511,7 +511,7 @@ safe_clean() {
if [[ ${#pids[@]} -gt 0 ]]; then
for pid in "${pids[@]}"; do
wait "$pid" 2> /dev/null || true
wait "$pid" 2>/dev/null || true
((completed++))
if [[ "$show_spinner" == "true" && -t 1 ]]; then
@@ -532,7 +532,7 @@ safe_clean() {
for path in "${existing_paths[@]}"; do
local result_file="$temp_dir/result_${idx}"
if [[ -f "$result_file" ]]; then
read -r size count < "$result_file" 2> /dev/null || true
read -r size count <"$result_file" 2>/dev/null || true
local removed=0
if [[ "$DRY_RUN" != "true" ]]; then
if safe_remove "$path" true; then
@@ -631,9 +631,9 @@ safe_clean() {
local size=0
if [[ -n "${temp_dir:-}" && -f "$temp_dir/result_${idx}" ]]; then
read -r size count < "$temp_dir/result_${idx}" 2> /dev/null || true
read -r size count <"$temp_dir/result_${idx}" 2>/dev/null || true
else
size=$(get_cleanup_path_size_kb "$path" 2> /dev/null || echo "0")
size=$(get_cleanup_path_size_kb "$path" 2>/dev/null || echo "0")
fi
[[ "$size" == "0" || -z "$size" ]] && {
@@ -641,7 +641,7 @@ safe_clean() {
continue
}
echo "$(dirname "$path")|$size|$path" >> "$paths_temp"
echo "$(dirname "$path")|$size|$path" >>"$paths_temp"
((idx++))
done
fi
@@ -672,9 +672,9 @@ safe_clean() {
' | while IFS='|' read -r display_path total_size child_count; do
local size_human=$(bytes_to_human "$((total_size * 1024))")
if [[ $child_count -gt 1 ]]; then
echo "$display_path # $size_human, $child_count items" >> "$EXPORT_LIST_FILE"
echo "$display_path # $size_human, $child_count items" >>"$EXPORT_LIST_FILE"
else
echo "$display_path # $size_human" >> "$EXPORT_LIST_FILE"
echo "$display_path # $size_human" >>"$EXPORT_LIST_FILE"
fi
done
@@ -714,7 +714,7 @@ start_cleanup() {
SYSTEM_CLEAN=false
ensure_user_file "$EXPORT_LIST_FILE"
cat > "$EXPORT_LIST_FILE" << EOF
cat >"$EXPORT_LIST_FILE" <<EOF
# Mole Cleanup Preview - $(date '+%Y-%m-%d %H:%M:%S')
#
# How to protect files:
@@ -730,7 +730,7 @@ EOF
fi
if [[ -t 0 ]]; then
if sudo -n true 2> /dev/null; then
if sudo -n true 2>/dev/null; then
SYSTEM_CLEAN=true
echo -e "${GREEN}${ICON_SUCCESS}${NC} Admin access already available"
echo ""
@@ -770,7 +770,7 @@ EOF
else
echo ""
echo "Running in non-interactive mode"
if sudo -n true 2> /dev/null; then
if sudo -n true 2>/dev/null; then
SYSTEM_CLEAN=true
echo " ${ICON_LIST} System-level cleanup enabled, sudo session active"
else
@@ -1022,7 +1022,7 @@ perform_cleanup() {
echo "# Potential cleanup: ${freed_gb}GB"
echo "# Items: $files_cleaned"
echo "# Categories: $total_items"
} >> "$EXPORT_LIST_FILE"
} >>"$EXPORT_LIST_FILE"
summary_details+=("Detailed file list: ${GRAY}$EXPORT_LIST_FILE${NC}")
summary_details+=("Use ${GRAY}mo clean --whitelist${NC} to add protection rules")
@@ -1078,30 +1078,22 @@ perform_cleanup() {
main() {
for arg in "$@"; do
case "$arg" in
"--help" | "-h")
echo "Usage: mo clean [OPTIONS]"
echo ""
echo "Clean up disk space by removing caches, logs, and temporary files."
echo ""
echo "Options:"
echo " --dry-run, -n Preview cleanup without making changes"
echo " --whitelist Manage protected paths"
echo " --debug Show detailed operation logs"
echo " -h, --help Show this help message"
exit 0
;;
"--debug")
export MO_DEBUG=1
;;
"--dry-run" | "-n")
DRY_RUN=true
export MOLE_DRY_RUN=1
;;
"--whitelist")
source "$SCRIPT_DIR/../lib/manage/whitelist.sh"
manage_whitelist "clean"
exit 0
;;
"--help" | "-h")
show_clean_help
exit 0
;;
"--debug")
export MO_DEBUG=1
;;
"--dry-run" | "-n")
DRY_RUN=true
export MOLE_DRY_RUN=1
;;
"--whitelist")
source "$SCRIPT_DIR/../lib/manage/whitelist.sh"
manage_whitelist "clean"
exit 0
;;
esac
done

View File

@@ -47,9 +47,9 @@ readonly MAX_ZIP_ENTRIES=50
ZIP_LIST_CMD=()
IN_ALT_SCREEN=0
if command -v zipinfo > /dev/null 2>&1; then
if command -v zipinfo >/dev/null 2>&1; then
ZIP_LIST_CMD=(zipinfo -1)
elif command -v unzip > /dev/null 2>&1; then
elif command -v unzip >/dev/null 2>&1; then
ZIP_LIST_CMD=(unzip -Z -1)
fi
@@ -62,7 +62,7 @@ is_installer_zip() {
[[ ${#ZIP_LIST_CMD[@]} -gt 0 ]] || return 1
if ! "${ZIP_LIST_CMD[@]}" "$zip" 2> /dev/null |
if ! "${ZIP_LIST_CMD[@]}" "$zip" 2>/dev/null |
head -n "$cap" |
awk '
/\.(app|pkg|dmg|xip)(\/|$)/ { found=1; exit 0 }
@@ -79,15 +79,15 @@ handle_candidate_file() {
[[ -L "$file" ]] && return 0 # Skip symlinks explicitly
case "$file" in
*.dmg | *.pkg | *.mpkg | *.iso | *.xip)
*.dmg | *.pkg | *.mpkg | *.iso | *.xip)
echo "$file"
;;
*.zip)
[[ -r "$file" ]] || return 0
if is_installer_zip "$file" 2>/dev/null; then
echo "$file"
;;
*.zip)
[[ -r "$file" ]] || return 0
if is_installer_zip "$file" 2> /dev/null; then
echo "$file"
fi
;;
fi
;;
esac
}
@@ -99,13 +99,13 @@ scan_installers_in_path() {
local file
if command -v fd > /dev/null 2>&1; then
if command -v fd >/dev/null 2>&1; then
while IFS= read -r file; do
handle_candidate_file "$file"
done < <(
fd --no-ignore --hidden --type f --max-depth "$max_depth" \
-e dmg -e pkg -e mpkg -e iso -e xip -e zip \
. "$path" 2> /dev/null || true
. "$path" 2>/dev/null || true
)
else
while IFS= read -r file; do
@@ -114,7 +114,7 @@ scan_installers_in_path() {
find "$path" -maxdepth "$max_depth" -type f \
\( -name '*.dmg' -o -name '*.pkg' -o -name '*.mpkg' \
-o -name '*.iso' -o -name '*.xip' -o -name '*.zip' \) \
2> /dev/null || true
2>/dev/null || true
)
fi
}
@@ -142,23 +142,23 @@ get_source_display() {
# Match against known paths and return friendly names
case "$dir_path" in
"$HOME/Downloads"*) echo "Downloads" ;;
"$HOME/Desktop"*) echo "Desktop" ;;
"$HOME/Documents"*) echo "Documents" ;;
"$HOME/Public"*) echo "Public" ;;
"$HOME/Library/Downloads"*) echo "Library" ;;
"/Users/Shared"*) echo "Shared" ;;
"$HOME/Library/Caches/Homebrew"*) echo "Homebrew" ;;
"$HOME/Library/Mobile Documents/com~apple~CloudDocs/Downloads"*) echo "iCloud" ;;
"$HOME/Library/Containers/com.apple.mail"*) echo "Mail" ;;
*"Telegram Desktop"*) echo "Telegram" ;;
*) echo "${dir_path##*/}" ;;
"$HOME/Downloads"*) echo "Downloads" ;;
"$HOME/Desktop"*) echo "Desktop" ;;
"$HOME/Documents"*) echo "Documents" ;;
"$HOME/Public"*) echo "Public" ;;
"$HOME/Library/Downloads"*) echo "Library" ;;
"/Users/Shared"*) echo "Shared" ;;
"$HOME/Library/Caches/Homebrew"*) echo "Homebrew" ;;
"$HOME/Library/Mobile Documents/com~apple~CloudDocs/Downloads"*) echo "iCloud" ;;
"$HOME/Library/Containers/com.apple.mail"*) echo "Mail" ;;
*"Telegram Desktop"*) echo "Telegram" ;;
*) echo "${dir_path##*/}" ;;
esac
}
get_terminal_width() {
if [[ $TERMINAL_WIDTH -le 0 ]]; then
TERMINAL_WIDTH=$(tput cols 2> /dev/null || echo 80)
TERMINAL_WIDTH=$(tput cols 2>/dev/null || echo 80)
fi
echo "$TERMINAL_WIDTH"
}
@@ -291,11 +291,11 @@ select_installers() {
_get_items_per_page() {
local term_height=24
if [[ -t 0 ]] || [[ -t 2 ]]; then
term_height=$(stty size < /dev/tty 2> /dev/null | awk '{print $1}')
term_height=$(stty size </dev/tty 2>/dev/null | awk '{print $1}')
fi
if [[ -z "$term_height" || $term_height -le 0 ]]; then
if command -v tput > /dev/null 2>&1; then
term_height=$(tput lines 2> /dev/null || echo "24")
if command -v tput >/dev/null 2>&1; then
term_height=$(tput lines 2>/dev/null || echo "24")
else
term_height=24
fi
@@ -322,8 +322,8 @@ select_installers() {
done
local original_stty=""
if [[ -t 0 ]] && command -v stty > /dev/null 2>&1; then
original_stty=$(stty -g 2> /dev/null || echo "")
if [[ -t 0 ]] && command -v stty >/dev/null 2>&1; then
original_stty=$(stty -g 2>/dev/null || echo "")
fi
restore_terminal() {
@@ -334,7 +334,7 @@ select_installers() {
fi
show_cursor
if [[ -n "${original_stty:-}" ]]; then
stty "${original_stty}" 2> /dev/null || stty sane 2> /dev/null || true
stty "${original_stty}" 2>/dev/null || stty sane 2>/dev/null || true
fi
}
@@ -417,7 +417,7 @@ select_installers() {
trap restore_terminal EXIT
trap handle_interrupt INT TERM
stty -echo -icanon intr ^C 2> /dev/null || true
stty -echo -icanon intr ^C 2>/dev/null || true
hide_cursor
if [[ -t 1 ]]; then
printf "\033[2J\033[H" >&2
@@ -429,75 +429,75 @@ select_installers() {
IFS= read -r -s -n1 key || key=""
case "$key" in
$'\x1b')
IFS= read -r -s -n1 -t 1 key2 || key2=""
if [[ "$key2" == "[" ]]; then
IFS= read -r -s -n1 -t 1 key3 || key3=""
case "$key3" in
A) # Up arrow
if [[ $cursor_pos -gt 0 ]]; then
((cursor_pos--))
elif [[ $top_index -gt 0 ]]; then
((top_index--))
fi
;;
B) # Down arrow
local absolute_index=$((top_index + cursor_pos))
local last_index=$((total_items - 1))
if [[ $absolute_index -lt $last_index ]]; then
local visible_count=$((total_items - top_index))
[[ $visible_count -gt $items_per_page ]] && visible_count=$items_per_page
if [[ $cursor_pos -lt $((visible_count - 1)) ]]; then
((cursor_pos++))
elif [[ $((top_index + visible_count)) -lt $total_items ]]; then
((top_index++))
fi
fi
;;
esac
else
# ESC alone
restore_terminal
return 1
fi
;;
" ") # Space - toggle current item
local idx=$((top_index + cursor_pos))
if [[ ${selected[idx]} == true ]]; then
selected[idx]=false
else
selected[idx]=true
fi
;;
"a" | "A") # Select all
for ((i = 0; i < total_items; i++)); do
selected[i]=true
done
;;
"i" | "I") # Invert selection
for ((i = 0; i < total_items; i++)); do
if [[ ${selected[i]} == true ]]; then
selected[i]=false
else
selected[i]=true
$'\x1b')
IFS= read -r -s -n1 -t 1 key2 || key2=""
if [[ "$key2" == "[" ]]; then
IFS= read -r -s -n1 -t 1 key3 || key3=""
case "$key3" in
A) # Up arrow
if [[ $cursor_pos -gt 0 ]]; then
((cursor_pos--))
elif [[ $top_index -gt 0 ]]; then
((top_index--))
fi
done
;;
"q" | "Q" | $'\x03') # Quit or Ctrl-C
;;
B) # Down arrow
local absolute_index=$((top_index + cursor_pos))
local last_index=$((total_items - 1))
if [[ $absolute_index -lt $last_index ]]; then
local visible_count=$((total_items - top_index))
[[ $visible_count -gt $items_per_page ]] && visible_count=$items_per_page
if [[ $cursor_pos -lt $((visible_count - 1)) ]]; then
((cursor_pos++))
elif [[ $((top_index + visible_count)) -lt $total_items ]]; then
((top_index++))
fi
fi
;;
esac
else
# ESC alone
restore_terminal
return 1
;;
"" | $'\n' | $'\r') # Enter - confirm
MOLE_SELECTION_RESULT=""
for ((i = 0; i < total_items; i++)); do
if [[ ${selected[i]} == true ]]; then
[[ -n "$MOLE_SELECTION_RESULT" ]] && MOLE_SELECTION_RESULT+=","
MOLE_SELECTION_RESULT+="$i"
fi
done
restore_terminal
return 0
;;
fi
;;
" ") # Space - toggle current item
local idx=$((top_index + cursor_pos))
if [[ ${selected[idx]} == true ]]; then
selected[idx]=false
else
selected[idx]=true
fi
;;
"a" | "A") # Select all
for ((i = 0; i < total_items; i++)); do
selected[i]=true
done
;;
"i" | "I") # Invert selection
for ((i = 0; i < total_items; i++)); do
if [[ ${selected[i]} == true ]]; then
selected[i]=false
else
selected[i]=true
fi
done
;;
"q" | "Q" | $'\x03') # Quit or Ctrl-C
restore_terminal
return 1
;;
"" | $'\n' | $'\r') # Enter - confirm
MOLE_SELECTION_RESULT=""
for ((i = 0; i < total_items; i++)); do
if [[ ${selected[i]} == true ]]; then
[[ -n "$MOLE_SELECTION_RESULT" ]] && MOLE_SELECTION_RESULT+=","
MOLE_SELECTION_RESULT+="$i"
fi
done
restore_terminal
return 0
;;
esac
done
}
@@ -522,7 +522,7 @@ show_installer_menu() {
delete_selected_installers() {
# Parse selection indices
local -a selected_indices=()
[[ -n "$MOLE_SELECTION_RESULT" ]] && IFS=',' read -ra selected_indices <<< "$MOLE_SELECTION_RESULT"
[[ -n "$MOLE_SELECTION_RESULT" ]] && IFS=',' read -ra selected_indices <<<"$MOLE_SELECTION_RESULT"
if [[ ${#selected_indices[@]} -eq 0 ]]; then
return 1
@@ -556,16 +556,16 @@ delete_selected_installers() {
IFS= read -r -s -n1 confirm || confirm=""
case "$confirm" in
$'\e' | q | Q)
return 1
;;
"" | $'\n' | $'\r')
printf "\r\033[K" # Clear prompt line
echo "" # Single line break
;;
*)
return 1
;;
$'\e' | q | Q)
return 1
;;
"" | $'\n' | $'\r')
printf "\r\033[K" # Clear prompt line
echo "" # Single line break
;;
*)
return 1
;;
esac
# Delete each selected installer with spinner
@@ -668,23 +668,17 @@ show_summary() {
main() {
for arg in "$@"; do
case "$arg" in
"--help" | "-h")
echo "Usage: mo installer [OPTIONS]"
echo ""
echo "Find and remove installer files (.dmg, .pkg, .iso, .xip, .zip)."
echo ""
echo "Options:"
echo " --debug Show detailed operation logs"
echo " -h, --help Show this help message"
exit 0
;;
"--debug")
export MO_DEBUG=1
;;
*)
echo "Unknown option: $arg"
exit 1
;;
"--help" | "-h")
show_installer_help
exit 0
;;
"--debug")
export MO_DEBUG=1
;;
*)
echo "Unknown option: $arg"
exit 1
;;
esac
done
@@ -694,15 +688,15 @@ main() {
show_cursor
case $exit_code in
0)
show_summary
;;
1)
printf '\n'
;;
2)
# Already handled by collect_installers
;;
0)
show_summary
;;
1)
printf '\n'
;;
2)
# Already handled by collect_installers
;;
esac
return 0

View File

@@ -139,12 +139,12 @@ show_optimization_summary() {
show_system_health() {
local health_json="$1"
local mem_used=$(echo "$health_json" | jq -r '.memory_used_gb // 0' 2> /dev/null || echo "0")
local mem_total=$(echo "$health_json" | jq -r '.memory_total_gb // 0' 2> /dev/null || echo "0")
local disk_used=$(echo "$health_json" | jq -r '.disk_used_gb // 0' 2> /dev/null || echo "0")
local disk_total=$(echo "$health_json" | jq -r '.disk_total_gb // 0' 2> /dev/null || echo "0")
local disk_percent=$(echo "$health_json" | jq -r '.disk_used_percent // 0' 2> /dev/null || echo "0")
local uptime=$(echo "$health_json" | jq -r '.uptime_days // 0' 2> /dev/null || echo "0")
local mem_used=$(echo "$health_json" | jq -r '.memory_used_gb // 0' 2>/dev/null || echo "0")
local mem_total=$(echo "$health_json" | jq -r '.memory_total_gb // 0' 2>/dev/null || echo "0")
local disk_used=$(echo "$health_json" | jq -r '.disk_used_gb // 0' 2>/dev/null || echo "0")
local disk_total=$(echo "$health_json" | jq -r '.disk_total_gb // 0' 2>/dev/null || echo "0")
local disk_percent=$(echo "$health_json" | jq -r '.disk_used_percent // 0' 2>/dev/null || echo "0")
local uptime=$(echo "$health_json" | jq -r '.uptime_days // 0' 2>/dev/null || echo "0")
mem_used=${mem_used:-0}
mem_total=${mem_total:-0}
@@ -159,7 +159,7 @@ show_system_health() {
parse_optimizations() {
local health_json="$1"
echo "$health_json" | jq -c '.optimizations[]' 2> /dev/null
echo "$health_json" | jq -c '.optimizations[]' 2>/dev/null
}
announce_action() {
@@ -177,12 +177,12 @@ announce_action() {
touchid_configured() {
local pam_file="/etc/pam.d/sudo"
[[ -f "$pam_file" ]] && grep -q "pam_tid.so" "$pam_file" 2> /dev/null
[[ -f "$pam_file" ]] && grep -q "pam_tid.so" "$pam_file" 2>/dev/null
}
touchid_supported() {
if command -v bioutil > /dev/null 2>&1; then
if bioutil -r 2> /dev/null | grep -qi "Touch ID"; then
if command -v bioutil >/dev/null 2>&1; then
if bioutil -r 2>/dev/null | grep -qi "Touch ID"; then
return 0
fi
fi
@@ -272,7 +272,7 @@ ask_for_security_fixes() {
echo ""
echo -e "${BLUE}SECURITY FIXES${NC}"
for entry in "${SECURITY_FIXES[@]}"; do
IFS='|' read -r _ label <<< "$entry"
IFS='|' read -r _ label <<<"$entry"
echo -e " ${ICON_LIST} $label"
done
echo ""
@@ -299,7 +299,7 @@ ask_for_security_fixes() {
}
apply_firewall_fix() {
if sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate on > /dev/null 2>&1; then
if sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate on >/dev/null 2>&1; then
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Firewall enabled"
FIREWALL_DISABLED=false
return 0
@@ -309,7 +309,7 @@ apply_firewall_fix() {
}
apply_gatekeeper_fix() {
if sudo spctl --master-enable 2> /dev/null; then
if sudo spctl --master-enable 2>/dev/null; then
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Gatekeeper enabled"
GATEKEEPER_DISABLED=false
return 0
@@ -333,17 +333,17 @@ perform_security_fixes() {
local applied=0
for entry in "${SECURITY_FIXES[@]}"; do
IFS='|' read -r action _ <<< "$entry"
IFS='|' read -r action _ <<<"$entry"
case "$action" in
firewall)
apply_firewall_fix && ((applied++))
;;
gatekeeper)
apply_gatekeeper_fix && ((applied++))
;;
touchid)
apply_touchid_fix && ((applied++))
;;
firewall)
apply_firewall_fix && ((applied++))
;;
gatekeeper)
apply_gatekeeper_fix && ((applied++))
;;
touchid)
apply_touchid_fix && ((applied++))
;;
esac
done
@@ -354,7 +354,7 @@ perform_security_fixes() {
}
cleanup_all() {
stop_inline_spinner 2> /dev/null || true
stop_inline_spinner 2>/dev/null || true
stop_sudo_session
cleanup_temp_files
# Log session end
@@ -373,28 +373,20 @@ main() {
local health_json
for arg in "$@"; do
case "$arg" in
"--help" | "-h")
echo "Usage: mo optimize [OPTIONS]"
echo ""
echo "Check and maintain system health, apply optimizations."
echo ""
echo "Options:"
echo " --dry-run Preview optimization without making changes"
echo " --whitelist Manage protected items"
echo " --debug Show detailed operation logs"
echo " -h, --help Show this help message"
exit 0
;;
"--debug")
export MO_DEBUG=1
;;
"--dry-run")
export MOLE_DRY_RUN=1
;;
"--whitelist")
manage_whitelist "optimize"
exit 0
;;
"--help" | "-h")
show_optimize_help
exit 0
;;
"--debug")
export MO_DEBUG=1
;;
"--dry-run")
export MOLE_DRY_RUN=1
;;
"--whitelist")
manage_whitelist "optimize"
exit 0
;;
esac
done
@@ -413,13 +405,13 @@ main() {
echo -e "${YELLOW}${ICON_DRY_RUN} DRY RUN MODE${NC}, No files will be modified\n"
fi
if ! command -v jq > /dev/null 2>&1; then
if ! command -v jq >/dev/null 2>&1; then
echo -e "${YELLOW}${ICON_ERROR}${NC} Missing dependency: jq"
echo -e "${GRAY}Install with: ${GREEN}brew install jq${NC}"
exit 1
fi
if ! command -v bc > /dev/null 2>&1; then
if ! command -v bc >/dev/null 2>&1; then
echo -e "${YELLOW}${ICON_ERROR}${NC} Missing dependency: bc"
echo -e "${GRAY}Install with: ${GREEN}brew install bc${NC}"
exit 1
@@ -429,7 +421,7 @@ main() {
start_inline_spinner "Collecting system info..."
fi
if ! health_json=$(generate_health_json 2> /dev/null); then
if ! health_json=$(generate_health_json 2>/dev/null); then
if [[ -t 1 ]]; then
stop_inline_spinner
fi
@@ -438,7 +430,7 @@ main() {
exit 1
fi
if ! echo "$health_json" | jq empty 2> /dev/null; then
if ! echo "$health_json" | jq empty 2>/dev/null; then
if [[ -t 1 ]]; then
stop_inline_spinner
fi
@@ -470,7 +462,7 @@ main() {
local -a confirm_items=()
local opts_file
opts_file=$(mktemp_file)
parse_optimizations "$health_json" > "$opts_file"
parse_optimizations "$health_json" >"$opts_file"
while IFS= read -r opt_json; do
[[ -z "$opt_json" ]] && continue
@@ -488,7 +480,7 @@ main() {
else
confirm_items+=("$item")
fi
done < "$opts_file"
done <"$opts_file"
echo ""
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
@@ -498,7 +490,7 @@ main() {
export FIRST_ACTION=true
if [[ ${#safe_items[@]} -gt 0 ]]; then
for item in "${safe_items[@]}"; do
IFS='|' read -r name desc action path <<< "$item"
IFS='|' read -r name desc action path <<<"$item"
announce_action "$name" "$desc" "safe"
execute_optimization "$action" "$path"
done
@@ -506,7 +498,7 @@ main() {
if [[ ${#confirm_items[@]} -gt 0 ]]; then
for item in "${confirm_items[@]}"; do
IFS='|' read -r name desc action path <<< "$item"
IFS='|' read -r name desc action path <<<"$item"
announce_action "$name" "$desc" "confirm"
execute_optimization "$action" "$path"
done

View File

@@ -26,21 +26,21 @@ readonly PAM_TID_LINE="auth sufficient pam_tid.so"
is_touchid_configured() {
# Check sudo_local first
if [[ -f "$PAM_SUDO_LOCAL_FILE" ]]; then
grep -q "pam_tid.so" "$PAM_SUDO_LOCAL_FILE" 2> /dev/null && return 0
grep -q "pam_tid.so" "$PAM_SUDO_LOCAL_FILE" 2>/dev/null && return 0
fi
# Fallback to standard sudo file
if [[ ! -f "$PAM_SUDO_FILE" ]]; then
return 1
fi
grep -q "pam_tid.so" "$PAM_SUDO_FILE" 2> /dev/null
grep -q "pam_tid.so" "$PAM_SUDO_FILE" 2>/dev/null
}
# Check if system supports Touch ID
supports_touchid() {
# Check if bioutil exists and has Touch ID capability
if command -v bioutil &> /dev/null; then
bioutil -r 2> /dev/null | grep -q "Touch ID" && return 0
if command -v bioutil &>/dev/null; then
bioutil -r 2>/dev/null | grep -q "Touch ID" && return 0
fi
# Fallback: check if running on Apple Silicon or modern Intel Mac
@@ -52,7 +52,7 @@ supports_touchid() {
# For Intel Macs, check if it's 2018 or later (approximation)
local model_year
model_year=$(system_profiler SPHardwareDataType 2> /dev/null | grep "Model Identifier" | grep -o "[0-9]\{4\}" | head -1)
model_year=$(system_profiler SPHardwareDataType 2>/dev/null | grep "Model Identifier" | grep -o "[0-9]\{4\}" | head -1)
if [[ -n "$model_year" ]] && [[ "$model_year" -ge 2018 ]]; then
return 0
fi
@@ -93,8 +93,8 @@ enable_touchid() {
if grep -q "pam_tid.so" "$PAM_SUDO_FILE"; then
# Clean up legacy config
temp_file=$(create_temp_file)
grep -v "pam_tid.so" "$PAM_SUDO_FILE" > "$temp_file"
if sudo mv "$temp_file" "$PAM_SUDO_FILE" 2> /dev/null; then
grep -v "pam_tid.so" "$PAM_SUDO_FILE" >"$temp_file"
if sudo mv "$temp_file" "$PAM_SUDO_FILE" 2>/dev/null; then
echo -e "${GREEN}${ICON_SUCCESS} Cleanup legacy configuration${NC}"
fi
fi
@@ -113,8 +113,8 @@ enable_touchid() {
local write_success=false
if [[ ! -f "$PAM_SUDO_LOCAL_FILE" ]]; then
# Create the file
echo "# sudo_local: local customizations for sudo" | sudo tee "$PAM_SUDO_LOCAL_FILE" > /dev/null
echo "$PAM_TID_LINE" | sudo tee -a "$PAM_SUDO_LOCAL_FILE" > /dev/null
echo "# sudo_local: local customizations for sudo" | sudo tee "$PAM_SUDO_LOCAL_FILE" >/dev/null
echo "$PAM_TID_LINE" | sudo tee -a "$PAM_SUDO_LOCAL_FILE" >/dev/null
sudo chmod 444 "$PAM_SUDO_LOCAL_FILE"
sudo chown root:wheel "$PAM_SUDO_LOCAL_FILE"
write_success=true
@@ -123,7 +123,7 @@ enable_touchid() {
if ! grep -q "pam_tid.so" "$PAM_SUDO_LOCAL_FILE"; then
temp_file=$(create_temp_file)
cp "$PAM_SUDO_LOCAL_FILE" "$temp_file"
echo "$PAM_TID_LINE" >> "$temp_file"
echo "$PAM_TID_LINE" >>"$temp_file"
sudo mv "$temp_file" "$PAM_SUDO_LOCAL_FILE"
sudo chmod 444 "$PAM_SUDO_LOCAL_FILE"
sudo chown root:wheel "$PAM_SUDO_LOCAL_FILE"
@@ -137,7 +137,7 @@ enable_touchid() {
# If we migrated from legacy, clean it up now
if $is_legacy_configured; then
temp_file=$(create_temp_file)
grep -v "pam_tid.so" "$PAM_SUDO_FILE" > "$temp_file"
grep -v "pam_tid.so" "$PAM_SUDO_FILE" >"$temp_file"
sudo mv "$temp_file" "$PAM_SUDO_FILE"
log_success "Touch ID migrated to sudo_local"
else
@@ -160,7 +160,7 @@ enable_touchid() {
# Create backup only if it doesn't exist to preserve original state
if [[ ! -f "${PAM_SUDO_FILE}.mole-backup" ]]; then
if ! sudo cp "$PAM_SUDO_FILE" "${PAM_SUDO_FILE}.mole-backup" 2> /dev/null; then
if ! sudo cp "$PAM_SUDO_FILE" "${PAM_SUDO_FILE}.mole-backup" 2>/dev/null; then
log_error "Failed to create backup"
return 1
fi
@@ -178,7 +178,7 @@ enable_touchid() {
inserted = 1
}
{ print }
' "$PAM_SUDO_FILE" > "$temp_file"
' "$PAM_SUDO_FILE" >"$temp_file"
# Verify content change
if cmp -s "$PAM_SUDO_FILE" "$temp_file"; then
@@ -187,7 +187,7 @@ enable_touchid() {
fi
# Apply the changes
if sudo mv "$temp_file" "$PAM_SUDO_FILE" 2> /dev/null; then
if sudo mv "$temp_file" "$PAM_SUDO_FILE" 2>/dev/null; then
log_success "Touch ID enabled, try: sudo ls"
return 0
else
@@ -210,13 +210,13 @@ disable_touchid() {
if [[ -f "$PAM_SUDO_LOCAL_FILE" ]] && grep -q "pam_tid.so" "$PAM_SUDO_LOCAL_FILE"; then
# Remove from sudo_local
temp_file=$(create_temp_file)
grep -v "pam_tid.so" "$PAM_SUDO_LOCAL_FILE" > "$temp_file"
grep -v "pam_tid.so" "$PAM_SUDO_LOCAL_FILE" >"$temp_file"
if sudo mv "$temp_file" "$PAM_SUDO_LOCAL_FILE" 2> /dev/null; then
if sudo mv "$temp_file" "$PAM_SUDO_LOCAL_FILE" 2>/dev/null; then
# Since we modified sudo_local, we should also check if it's in sudo file (legacy cleanup)
if grep -q "pam_tid.so" "$PAM_SUDO_FILE"; then
temp_file=$(create_temp_file)
grep -v "pam_tid.so" "$PAM_SUDO_FILE" > "$temp_file"
grep -v "pam_tid.so" "$PAM_SUDO_FILE" >"$temp_file"
sudo mv "$temp_file" "$PAM_SUDO_FILE"
fi
echo -e "${GREEN}${ICON_SUCCESS} Touch ID disabled, removed from sudo_local${NC}"
@@ -232,7 +232,7 @@ disable_touchid() {
if grep -q "pam_tid.so" "$PAM_SUDO_FILE"; then
# Create backup only if it doesn't exist
if [[ ! -f "${PAM_SUDO_FILE}.mole-backup" ]]; then
if ! sudo cp "$PAM_SUDO_FILE" "${PAM_SUDO_FILE}.mole-backup" 2> /dev/null; then
if ! sudo cp "$PAM_SUDO_FILE" "${PAM_SUDO_FILE}.mole-backup" 2>/dev/null; then
log_error "Failed to create backup"
return 1
fi
@@ -240,9 +240,9 @@ disable_touchid() {
# Remove pam_tid.so line
temp_file=$(create_temp_file)
grep -v "pam_tid.so" "$PAM_SUDO_FILE" > "$temp_file"
grep -v "pam_tid.so" "$PAM_SUDO_FILE" >"$temp_file"
if sudo mv "$temp_file" "$PAM_SUDO_FILE" 2> /dev/null; then
if sudo mv "$temp_file" "$PAM_SUDO_FILE" 2>/dev/null; then
echo -e "${GREEN}${ICON_SUCCESS} Touch ID disabled${NC}"
echo ""
return 0
@@ -268,17 +268,17 @@ show_menu() {
echo ""
case "$key" in
$'\e') # ESC
return 0
;;
"" | $'\n' | $'\r') # Enter
printf "\r\033[K" # Clear the prompt line
disable_touchid
;;
*)
echo ""
log_error "Invalid key"
;;
$'\e') # ESC
return 0
;;
"" | $'\n' | $'\r') # Enter
printf "\r\033[K" # Clear the prompt line
disable_touchid
;;
*)
echo ""
log_error "Invalid key"
;;
esac
else
echo -ne "${PURPLE}${NC} Press ${GREEN}Enter${NC} to enable, ${GRAY}Q${NC} to quit: "
@@ -286,17 +286,17 @@ show_menu() {
drain_pending_input # Clean up any escape sequence remnants
case "$key" in
$'\e') # ESC
return 0
;;
"" | $'\n' | $'\r') # Enter
printf "\r\033[K" # Clear the prompt line
enable_touchid
;;
*)
echo ""
log_error "Invalid key"
;;
$'\e') # ESC
return 0
;;
"" | $'\n' | $'\r') # Enter
printf "\r\033[K" # Clear the prompt line
enable_touchid
;;
*)
echo ""
log_error "Invalid key"
;;
esac
fi
}
@@ -306,38 +306,25 @@ main() {
local command="${1:-}"
case "$command" in
"--help" | "-h")
echo "Usage: mo touchid [COMMAND]"
echo ""
echo "Configure Touch ID for sudo authentication."
echo ""
echo "Commands:"
echo " enable Enable Touch ID for sudo"
echo " disable Disable Touch ID for sudo"
echo " status Show current Touch ID status"
echo ""
echo "Options:"
echo " -h, --help Show this help message"
echo ""
echo "If no command is provided, an interactive menu is shown."
exit 0
;;
enable)
enable_touchid
;;
disable)
disable_touchid
;;
status)
show_status
;;
"")
show_menu
;;
*)
log_error "Unknown command: $command"
exit 1
;;
"--help" | "-h")
show_touchid_help
;;
enable)
enable_touchid
;;
disable)
disable_touchid
;;
status)
show_status
;;
"")
show_menu
;;
*)
log_error "Unknown command: $command"
exit 1
;;
esac
}

View File

@@ -79,17 +79,17 @@ uninstall_resolve_display_name() {
if [[ -f "$app_path/Contents/Info.plist" ]]; then
local md_display_name
if [[ -n "$MOLE_UNINSTALL_USER_LC_ALL" ]]; then
md_display_name=$(run_with_timeout 0.04 env LC_ALL="$MOLE_UNINSTALL_USER_LC_ALL" LANG="$MOLE_UNINSTALL_USER_LANG" mdls -name kMDItemDisplayName -raw "$app_path" 2> /dev/null || echo "")
md_display_name=$(run_with_timeout 0.04 env LC_ALL="$MOLE_UNINSTALL_USER_LC_ALL" LANG="$MOLE_UNINSTALL_USER_LANG" mdls -name kMDItemDisplayName -raw "$app_path" 2>/dev/null || echo "")
elif [[ -n "$MOLE_UNINSTALL_USER_LANG" ]]; then
md_display_name=$(run_with_timeout 0.04 env LANG="$MOLE_UNINSTALL_USER_LANG" mdls -name kMDItemDisplayName -raw "$app_path" 2> /dev/null || echo "")
md_display_name=$(run_with_timeout 0.04 env LANG="$MOLE_UNINSTALL_USER_LANG" mdls -name kMDItemDisplayName -raw "$app_path" 2>/dev/null || echo "")
else
md_display_name=$(run_with_timeout 0.04 mdls -name kMDItemDisplayName -raw "$app_path" 2> /dev/null || echo "")
md_display_name=$(run_with_timeout 0.04 mdls -name kMDItemDisplayName -raw "$app_path" 2>/dev/null || echo "")
fi
local bundle_display_name
bundle_display_name=$(plutil -extract CFBundleDisplayName raw "$app_path/Contents/Info.plist" 2> /dev/null || echo "")
bundle_display_name=$(plutil -extract CFBundleDisplayName raw "$app_path/Contents/Info.plist" 2>/dev/null || echo "")
local bundle_name
bundle_name=$(plutil -extract CFBundleName raw "$app_path/Contents/Info.plist" 2> /dev/null || echo "")
bundle_name=$(plutil -extract CFBundleName raw "$app_path/Contents/Info.plist" 2>/dev/null || echo "")
if [[ "$md_display_name" == /* ]]; then
md_display_name=""
@@ -125,7 +125,7 @@ uninstall_acquire_metadata_lock() {
local lock_dir="$1"
local attempts=0
while ! mkdir "$lock_dir" 2> /dev/null; do
while ! mkdir "$lock_dir" 2>/dev/null; do
((attempts++))
if [[ $attempts -ge 40 ]]; then
return 1
@@ -140,12 +140,12 @@ uninstall_acquire_metadata_lock() {
local lock_age
lock_age=$(($(get_epoch_seconds) - lock_mtime))
if [[ "$lock_age" =~ ^-?[0-9]+$ && $lock_age -gt 300 ]]; then
rmdir "$lock_dir" 2> /dev/null || true
rmdir "$lock_dir" 2>/dev/null || true
fi
fi
fi
sleep 0.1 2> /dev/null || sleep 1
sleep 0.1 2>/dev/null || sleep 1
done
return 0
@@ -153,7 +153,7 @@ uninstall_acquire_metadata_lock() {
uninstall_release_metadata_lock() {
local lock_dir="$1"
[[ -d "$lock_dir" ]] && rmdir "$lock_dir" 2> /dev/null || true
[[ -d "$lock_dir" ]] && rmdir "$lock_dir" 2>/dev/null || true
}
uninstall_collect_inline_metadata() {
@@ -167,9 +167,9 @@ uninstall_collect_inline_metadata() {
local last_used_epoch=0
local metadata_date
metadata_date=$(run_with_timeout "$MOLE_UNINSTALL_INLINE_MDLS_TIMEOUT_SEC" mdls -name kMDItemLastUsedDate -raw "$app_path" 2> /dev/null || echo "")
metadata_date=$(run_with_timeout "$MOLE_UNINSTALL_INLINE_MDLS_TIMEOUT_SEC" mdls -name kMDItemLastUsedDate -raw "$app_path" 2>/dev/null || echo "")
if [[ "$metadata_date" != "(null)" && -n "$metadata_date" ]]; then
last_used_epoch=$(date -j -f "%Y-%m-%d %H:%M:%S %z" "$metadata_date" "+%s" 2> /dev/null || echo "0")
last_used_epoch=$(date -j -f "%Y-%m-%d %H:%M:%S %z" "$metadata_date" "+%s" 2>/dev/null || echo "0")
fi
# Fallback to app mtime so first scan does not show "...".
@@ -187,7 +187,7 @@ uninstall_collect_inline_metadata() {
start_uninstall_metadata_refresh() {
local refresh_file="$1"
[[ ! -s "$refresh_file" ]] && {
rm -f "$refresh_file" 2> /dev/null || true
rm -f "$refresh_file" 2>/dev/null || true
return 0
}
@@ -195,15 +195,15 @@ start_uninstall_metadata_refresh() {
_refresh_debug() {
if [[ "${MO_DEBUG:-}" == "1" ]]; then
local ts
ts=$(date "+%Y-%m-%d %H:%M:%S" 2> /dev/null || echo "?")
echo "[$ts] DEBUG: [metadata-refresh] $*" >> "${HOME}/.config/mole/mole_debug_session.log" 2> /dev/null || true
ts=$(date "+%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "?")
echo "[$ts] DEBUG: [metadata-refresh] $*" >>"${HOME}/.config/mole/mole_debug_session.log" 2>/dev/null || true
fi
}
ensure_user_dir "$MOLE_UNINSTALL_META_CACHE_DIR"
ensure_user_file "$MOLE_UNINSTALL_META_CACHE_FILE"
if [[ ! -r "$MOLE_UNINSTALL_META_CACHE_FILE" ]]; then
if ! : > "$MOLE_UNINSTALL_META_CACHE_FILE" 2> /dev/null; then
if ! : >"$MOLE_UNINSTALL_META_CACHE_FILE" 2>/dev/null; then
_refresh_debug "Cannot create cache file, aborting"
exit 0
fi
@@ -214,7 +214,7 @@ start_uninstall_metadata_refresh() {
fi
local updates_file
updates_file=$(mktemp 2> /dev/null) || {
updates_file=$(mktemp 2>/dev/null) || {
_refresh_debug "mktemp failed, aborting"
exit 0
}
@@ -238,9 +238,9 @@ start_uninstall_metadata_refresh() {
(
local last_used_epoch=0
local metadata_date
metadata_date=$(run_with_timeout 0.2 mdls -name kMDItemLastUsedDate -raw "$app_path" 2> /dev/null || echo "")
metadata_date=$(run_with_timeout 0.2 mdls -name kMDItemLastUsedDate -raw "$app_path" 2>/dev/null || echo "")
if [[ "$metadata_date" != "(null)" && -n "$metadata_date" ]]; then
last_used_epoch=$(date -j -f "%Y-%m-%d %H:%M:%S %z" "$metadata_date" "+%s" 2> /dev/null || echo "0")
last_used_epoch=$(date -j -f "%Y-%m-%d %H:%M:%S %z" "$metadata_date" "+%s" 2>/dev/null || echo "0")
fi
if [[ ! "$last_used_epoch" =~ ^[0-9]+$ || $last_used_epoch -le 0 ]]; then
@@ -251,25 +251,25 @@ start_uninstall_metadata_refresh() {
size_kb=$(get_path_size_kb "$app_path")
[[ "$size_kb" =~ ^[0-9]+$ ]] || size_kb=0
printf "%s|%s|%s|%s|%s|%s|%s\n" "$app_path" "${app_mtime:-0}" "$size_kb" "${last_used_epoch:-0}" "$now_epoch" "$bundle_id" "$display_name" > "$worker_output"
printf "%s|%s|%s|%s|%s|%s|%s\n" "$app_path" "${app_mtime:-0}" "$size_kb" "${last_used_epoch:-0}" "$now_epoch" "$bundle_id" "$display_name" >"$worker_output"
) &
worker_pids+=($!)
if ((${#worker_pids[@]} >= max_parallel)); then
wait "${worker_pids[0]}" 2> /dev/null || true
wait "${worker_pids[0]}" 2>/dev/null || true
worker_pids=("${worker_pids[@]:1}")
fi
done < "$refresh_file"
done <"$refresh_file"
local worker_pid
for worker_pid in "${worker_pids[@]}"; do
wait "$worker_pid" 2> /dev/null || true
wait "$worker_pid" 2>/dev/null || true
done
local worker_output
for worker_output in "${updates_file}".*; do
[[ -f "$worker_output" ]] || continue
cat "$worker_output" >> "$updates_file"
cat "$worker_output" >>"$updates_file"
rm -f "$worker_output"
done
@@ -285,7 +285,7 @@ start_uninstall_metadata_refresh() {
fi
local merged_file
merged_file=$(mktemp 2> /dev/null) || {
merged_file=$(mktemp 2>/dev/null) || {
_refresh_debug "mktemp for merge failed, aborting"
uninstall_release_metadata_lock "$MOLE_UNINSTALL_META_CACHE_LOCK"
rm -f "$updates_file"
@@ -300,17 +300,17 @@ start_uninstall_metadata_refresh() {
print updates[path]
}
}
' "$updates_file" "$MOLE_UNINSTALL_META_CACHE_FILE" > "$merged_file"
' "$updates_file" "$MOLE_UNINSTALL_META_CACHE_FILE" >"$merged_file"
mv "$merged_file" "$MOLE_UNINSTALL_META_CACHE_FILE" 2> /dev/null || {
cp "$merged_file" "$MOLE_UNINSTALL_META_CACHE_FILE" 2> /dev/null || true
mv "$merged_file" "$MOLE_UNINSTALL_META_CACHE_FILE" 2>/dev/null || {
cp "$merged_file" "$MOLE_UNINSTALL_META_CACHE_FILE" 2>/dev/null || true
rm -f "$merged_file"
}
uninstall_release_metadata_lock "$MOLE_UNINSTALL_META_CACHE_LOCK"
rm -f "$updates_file"
rm -f "$refresh_file" 2> /dev/null || true
) > /dev/null 2>&1 &
rm -f "$refresh_file" 2>/dev/null || true
) >/dev/null 2>&1 &
}
@@ -322,9 +322,9 @@ scan_applications() {
merged_file="${temp_file}.merged"
refresh_file="${temp_file}.refresh"
cache_snapshot_file="${temp_file}.cache"
: > "$scan_raw_file"
: > "$refresh_file"
: > "$cache_snapshot_file"
: >"$scan_raw_file"
: >"$refresh_file"
: >"$cache_snapshot_file"
ensure_user_dir "$MOLE_UNINSTALL_META_CACHE_DIR"
ensure_user_file "$MOLE_UNINSTALL_META_CACHE_FILE"
@@ -332,7 +332,7 @@ scan_applications() {
local cache_source_is_temp=false
if [[ ! -r "$cache_source" ]]; then
cache_source=$(create_temp_file)
: > "$cache_source"
: >"$cache_source"
cache_source_is_temp=true
fi
@@ -348,7 +348,7 @@ scan_applications() {
cache_mtimes+=("${cache_mtime:-0}")
cache_bundle_ids+=("${cache_bundle:-}")
cache_display_names+=("${cache_display:-}")
done < "$cache_source"
done <"$cache_source"
lookup_cached_identity() {
local target_path="$1"
@@ -385,13 +385,13 @@ scan_applications() {
# shellcheck disable=SC2329 # Function invoked indirectly via trap
trap_scan_cleanup() {
if [[ -n "$spinner_pid" ]]; then
kill -TERM "$spinner_pid" 2> /dev/null || true
wait "$spinner_pid" 2> /dev/null || true
kill -TERM "$spinner_pid" 2>/dev/null || true
wait "$spinner_pid" 2>/dev/null || true
fi
if [[ -f "$spinner_shown_file" ]]; then
printf "\r\033[K" >&2
fi
rm -f "$temp_file" "$scan_raw_file" "$merged_file" "$refresh_file" "$cache_snapshot_file" "${temp_file}.sorted" "${temp_file}.progress" "$spinner_shown_file" 2> /dev/null || true
rm -f "$temp_file" "$scan_raw_file" "$merged_file" "$refresh_file" "$cache_snapshot_file" "${temp_file}.sorted" "${temp_file}.progress" "$spinner_shown_file" 2>/dev/null || true
exit 130
}
trap trap_scan_cleanup INT
@@ -440,18 +440,18 @@ scan_applications() {
if [[ -L "$app_path" ]]; then
local link_target
link_target=$(readlink "$app_path" 2> /dev/null)
link_target=$(readlink "$app_path" 2>/dev/null)
if [[ -n "$link_target" ]]; then
local resolved_target="$link_target"
if [[ "$link_target" != /* ]]; then
local link_dir
link_dir=$(dirname "$app_path")
resolved_target=$(cd "$link_dir" 2> /dev/null && cd "$(dirname "$link_target")" 2> /dev/null && pwd)/$(basename "$link_target") 2> /dev/null || echo ""
resolved_target=$(cd "$link_dir" 2>/dev/null && cd "$(dirname "$link_target")" 2>/dev/null && pwd)/$(basename "$link_target") 2>/dev/null || echo ""
fi
case "$resolved_target" in
/System/* | /usr/bin/* | /usr/lib/* | /bin/* | /sbin/* | /private/etc/*)
continue
;;
/System/* | /usr/bin/* | /usr/lib/* | /bin/* | /sbin/* | /private/etc/*)
continue
;;
esac
fi
fi
@@ -461,16 +461,16 @@ scan_applications() {
local cached_identity cached_bundle_id cached_display_name
cached_identity=$(lookup_cached_identity "$app_path" "$app_mtime")
IFS='|' read -r cached_bundle_id cached_display_name <<< "$cached_identity"
IFS='|' read -r cached_bundle_id cached_display_name <<<"$cached_identity"
# Store tuple for pass 2 (bundle + display resolution, then cache merge).
app_data_tuples+=("${app_path}|${app_name}|${app_mtime}|${cached_bundle_id}|${cached_display_name}")
done < <(command find "$app_dir" -name "*.app" -maxdepth 3 -print0 2> /dev/null)
done < <(command find "$app_dir" -name "*.app" -maxdepth 3 -print0 2>/dev/null)
done
if [[ ${#app_data_tuples[@]} -eq 0 ]]; then
rm -f "$temp_file" "$scan_raw_file" "$merged_file" "$refresh_file" "$cache_snapshot_file" "${temp_file}.sorted" "${temp_file}.progress" "$spinner_shown_file" 2> /dev/null || true
[[ $cache_source_is_temp == true ]] && rm -f "$cache_source" 2> /dev/null || true
rm -f "$temp_file" "$scan_raw_file" "$merged_file" "$refresh_file" "$cache_snapshot_file" "${temp_file}.sorted" "${temp_file}.progress" "$spinner_shown_file" 2>/dev/null || true
[[ $cache_source_is_temp == true ]] && rm -f "$cache_source" 2>/dev/null || true
restore_scan_int_trap
printf "\r\033[K" >&2
echo "No applications found to uninstall." >&2
@@ -492,13 +492,13 @@ scan_applications() {
local app_data_tuple="$1"
local output_file="$2"
IFS='|' read -r app_path app_name app_mtime cached_bundle_id cached_display_name <<< "$app_data_tuple"
IFS='|' read -r app_path app_name app_mtime cached_bundle_id cached_display_name <<<"$app_data_tuple"
local bundle_id="${cached_bundle_id:-}"
if [[ -z "$bundle_id" ]]; then
bundle_id="unknown"
if [[ -f "$app_path/Contents/Info.plist" ]]; then
bundle_id=$(defaults read "$app_path/Contents/Info.plist" CFBundleIdentifier 2> /dev/null || echo "unknown")
bundle_id=$(defaults read "$app_path/Contents/Info.plist" CFBundleIdentifier 2>/dev/null || echo "unknown")
fi
fi
@@ -515,27 +515,27 @@ scan_applications() {
display_name="${display_name//|/-}"
display_name="${display_name//[$'\t\r\n']/}"
echo "${app_path}|${display_name}|${bundle_id}|${app_mtime}" >> "$output_file"
echo "${app_path}|${display_name}|${bundle_id}|${app_mtime}" >>"$output_file"
}
local progress_file="${temp_file}.progress"
echo "0" > "$progress_file"
echo "0" >"$progress_file"
(
# shellcheck disable=SC2329 # Function invoked indirectly via trap
cleanup_spinner() { exit 0; }
trap cleanup_spinner TERM INT EXIT
sleep "$MOLE_UNINSTALL_SCAN_SPINNER_DELAY_SEC" 2> /dev/null || sleep 1
sleep "$MOLE_UNINSTALL_SCAN_SPINNER_DELAY_SEC" 2>/dev/null || sleep 1
[[ -f "$progress_file" ]] || exit 0
local spinner_chars="|/-\\"
local i=0
: > "$spinner_shown_file"
: >"$spinner_shown_file"
while true; do
local completed=$(cat "$progress_file" 2> /dev/null || echo 0)
local completed=$(cat "$progress_file" 2>/dev/null || echo 0)
local c="${spinner_chars:$((i % 4)):1}"
printf "\r\033[K%s Scanning applications... %d/%d" "$c" "$completed" "$total_apps" >&2
((i++))
sleep 0.1 2> /dev/null || sleep 1
sleep 0.1 2>/dev/null || sleep 1
done
) &
spinner_pid=$!
@@ -544,21 +544,21 @@ scan_applications() {
((app_count++))
process_app_metadata "$app_data_tuple" "$scan_raw_file" &
pids+=($!)
echo "$app_count" > "$progress_file"
echo "$app_count" >"$progress_file"
if ((${#pids[@]} >= max_parallel)); then
wait "${pids[0]}" 2> /dev/null
wait "${pids[0]}" 2>/dev/null
pids=("${pids[@]:1}")
fi
done
for pid in "${pids[@]}"; do
wait "$pid" 2> /dev/null
wait "$pid" 2>/dev/null
done
if [[ -n "$spinner_pid" ]]; then
kill -TERM "$spinner_pid" 2> /dev/null || true
wait "$spinner_pid" 2> /dev/null || true
kill -TERM "$spinner_pid" 2>/dev/null || true
wait "$spinner_pid" 2>/dev/null || true
fi
if [[ -f "$spinner_shown_file" ]]; then
echo -ne "\r\033[K" >&2
@@ -567,8 +567,8 @@ scan_applications() {
if [[ ! -s "$scan_raw_file" ]]; then
echo "No applications found to uninstall" >&2
rm -f "$temp_file" "$scan_raw_file" "$merged_file" "$refresh_file" "$cache_snapshot_file" "${temp_file}.sorted" "${temp_file}.progress" "$spinner_shown_file" 2> /dev/null || true
[[ $cache_source_is_temp == true ]] && rm -f "$cache_source" 2> /dev/null || true
rm -f "$temp_file" "$scan_raw_file" "$merged_file" "$refresh_file" "$cache_snapshot_file" "${temp_file}.sorted" "${temp_file}.progress" "$spinner_shown_file" 2>/dev/null || true
[[ $cache_source_is_temp == true ]] && rm -f "$cache_source" 2>/dev/null || true
restore_scan_int_trap
return 1
fi
@@ -590,9 +590,9 @@ scan_applications() {
{
print $0 "|" cache_mtime[$1] "|" cache_size[$1] "|" cache_epoch[$1] "|" cache_updated[$1] "|" cache_bundle[$1] "|" cache_display[$1]
}
' "$cache_source" "$scan_raw_file" > "$merged_file"
' "$cache_source" "$scan_raw_file" >"$merged_file"
if [[ ! -s "$merged_file" && -s "$scan_raw_file" ]]; then
awk '{print $0 "||||||"}' "$scan_raw_file" > "$merged_file"
awk '{print $0 "||||||"}' "$scan_raw_file" >"$merged_file"
fi
local current_epoch
@@ -644,7 +644,7 @@ scan_applications() {
if [[ $inline_metadata_count -lt $MOLE_UNINSTALL_INLINE_METADATA_LIMIT ]]; then
local inline_metadata inline_size_kb inline_epoch inline_updated_epoch
inline_metadata=$(uninstall_collect_inline_metadata "$app_path" "${app_mtime:-0}" "$current_epoch")
IFS='|' read -r inline_size_kb inline_epoch inline_updated_epoch <<< "$inline_metadata"
IFS='|' read -r inline_size_kb inline_epoch inline_updated_epoch <<<"$inline_metadata"
((inline_metadata_count++))
if [[ "$inline_size_kb" =~ ^[0-9]+$ && $inline_size_kb -gt 0 ]]; then
@@ -659,36 +659,36 @@ scan_applications() {
cached_updated_epoch="$inline_updated_epoch"
fi
fi
printf "%s|%s|%s|%s\n" "$app_path" "${app_mtime:-0}" "$bundle_id" "$display_name" >> "$refresh_file"
printf "%s|%s|%s|%s\n" "$app_path" "${app_mtime:-0}" "$bundle_id" "$display_name" >>"$refresh_file"
fi
local persist_updated_epoch=0
if [[ "$cached_updated_epoch" =~ ^[0-9]+$ && $cached_updated_epoch -gt 0 ]]; then
persist_updated_epoch="$cached_updated_epoch"
fi
printf "%s|%s|%s|%s|%s|%s|%s\n" "$app_path" "${app_mtime:-0}" "${final_size_kb:-0}" "${final_epoch:-0}" "${persist_updated_epoch:-0}" "$bundle_id" "$display_name" >> "$cache_snapshot_file"
printf "%s|%s|%s|%s|%s|%s|%s\n" "$app_path" "${app_mtime:-0}" "${final_size_kb:-0}" "${final_epoch:-0}" "${persist_updated_epoch:-0}" "$bundle_id" "$display_name" >>"$cache_snapshot_file"
echo "${final_epoch}|${app_path}|${display_name}|${bundle_id}|${final_size}|${final_last_used}|${final_size_kb}" >> "$temp_file"
done < "$merged_file"
echo "${final_epoch}|${app_path}|${display_name}|${bundle_id}|${final_size}|${final_last_used}|${final_size_kb}" >>"$temp_file"
done <"$merged_file"
if [[ -s "$cache_snapshot_file" ]]; then
if uninstall_acquire_metadata_lock "$MOLE_UNINSTALL_META_CACHE_LOCK"; then
mv "$cache_snapshot_file" "$MOLE_UNINSTALL_META_CACHE_FILE" 2> /dev/null || {
cp "$cache_snapshot_file" "$MOLE_UNINSTALL_META_CACHE_FILE" 2> /dev/null || true
mv "$cache_snapshot_file" "$MOLE_UNINSTALL_META_CACHE_FILE" 2>/dev/null || {
cp "$cache_snapshot_file" "$MOLE_UNINSTALL_META_CACHE_FILE" 2>/dev/null || true
rm -f "$cache_snapshot_file"
}
uninstall_release_metadata_lock "$MOLE_UNINSTALL_META_CACHE_LOCK"
fi
fi
sort -t'|' -k1,1n "$temp_file" > "${temp_file}.sorted" || {
sort -t'|' -k1,1n "$temp_file" >"${temp_file}.sorted" || {
rm -f "$temp_file" "$scan_raw_file" "$merged_file" "$refresh_file" "$cache_snapshot_file"
[[ $cache_source_is_temp == true ]] && rm -f "$cache_source" 2> /dev/null || true
[[ $cache_source_is_temp == true ]] && rm -f "$cache_source" 2>/dev/null || true
restore_scan_int_trap
return 1
}
rm -f "$temp_file" "$scan_raw_file" "$merged_file" "$cache_snapshot_file"
[[ $cache_source_is_temp == true ]] && rm -f "$cache_source" 2> /dev/null || true
[[ $cache_source_is_temp == true ]] && rm -f "$cache_source" 2>/dev/null || true
[[ $total_apps -gt 50 ]] && printf "\r\033[K" >&2
@@ -720,7 +720,7 @@ load_applications() {
apps_data+=("$epoch|$app_path|$app_name|$bundle_id|$size|$last_used|${size_kb:-0}")
selection_state+=(false)
done < "$apps_file"
done <"$apps_file"
if [[ ${#apps_data[@]} -eq 0 ]]; then
log_warning "No applications available for uninstallation"
@@ -737,8 +737,8 @@ cleanup() {
unset MOLE_ALT_SCREEN_ACTIVE
fi
if [[ -n "${sudo_keepalive_pid:-}" ]]; then
kill "$sudo_keepalive_pid" 2> /dev/null || true
wait "$sudo_keepalive_pid" 2> /dev/null || true
kill "$sudo_keepalive_pid" 2>/dev/null || true
wait "$sudo_keepalive_pid" 2>/dev/null || true
sudo_keepalive_pid=""
fi
# Log session end
@@ -757,19 +757,13 @@ main() {
# Global flags
for arg in "$@"; do
case "$arg" in
"--help" | "-h")
echo "Usage: mo uninstall [OPTIONS]"
echo ""
echo "Interactively remove applications and their leftover files."
echo ""
echo "Options:"
echo " --debug Show detailed operation logs"
echo " -h, --help Show this help message"
exit 0
;;
"--debug")
export MO_DEBUG=1
;;
"--help" | "-h")
show_uninstall_help
exit 0
;;
"--debug")
export MO_DEBUG=1
;;
esac
done
@@ -821,7 +815,7 @@ main() {
local max_size_width=0
local max_last_width=0
for selected_app in "${selected_apps[@]}"; do
IFS='|' read -r _ _ app_name _ size last_used _ <<< "$selected_app"
IFS='|' read -r _ _ app_name _ size last_used _ <<<"$selected_app"
local name_width=$(get_display_width "$app_name")
[[ $name_width -gt $max_name_display_width ]] && max_name_display_width=$name_width
local size_display="$size"
@@ -835,7 +829,7 @@ main() {
((max_last_width < 5)) && max_last_width=5
((max_name_display_width < 16)) && max_name_display_width=16
local term_width=$(tput cols 2> /dev/null || echo 100)
local term_width=$(tput cols 2>/dev/null || echo 100)
local available_for_name=$((term_width - 17 - max_size_width - max_last_width))
local min_name_width=24
@@ -855,7 +849,7 @@ main() {
max_name_display_width=0
for selected_app in "${selected_apps[@]}"; do
IFS='|' read -r epoch app_path app_name bundle_id size last_used size_kb <<< "$selected_app"
IFS='|' read -r epoch app_path app_name bundle_id size last_used size_kb <<<"$selected_app"
local display_name
display_name=$(truncate_by_display_width "$app_name" "$name_trunc_limit")
@@ -880,7 +874,7 @@ main() {
local index=1
for row in "${summary_rows[@]}"; do
IFS='|' read -r name_cell size_cell last_cell <<< "$row"
IFS='|' read -r name_cell size_cell last_cell <<<"$row"
local name_display_width
name_display_width=$(get_display_width "$name_cell")
local name_char_count=${#name_cell}

View File

@@ -18,6 +18,7 @@ source "$_MOLE_CORE_DIR/log.sh"
source "$_MOLE_CORE_DIR/timeout.sh"
source "$_MOLE_CORE_DIR/file_ops.sh"
source "$_MOLE_CORE_DIR/help.sh"
source "$_MOLE_CORE_DIR/ui.sh"
source "$_MOLE_CORE_DIR/app_protection.sh"

61
lib/core/help.sh Normal file
View File

@@ -0,0 +1,61 @@
#!/bin/bash
show_clean_help() {
echo "Usage: mo clean [OPTIONS]"
echo ""
echo "Clean up disk space by removing caches, logs, and temporary files."
echo ""
echo "Options:"
echo " --dry-run, -n Preview cleanup without making changes"
echo " --whitelist Manage protected paths"
echo " --debug Show detailed operation logs"
echo " -h, --help Show this help message"
}
show_installer_help() {
echo "Usage: mo installer [OPTIONS]"
echo ""
echo "Find and remove installer files (.dmg, .pkg, .iso, .xip, .zip)."
echo ""
echo "Options:"
echo " --debug Show detailed operation logs"
echo " -h, --help Show this help message"
}
show_optimize_help() {
echo "Usage: mo optimize [OPTIONS]"
echo ""
echo "Check and maintain system health, apply optimizations."
echo ""
echo "Options:"
echo " --dry-run Preview optimization without making changes"
echo " --whitelist Manage protected items"
echo " --debug Show detailed operation logs"
echo " -h, --help Show this help message"
}
show_touchid_help() {
echo "Usage: mo touchid [COMMAND]"
echo ""
echo "Configure Touch ID for sudo authentication."
echo ""
echo "Commands:"
echo " enable Enable Touch ID for sudo"
echo " disable Disable Touch ID for sudo"
echo " status Show current Touch ID status"
echo ""
echo "Options:"
echo " -h, --help Show this help message"
echo ""
echo "If no command is provided, an interactive menu is shown."
}
show_uninstall_help() {
echo "Usage: mo uninstall [OPTIONS]"
echo ""
echo "Interactively remove applications and their leftover files."
echo ""
echo "Options:"
echo " --debug Show detailed operation logs"
echo " -h, --help Show this help message"
}