From 4bd4ffc7be222ea136c77211ce6f7de03c43086d Mon Sep 17 00:00:00 2001 From: Tw93 Date: Mon, 1 Dec 2025 16:58:35 +0800 Subject: [PATCH] Reconstruct clean lib code --- bin/check.sh | 13 +- bin/clean.sh | 24 +- bin/optimize.sh | 16 +- bin/touchid.sh | 4 +- bin/uninstall.sh | 12 +- install.sh | 8 +- lib/check/all.sh | 656 ++++++++++++++++++ lib/check_config.sh | 58 -- lib/check_health.sh | 222 ------ lib/check_security.sh | 66 -- lib/check_updates.sh | 303 -------- .../app_caches.sh} | 0 lib/{clean_apps.sh => clean/apps.sh} | 0 lib/{clean_brew.sh => clean/brew.sh} | 0 lib/{clean_caches.sh => clean/caches.sh} | 0 lib/{clean_dev.sh => clean/dev.sh} | 0 .../maintenance.sh} | 0 lib/{clean_system.sh => clean/system.sh} | 0 lib/{clean_user_data.sh => clean/user.sh} | 0 lib/{ => core}/common.sh | 0 lib/{sudo_manager.sh => core/sudo.sh} | 0 lib/{autofix_manager.sh => manage/autofix.sh} | 0 lib/{update_manager.sh => manage/update.sh} | 0 .../whitelist.sh} | 6 +- .../tasks.sh} | 279 ++++++++ lib/optimize_health.sh | 279 -------- .../app_selector.sh} | 0 lib/{ => ui}/menu_paginated.sh | 0 lib/{ => ui}/menu_simple.sh | 0 lib/{uninstall_batch.sh => uninstall.sh} | 4 +- mole | 4 +- scripts/check.sh | 6 +- tests/clean_caches.bats | 30 +- tests/cli.bats | 3 + tests/common.bats | 32 +- tests/optimization_tasks.bats | 40 +- tests/safe_functions.bats | 42 +- tests/scripts.bats | 14 +- tests/sudo_manager.bats | 16 +- tests/uninstall.bats | 24 +- tests/update_manager.bats | 26 +- tests/update_remove.bats | 2 +- tests/whitelist.bats | 14 +- 43 files changed, 1105 insertions(+), 1098 deletions(-) create mode 100644 lib/check/all.sh delete mode 100644 lib/check_config.sh delete mode 100644 lib/check_health.sh delete mode 100644 lib/check_security.sh delete mode 100644 lib/check_updates.sh rename lib/{clean_user_apps.sh => clean/app_caches.sh} (100%) rename lib/{clean_apps.sh => clean/apps.sh} (100%) rename lib/{clean_brew.sh => clean/brew.sh} (100%) rename lib/{clean_caches.sh => clean/caches.sh} (100%) rename lib/{clean_dev.sh => clean/dev.sh} (100%) rename lib/{clean_maintenance.sh => clean/maintenance.sh} (100%) rename lib/{clean_system.sh => clean/system.sh} (100%) rename lib/{clean_user_data.sh => clean/user.sh} (100%) rename lib/{ => core}/common.sh (100%) rename lib/{sudo_manager.sh => core/sudo.sh} (100%) rename lib/{autofix_manager.sh => manage/autofix.sh} (100%) rename lib/{update_manager.sh => manage/update.sh} (100%) rename lib/{whitelist_manager.sh => manage/whitelist.sh} (98%) rename lib/{optimization_tasks.sh => optimize/tasks.sh} (64%) delete mode 100755 lib/optimize_health.sh rename lib/{ui_app_selector.sh => ui/app_selector.sh} (100%) rename lib/{ => ui}/menu_paginated.sh (100%) rename lib/{ => ui}/menu_simple.sh (100%) rename lib/{uninstall_batch.sh => uninstall.sh} (99%) diff --git a/bin/check.sh b/bin/check.sh index abd9141..73deeef 100755 --- a/bin/check.sh +++ b/bin/check.sh @@ -4,15 +4,12 @@ set -euo pipefail # Load common functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -source "$SCRIPT_DIR/lib/common.sh" -source "$SCRIPT_DIR/lib/sudo_manager.sh" -source "$SCRIPT_DIR/lib/update_manager.sh" -source "$SCRIPT_DIR/lib/autofix_manager.sh" +source "$SCRIPT_DIR/lib/core/common.sh" +source "$SCRIPT_DIR/lib/core/sudo.sh" +source "$SCRIPT_DIR/lib/manage/update.sh" +source "$SCRIPT_DIR/lib/manage/autofix.sh" -source "$SCRIPT_DIR/lib/check_updates.sh" -source "$SCRIPT_DIR/lib/check_health.sh" -source "$SCRIPT_DIR/lib/check_security.sh" -source "$SCRIPT_DIR/lib/check_config.sh" +source "$SCRIPT_DIR/lib/check/all.sh" cleanup_all() { stop_sudo_session diff --git a/bin/clean.sh b/bin/clean.sh index 2972a26..e19ae2b 100755 --- a/bin/clean.sh +++ b/bin/clean.sh @@ -10,16 +10,16 @@ export LANG=C # Get script directory and source common functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "$SCRIPT_DIR/../lib/common.sh" -source "$SCRIPT_DIR/../lib/sudo_manager.sh" -source "$SCRIPT_DIR/../lib/clean_brew.sh" -source "$SCRIPT_DIR/../lib/clean_caches.sh" -source "$SCRIPT_DIR/../lib/clean_apps.sh" -source "$SCRIPT_DIR/../lib/clean_dev.sh" -source "$SCRIPT_DIR/../lib/clean_user_apps.sh" -source "$SCRIPT_DIR/../lib/clean_system.sh" -source "$SCRIPT_DIR/../lib/clean_user_data.sh" -source "$SCRIPT_DIR/../lib/clean_maintenance.sh" +source "$SCRIPT_DIR/../lib/core/common.sh" +source "$SCRIPT_DIR/../lib/core/sudo.sh" +source "$SCRIPT_DIR/../lib/clean/brew.sh" +source "$SCRIPT_DIR/../lib/clean/caches.sh" +source "$SCRIPT_DIR/../lib/clean/apps.sh" +source "$SCRIPT_DIR/../lib/clean/dev.sh" +source "$SCRIPT_DIR/../lib/clean/app_caches.sh" +source "$SCRIPT_DIR/../lib/clean/system.sh" +source "$SCRIPT_DIR/../lib/clean/user.sh" +source "$SCRIPT_DIR/../lib/clean/maintenance.sh" # Configuration SYSTEM_CLEAN=false @@ -35,7 +35,7 @@ readonly PROTECTED_SW_DOMAINS=( ) # Whitelist patterns (loaded from common.sh) -# FINDER_METADATA_SENTINEL and DEFAULT_WHITELIST_PATTERNS defined in lib/common.sh +# FINDER_METADATA_SENTINEL and DEFAULT_WHITELIST_PATTERNS defined in lib/core/common.sh declare -a WHITELIST_PATTERNS=() WHITELIST_WARNINGS=() @@ -846,7 +846,7 @@ main() { DRY_RUN=true ;; "--whitelist") - source "$SCRIPT_DIR/../lib/whitelist_manager.sh" + source "$SCRIPT_DIR/../lib/manage/whitelist.sh" manage_whitelist exit 0 ;; diff --git a/bin/optimize.sh b/bin/optimize.sh index cb09fcc..ab8a312 100755 --- a/bin/optimize.sh +++ b/bin/optimize.sh @@ -4,18 +4,14 @@ set -euo pipefail # Load common functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -source "$SCRIPT_DIR/lib/common.sh" -source "$SCRIPT_DIR/lib/optimize_health.sh" -source "$SCRIPT_DIR/lib/sudo_manager.sh" -source "$SCRIPT_DIR/lib/update_manager.sh" -source "$SCRIPT_DIR/lib/autofix_manager.sh" -source "$SCRIPT_DIR/lib/optimization_tasks.sh" +source "$SCRIPT_DIR/lib/core/common.sh" +source "$SCRIPT_DIR/lib/core/sudo.sh" +source "$SCRIPT_DIR/lib/manage/update.sh" +source "$SCRIPT_DIR/lib/manage/autofix.sh" +source "$SCRIPT_DIR/lib/optimize/tasks.sh" # Load check modules -source "$SCRIPT_DIR/lib/check_updates.sh" -source "$SCRIPT_DIR/lib/check_health.sh" -source "$SCRIPT_DIR/lib/check_security.sh" -source "$SCRIPT_DIR/lib/check_config.sh" +source "$SCRIPT_DIR/lib/check/all.sh" # Colors and icons from common.sh diff --git a/bin/touchid.sh b/bin/touchid.sh index e6c19e9..421dfa9 100755 --- a/bin/touchid.sh +++ b/bin/touchid.sh @@ -9,8 +9,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LIB_DIR="$(cd "$SCRIPT_DIR/../lib" && pwd)" # Source common functions -# shellcheck source=../lib/common.sh -source "$LIB_DIR/common.sh" +# shellcheck source=../lib/core/common.sh +source "$LIB_DIR/core/common.sh" readonly PAM_SUDO_FILE="/etc/pam.d/sudo" readonly PAM_TID_LINE="auth sufficient pam_tid.so" diff --git a/bin/uninstall.sh b/bin/uninstall.sh index 1b0f488..f38d850 100755 --- a/bin/uninstall.sh +++ b/bin/uninstall.sh @@ -14,12 +14,12 @@ export LANG=C # Get script directory and source common functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "$SCRIPT_DIR/../lib/common.sh" -source "$SCRIPT_DIR/../lib/menu_paginated.sh" -source "$SCRIPT_DIR/../lib/ui_app_selector.sh" -source "$SCRIPT_DIR/../lib/uninstall_batch.sh" +source "$SCRIPT_DIR/../lib/core/common.sh" +source "$SCRIPT_DIR/../lib/ui/menu_paginated.sh" +source "$SCRIPT_DIR/../lib/ui/app_selector.sh" +source "$SCRIPT_DIR/../lib/uninstall.sh" -# Note: Bundle preservation logic is now in lib/common.sh +# Note: Bundle preservation logic is now in lib/core/common.sh # Initialize global variables selected_apps=() # Global array for app selection @@ -404,7 +404,7 @@ load_applications() { # Read a single key with proper escape sequence handling # This function has been replaced by the menu.sh library -# Note: App file discovery and size calculation functions moved to lib/common.sh +# Note: App file discovery and size calculation functions moved to lib/core/common.sh # Use find_app_files() and calculate_total_size() from common.sh # Uninstall selected applications diff --git a/install.sh b/install.sh index ef1bb54..c0de028 100755 --- a/install.sh +++ b/install.sh @@ -39,7 +39,7 @@ fi; } # Verbosity (0 = quiet, 1 = verbose) VERBOSE=1 -# Icons (duplicated from lib/common.sh - necessary as install.sh runs standalone) +# Icons (duplicated from lib/core/common.sh - necessary as install.sh runs standalone) readonly ICON_SUCCESS="✓" readonly ICON_ADMIN="●" readonly ICON_CONFIRM="◎" @@ -349,7 +349,7 @@ install_files() { # Verify installation verify_installation() { - if [[ -x "$INSTALL_DIR/mole" ]] && [[ -f "$CONFIG_DIR/lib/common.sh" ]]; then + if [[ -x "$INSTALL_DIR/mole" ]] && [[ -f "$CONFIG_DIR/lib/core/common.sh" ]]; then # Test if mole command works if "$INSTALL_DIR/mole" --help > /dev/null 2>&1; then @@ -522,9 +522,9 @@ perform_update() { if command -v brew > /dev/null 2>&1 && brew list mole > /dev/null 2>&1; then # Try to use shared function if available (when running from installed Mole) resolve_source_dir 2> /dev/null || true - if [[ -f "$SOURCE_DIR/lib/common.sh" ]]; then + if [[ -f "$SOURCE_DIR/lib/core/common.sh" ]]; then # shellcheck disable=SC1090,SC1091 - source "$SOURCE_DIR/lib/common.sh" + source "$SOURCE_DIR/lib/core/common.sh" update_via_homebrew "$VERSION" else # Fallback: inline implementation diff --git a/lib/check/all.sh b/lib/check/all.sh new file mode 100644 index 0000000..7afbd4d --- /dev/null +++ b/lib/check/all.sh @@ -0,0 +1,656 @@ +#!/bin/bash +# System Checks Module +# Combines configuration, security, updates, and health checks + +set -euo pipefail + +# ============================================================================ +# Configuration Checks +# ============================================================================ + +check_touchid_sudo() { + # Check if Touch ID is configured for sudo + local pam_file="/etc/pam.d/sudo" + if [[ -f "$pam_file" ]] && grep -q "pam_tid.so" "$pam_file" 2> /dev/null; then + echo -e " ${GREEN}✓${NC} Touch ID Enabled for sudo" + else + # Check if Touch ID is supported + local is_supported=false + if command -v bioutil > /dev/null 2>&1; then + if bioutil -r 2> /dev/null | grep -q "Touch ID"; then + is_supported=true + fi + elif [[ "$(uname -m)" == "arm64" ]]; then + is_supported=true + fi + + if [[ "$is_supported" == "true" ]]; then + echo -e " ${YELLOW}${ICON_WARNING}${NC} Touch ID ${YELLOW}Not configured${NC} for sudo" + export TOUCHID_NOT_CONFIGURED=true + fi + fi +} + +check_rosetta() { + # Check Rosetta 2 (for Apple Silicon Macs) + if [[ "$(uname -m)" == "arm64" ]]; then + if [[ -f "/Library/Apple/usr/share/rosetta/rosetta" ]]; then + echo -e " ${GREEN}✓${NC} Rosetta 2 Installed" + else + echo -e " ${YELLOW}${ICON_WARNING}${NC} Rosetta 2 ${YELLOW}Not installed${NC}" + export ROSETTA_NOT_INSTALLED=true + fi + fi +} + +check_git_config() { + # Check basic Git configuration + if command -v git > /dev/null 2>&1; then + local git_name=$(git config --global user.name 2> /dev/null || echo "") + local git_email=$(git config --global user.email 2> /dev/null || echo "") + + if [[ -n "$git_name" && -n "$git_email" ]]; then + echo -e " ${GREEN}✓${NC} Git Config Configured" + else + echo -e " ${YELLOW}${ICON_WARNING}${NC} Git Config ${YELLOW}Not configured${NC}" + fi + fi +} + +check_all_config() { + check_touchid_sudo + check_rosetta + check_git_config +} + +# ============================================================================ +# Security Checks +# ============================================================================ + +check_filevault() { + # Check FileVault encryption status + if command -v fdesetup > /dev/null 2>&1; then + local fv_status=$(fdesetup status 2> /dev/null || echo "") + if echo "$fv_status" | grep -q "FileVault is On"; then + echo -e " ${GREEN}✓${NC} FileVault Enabled" + else + echo -e " ${RED}✗${NC} FileVault ${RED}Disabled${NC} (Recommend enabling)" + export FILEVAULT_DISABLED=true + fi + fi +} + +check_firewall() { + # Check firewall status + unset FIREWALL_DISABLED + local firewall_status=$(defaults read /Library/Preferences/com.apple.alf globalstate 2> /dev/null || echo "0") + if [[ "$firewall_status" == "1" || "$firewall_status" == "2" ]]; then + echo -e " ${GREEN}✓${NC} Firewall Enabled" + else + echo -e " ${YELLOW}${ICON_WARNING}${NC} Firewall ${YELLOW}Disabled${NC} (Consider enabling)" + echo -e " ${GRAY}System Settings → Network → Firewall, or run:${NC}" + echo -e " ${GRAY}sudo defaults write /Library/Preferences/com.apple.alf globalstate -int 1${NC}" + export FIREWALL_DISABLED=true + fi +} + +check_gatekeeper() { + # Check Gatekeeper status + if command -v spctl > /dev/null 2>&1; then + local gk_status=$(spctl --status 2> /dev/null || echo "") + if echo "$gk_status" | grep -q "enabled"; then + echo -e " ${GREEN}✓${NC} Gatekeeper Active" + unset GATEKEEPER_DISABLED + else + echo -e " ${YELLOW}${ICON_WARNING}${NC} Gatekeeper ${YELLOW}Disabled${NC}" + echo -e " ${GRAY}Enable via System Settings → Privacy & Security, or:${NC}" + echo -e " ${GRAY}sudo spctl --master-enable${NC}" + export GATEKEEPER_DISABLED=true + fi + fi +} + +check_sip() { + # Check System Integrity Protection + if command -v csrutil > /dev/null 2>&1; then + local sip_status=$(csrutil status 2> /dev/null || echo "") + if echo "$sip_status" | grep -q "enabled"; then + echo -e " ${GREEN}✓${NC} SIP Enabled" + else + echo -e " ${YELLOW}${ICON_WARNING}${NC} SIP ${YELLOW}Disabled${NC}" + echo -e " ${GRAY}Restart into Recovery → Utilities → Terminal → run: csrutil enable${NC}" + fi + fi +} + +check_all_security() { + check_filevault + check_firewall + check_gatekeeper + check_sip +} + +# ============================================================================ +# Software Update Checks +# ============================================================================ + +# Cache configuration +CACHE_DIR="${HOME}/.cache/mole" +CACHE_TTL=600 # 10 minutes in seconds + +# Ensure cache directory exists +mkdir -p "$CACHE_DIR" 2> /dev/null || true + +clear_cache_file() { + local file="$1" + rm -f "$file" 2> /dev/null || true +} + +reset_brew_cache() { + clear_cache_file "$CACHE_DIR/brew_updates" +} + +reset_softwareupdate_cache() { + clear_cache_file "$CACHE_DIR/softwareupdate_list" + SOFTWARE_UPDATE_LIST="" +} + +reset_mole_cache() { + clear_cache_file "$CACHE_DIR/mole_version" +} + +# Check if cache is still valid +is_cache_valid() { + local cache_file="$1" + local ttl="${2:-$CACHE_TTL}" + + if [[ ! -f "$cache_file" ]]; then + return 1 + fi + + local cache_age=$(($(date +%s) - $(get_file_mtime "$cache_file"))) + [[ $cache_age -lt $ttl ]] +} + +check_homebrew_updates() { + if ! command -v brew > /dev/null 2>&1; then + return + fi + + local cache_file="$CACHE_DIR/brew_updates" + local formula_count=0 + local cask_count=0 + + if is_cache_valid "$cache_file"; then + read -r formula_count cask_count < "$cache_file" 2> /dev/null || true + formula_count=${formula_count:-0} + cask_count=${cask_count:-0} + else + # Show spinner while checking + if [[ -t 1 ]]; then + start_inline_spinner "Checking Homebrew..." + fi + + local outdated_list="" + outdated_list=$(brew outdated --quiet 2> /dev/null || echo "") + if [[ -n "$outdated_list" ]]; then + formula_count=$(echo "$outdated_list" | wc -l | tr -d ' ') + fi + + local cask_list="" + cask_list=$(brew outdated --cask --quiet 2> /dev/null || echo "") + if [[ -n "$cask_list" ]]; then + cask_count=$(echo "$cask_list" | wc -l | tr -d ' ') + fi + + echo "$formula_count $cask_count" > "$cache_file" 2> /dev/null || true + + # Stop spinner before output + if [[ -t 1 ]]; then + stop_inline_spinner + fi + fi + + local total_count=$((formula_count + cask_count)) + export BREW_FORMULA_OUTDATED_COUNT=$formula_count + export BREW_CASK_OUTDATED_COUNT=$cask_count + export BREW_OUTDATED_COUNT=$total_count + + if [[ $total_count -gt 0 ]]; then + local breakdown="" + if [[ $formula_count -gt 0 && $cask_count -gt 0 ]]; then + breakdown=" (${formula_count} formula, ${cask_count} cask)" + elif [[ $formula_count -gt 0 ]]; then + breakdown=" (${formula_count} formula)" + elif [[ $cask_count -gt 0 ]]; then + breakdown=" (${cask_count} cask)" + fi + echo -e " ${YELLOW}${ICON_WARNING}${NC} Homebrew ${YELLOW}${total_count} updates${NC}${breakdown}" + echo -e " ${GRAY}Run: ${GREEN}brew upgrade${NC} ${GRAY}and/or${NC} ${GREEN}brew upgrade --cask${NC}" + else + echo -e " ${GREEN}✓${NC} Homebrew Up to date" + fi +} + +# Cache software update list to avoid calling softwareupdate twice +SOFTWARE_UPDATE_LIST="" + +get_software_updates() { + local cache_file="$CACHE_DIR/softwareupdate_list" + + if [[ -z "$SOFTWARE_UPDATE_LIST" ]]; then + # Check cache first + if is_cache_valid "$cache_file"; then + SOFTWARE_UPDATE_LIST=$(cat "$cache_file" 2> /dev/null || echo "") + else + # Show spinner while checking (only on first call) + local show_spinner=false + if [[ -t 1 && -z "${SOFTWAREUPDATE_SPINNER_SHOWN:-}" ]]; then + start_inline_spinner "Checking system updates..." + show_spinner=true + export SOFTWAREUPDATE_SPINNER_SHOWN="true" + fi + + SOFTWARE_UPDATE_LIST=$(softwareupdate -l 2> /dev/null || echo "") + # Save to cache + echo "$SOFTWARE_UPDATE_LIST" > "$cache_file" 2> /dev/null || true + + # Stop spinner + if [[ "$show_spinner" == "true" ]]; then + stop_inline_spinner + fi + fi + fi + echo "$SOFTWARE_UPDATE_LIST" +} + +check_appstore_updates() { + local spinner_started=false + if [[ -t 1 ]]; then + printf " Checking App Store updates...\r" + start_inline_spinner "Checking App Store updates..." + spinner_started=true + export SOFTWAREUPDATE_SPINNER_SHOWN="external" + else + echo "Checking App Store updates..." + fi + + local update_list="" + update_list=$(get_software_updates | grep -v "Software Update Tool" | grep "^\*" | grep -vi "macOS" || echo "") + + if [[ "$spinner_started" == "true" ]]; then + stop_inline_spinner + unset SOFTWAREUPDATE_SPINNER_SHOWN + fi + + local update_count=0 + if [[ -n "$update_list" ]]; then + update_count=$(echo "$update_list" | wc -l | tr -d ' ') + fi + + export APPSTORE_UPDATE_COUNT=$update_count + + if [[ $update_count -gt 0 ]]; then + echo -e " ${YELLOW}${ICON_WARNING}${NC} App Store ${YELLOW}${update_count} apps${NC} need update" + echo -e " ${GRAY}Run: ${GREEN}softwareupdate -i