diff --git a/README.md b/README.md index 30d7c1c..c5d2ce0 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ mo clean --dry-run # Preview mode mo clean --whitelist # Manage protected caches mo uninstall # Uninstall apps mo analyze # Disk analyzer +mo touchid # Configure Touch ID for sudo mo update # Update Mole mo remove # Remove Mole from system mo --help # Show help @@ -148,16 +149,7 @@ Total: 156.8GB - **Can I protect specific caches?** Yes. Run `mo clean --whitelist` to select caches to protect. -- **Touch ID support?** Mole uses `sudo` for privileges, so you'll get a password prompt unless you've configured Touch ID for sudo. - - ```bash - sudo nano /etc/pam.d/sudo - - # Add this line below the comments at the top: - auth sufficient pam_tid.so - - # Save: Ctrl+O, then exit: Ctrl+X - ``` +- **Touch ID support?** Yes. Run `mo touchid` to enable Touch ID for sudo and avoid repeated password prompts. This is optional but recommended for better experience. ## Support diff --git a/bin/touchid.sh b/bin/touchid.sh new file mode 100755 index 0000000..9195199 --- /dev/null +++ b/bin/touchid.sh @@ -0,0 +1,238 @@ +#!/bin/bash +# Mole - Touch ID Configuration Helper +# Automatically configure Touch ID for sudo + +set -euo pipefail + +# Determine script location and source common functions +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" + +readonly PAM_SUDO_FILE="/etc/pam.d/sudo" +readonly PAM_TID_LINE="auth sufficient pam_tid.so" + +# Check if Touch ID is already configured +is_touchid_configured() { + if [[ ! -f "$PAM_SUDO_FILE" ]]; then + return 1 + fi + 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 + fi + + # Fallback: check if running on Apple Silicon or modern Intel Mac + local arch + arch=$(uname -m) + if [[ "$arch" == "arm64" ]]; then + return 0 + fi + + # 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) + if [[ -n "$model_year" ]] && [[ "$model_year" -ge 2018 ]]; then + return 0 + fi + + return 1 +} + +# Show current Touch ID status +show_status() { + if is_touchid_configured; then + echo -e "${GREEN}${ICON_SUCCESS}${NC} Touch ID is enabled for sudo" + else + echo -e "${YELLOW}○${NC} Touch ID is not configured for sudo" + fi +} + +# Enable Touch ID for sudo +enable_touchid() { + # First check if system supports Touch ID + if ! supports_touchid; then + log_warning "This Mac may not support Touch ID" + read -rp "Continue anyway? [y/N] " confirm + if [[ ! "$confirm" =~ ^[Yy]$ ]]; then + echo -e "${YELLOW}Cancelled${NC}" + return 1 + fi + echo "" + fi + + # Check if already configured + if is_touchid_configured; then + echo -e "${GREEN}${ICON_SUCCESS} Touch ID is already enabled${NC}" + return 0 + fi + + # Create backup and apply changes + if ! sudo cp "$PAM_SUDO_FILE" "${PAM_SUDO_FILE}.mole-backup" 2>/dev/null; then + log_error "Failed to create backup" + return 1 + fi + + # Create temp file with the modification + local temp_file + temp_file=$(mktemp) + + # Insert pam_tid.so after the first comment block + awk ' + BEGIN { inserted = 0 } + /^#/ { print; next } + !inserted && /^[^#]/ { + print "'"$PAM_TID_LINE"'" + inserted = 1 + } + { print } + ' "$PAM_SUDO_FILE" > "$temp_file" + + # Apply the changes + if sudo mv "$temp_file" "$PAM_SUDO_FILE" 2>/dev/null; then + echo -e "${GREEN}${ICON_SUCCESS} Touch ID enabled${NC} ${GRAY}- try: sudo ls${NC}" + echo "" + return 0 + else + rm -f "$temp_file" 2>/dev/null || true + log_error "Failed to enable Touch ID" + return 1 + fi +} + +# Disable Touch ID for sudo +disable_touchid() { + if ! is_touchid_configured; then + echo -e "${YELLOW}Touch ID is not currently enabled${NC}" + return 0 + fi + + # Create backup and remove configuration + if ! sudo cp "$PAM_SUDO_FILE" "${PAM_SUDO_FILE}.mole-backup" 2>/dev/null; then + log_error "Failed to create backup" + return 1 + fi + + # Remove pam_tid.so line + local temp_file + temp_file=$(mktemp) + 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} Touch ID disabled${NC}" + echo "" + return 0 + else + rm -f "$temp_file" 2>/dev/null || true + log_error "Failed to disable Touch ID" + return 1 + fi +} + +# Show help +show_help() { + cat << EOF +Usage: mo touchid [command] + +Configure Touch ID for sudo to avoid repeated password prompts. + +Commands: + enable Enable Touch ID for sudo + disable Disable Touch ID for sudo + status Show current Touch ID configuration + help Show this help message + +Examples: + mo touchid # Show status and options + mo touchid enable # Enable Touch ID + mo touchid status # Check current status + +Notes: + - Requires macOS with Touch ID sensor + - Changes are applied to /etc/pam.d/sudo + - Automatic backup is created before changes + - You can restore from backup at ${PAM_SUDO_FILE}.mole-backup + +EOF +} + +# Interactive menu +show_menu() { + echo "" + show_status + if is_touchid_configured; then + echo -ne "${PURPLE}☛${NC} Press ${GREEN}Enter${NC} to disable, ${GRAY}ESC${NC} to quit: " + IFS= read -r -s -n1 key || key="" + 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" + ;; + esac + else + echo -ne "${PURPLE}☛${NC} Press ${GREEN}Enter${NC} to enable, ${GRAY}ESC${NC} to quit: " + IFS= read -r -s -n1 key || key="" + + 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" + ;; + esac + fi +} + +# Main +main() { + local command="${1:-}" + + case "$command" in + enable) + enable_touchid + ;; + disable) + disable_touchid + ;; + status) + show_status + ;; + help|--help|-h) + show_help + ;; + "") + show_menu + ;; + *) + log_error "Unknown command: $command" + echo "" + show_help + exit 1 + ;; + esac +} + +main "$@" diff --git a/mole b/mole index f13b1cd..fdd0087 100755 --- a/mole +++ b/mole @@ -22,7 +22,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/lib/common.sh" # Version info -VERSION="1.7.5" +VERSION="1.7.6" MOLE_TAGLINE="can dig deep to clean your Mac." # Get latest version from remote repository @@ -122,7 +122,7 @@ EOF } show_version() { - printf 'Mole version %s\n' "$VERSION" + printf '\nMole version %s\n\n' "$VERSION" } show_help() { @@ -135,6 +135,7 @@ show_help() { printf " %s%-28s%s %s\n" "$GREEN" "mo clean --whitelist" "$NC" "Manage protected caches" printf " %s%-28s%s %s\n" "$GREEN" "mo uninstall" "$NC" "Remove applications completely" printf " %s%-28s%s %s\n" "$GREEN" "mo analyze" "$NC" "Interactive disk space explorer" + 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 Mole to the latest version" printf " %s%-28s%s %s\n" "$GREEN" "mo remove" "$NC" "Remove Mole from the system" printf " %s%-28s%s %s\n" "$GREEN" "mo --version" "$NC" "Show installed version" @@ -161,7 +162,9 @@ update_mole() { fi if [[ "$VERSION" == "$latest" ]]; then - echo -e "${GREEN}✓${NC} Mole is already up to date (${VERSION})" + echo "" + echo -e "${GREEN}✓${NC} Already on latest version (${VERSION})" + echo "" exit 0 fi @@ -249,11 +252,6 @@ update_mole() { # Remove Mole from system remove_mole() { - clear - echo "" - echo -e "${YELLOW}Remove Mole${NC}" - echo "" - # Detect all installations with loading if [[ -t 1 ]]; then start_inline_spinner "Detecting Mole installations..." @@ -302,100 +300,84 @@ remove_mole() { stop_inline_spinner fi + printf '\n' + # Check if anything to remove if [[ "$is_homebrew" == "false" && ${#manual_installs[@]:-0} -eq 0 && ${#alias_installs[@]:-0} -eq 0 ]]; then - echo "" - echo -e "${YELLOW}No Mole installation detected${NC}" + printf '%s\n\n' "${YELLOW}No Mole installation detected${NC}" exit 0 fi # Show what will be removed - echo "Will remove:" - echo "" - + 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 "" - echo -n "Press Enter to confirm, ESC or q to cancel: " + echo -ne "${PURPLE}☛${NC} Press ${GREEN}Enter${NC} to confirm, ${GRAY}ESC${NC} to cancel: " # Read single key IFS= read -r -s -n1 key || key="" - echo "" case "$key" in - $'\e'|q|Q) - echo "Cancelled" + $'\e') + echo -e "${GRAY}Cancelled${NC}" + echo "" exit 0 ;; ""|$'\n'|$'\r') + printf "\r\033[K" # Clear the prompt line # Continue with removal ;; *) - echo "Cancelled" + echo -e "${GRAY}Cancelled${NC}" + echo "" exit 0 ;; esac - echo "" - - # Remove Homebrew installation + # Remove Homebrew installation (silent) + local has_error=false if [[ "$is_homebrew" == "true" ]]; then - if brew uninstall mole 2>&1 | grep -q "Uninstalling"; then - log_success "Uninstalled via Homebrew" - else - log_error "Failed to uninstall via Homebrew" - echo "Try manually: brew uninstall mole" + if ! brew uninstall mole >/dev/null 2>&1; then + has_error=true fi fi - - # Remove manual installations + # Remove manual installations (silent) if [[ ${#manual_installs[@]:-0} -gt 0 ]]; then for install in "${manual_installs[@]}"; do if [[ -f "$install" ]]; then - if rm -f "$install" 2>/dev/null; then - log_success "Removed: $install" - else - log_error "Failed to remove $install (try with sudo)" - fi + rm -f "$install" 2>/dev/null || has_error=true fi done fi - if [[ ${#alias_installs[@]} -gt 0 ]]; then for alias in "${alias_installs[@]}"; do if [[ -f "$alias" ]]; then - if rm -f "$alias" 2>/dev/null; then - log_success "Removed alias: $alias" - else - log_warning "Could not remove alias $alias (may need sudo)" - fi + rm -f "$alias" 2>/dev/null || true fi done fi - - # Clean up cache first (logs to config) + # Clean up cache first (silent) if [[ -d "$HOME/.cache/mole" ]]; then - rm -rf "$HOME/.cache/mole" 2>/dev/null && log_success "Removed cache" + rm -rf "$HOME/.cache/mole" 2>/dev/null || true fi - - # Clean up configuration last (contains logs) + # Clean up configuration last (silent) if [[ -d "$HOME/.config/mole" ]]; then - log_success "Removed configuration" - rm -rf "$HOME/.config/mole" 2>/dev/null + rm -rf "$HOME/.config/mole" 2>/dev/null || true fi - echo "" - echo -e "${GREEN}Mole has been removed successfully${NC}" - echo "" - echo "Thank you for using Mole!" + # Show final result + local final_message + if [[ "$has_error" == "true" ]]; then + final_message="${YELLOW}⚠ Mole uninstalled with some errors, thank you for using Mole!${NC}" + else + final_message="${GREEN}✓ Mole uninstalled successfully, thank you for using Mole!${NC}" + fi + printf '\n%s\n\n' "$final_message" exit 0 } @@ -443,6 +425,7 @@ show_main_menu() { if [[ -t 0 ]]; then printf '\r\033[2K\n' printf '\r\033[2K%s\n' " ${GRAY}↑/↓${NC} Navigate ${GRAY}|${NC} ${GRAY}Enter${NC} Select ${GRAY}|${NC} ${GRAY}Q/ESC${NC} Quit" + printf '\r\033[2K\n' fi # Clear any remaining content below without full screen wipe @@ -478,8 +461,6 @@ interactive_main_menu() { cleanup_and_exit() { show_cursor - echo "" - echo "Thank you for using Mole!" exit 0 } @@ -541,6 +522,9 @@ main() { "analyze") exec "$SCRIPT_DIR/bin/analyze.sh" "${@:2}" ;; + "touchid") + exec "$SCRIPT_DIR/bin/touchid.sh" "${@:2}" + ;; "update") update_mole exit 0