diff --git a/bin/optimize.sh b/bin/optimize.sh index c5c9d80..2bddc15 100755 --- a/bin/optimize.sh +++ b/bin/optimize.sh @@ -27,6 +27,7 @@ print_header() { # System check functions (real-time display) run_system_checks() { + unset AUTO_FIX_SUMMARY AUTO_FIX_DETAILS echo "" echo -e "${PURPLE}System Check${NC}" echo "" @@ -44,6 +45,9 @@ run_system_checks() { # Check security - real-time display echo -e "${BLUE}${ICON_ARROW}${NC} Security posture" check_all_security + if ask_for_security_fixes; then + perform_security_fixes + fi echo "" # Check configuration - real-time display @@ -67,30 +71,32 @@ run_system_checks() { } show_optimization_summary() { - if [[ -z "${OPTIMIZE_SAFE_COUNT:-}" ]]; then + local safe_count="${OPTIMIZE_SAFE_COUNT:-0}" + local confirm_count="${OPTIMIZE_CONFIRM_COUNT:-0}" + if (( safe_count == 0 && confirm_count == 0 )) && [[ -z "${AUTO_FIX_SUMMARY:-}" ]]; then return fi - echo "" local summary_title="Optimization and Check Complete" local -a summary_details=() # Optimization results - if ((OPTIMIZE_SAFE_COUNT > 0)); then - summary_details+=("Applied ${GREEN}${OPTIMIZE_SAFE_COUNT}${NC} optimizations") + summary_details+=("Optimizations: ${GREEN}${safe_count}${NC} applied, ${YELLOW}${confirm_count}${NC} manual checks") + summary_details+=("Caches refreshed; services restarted; system tuned") + summary_details+=("Updates & security reviewed across system") + + local summary_line4="" + if [[ -n "${AUTO_FIX_SUMMARY:-}" ]]; then + summary_line4="${AUTO_FIX_SUMMARY}" + if [[ -n "${AUTO_FIX_DETAILS:-}" ]]; then + local detail_join + detail_join=$(echo "${AUTO_FIX_DETAILS}" | paste -sd ", " -) + [[ -n "$detail_join" ]] && summary_line4+=" — ${detail_join}" + fi else - summary_details+=("System already optimized") + summary_line4="Mac should feel faster and more responsive" fi - - if ((OPTIMIZE_CONFIRM_COUNT > 0)); then - summary_details+=("${YELLOW}${OPTIMIZE_CONFIRM_COUNT}${NC} manual checks suggested") - fi - - summary_details+=("Caches cleared, databases rebuilt, services refreshed") - - # System check results - summary_details+=("System updates, health, security, and config reviewed") - summary_details+=("System should feel faster and more responsive") + summary_details+=("$summary_line4") if [[ "${OPTIMIZE_SHOW_TOUCHID_TIP:-false}" == "true" ]]; then echo -e "${YELLOW}☻${NC} Run ${GRAY}mo touchid${NC} to approve sudo via Touch ID" @@ -218,6 +224,97 @@ count_local_snapshots() { echo "$output" | grep -c "com.apple.TimeMachine." | tr -d ' ' } +declare -a SECURITY_FIXES=() + +collect_security_fix_actions() { + SECURITY_FIXES=() + if [[ "${FIREWALL_DISABLED:-}" == "true" ]]; then + SECURITY_FIXES+=("firewall|Enable macOS firewall") + fi + if [[ "${GATEKEEPER_DISABLED:-}" == "true" ]]; then + SECURITY_FIXES+=("gatekeeper|Enable Gatekeeper (App download protection)") + fi + + ((${#SECURITY_FIXES[@]} > 0)) +} + +ask_for_security_fixes() { + if ! collect_security_fix_actions; then + return 1 + fi + + echo -e "${BLUE}SECURITY FIXES${NC}" + for entry in "${SECURITY_FIXES[@]}"; do + IFS='|' read -r _ label <<< "$entry" + echo -e " ${ICON_LIST} $label" + done + echo "" + echo -ne "${YELLOW}Apply now?${NC} ${GRAY}Enter confirm / ESC cancel${NC}: " + + local key + if ! key=$(read_key); then + echo "skip" + echo "" + return 1 + fi + + if [[ "$key" == "ENTER" ]]; then + echo "apply" + echo "" + return 0 + else + echo "skip" + echo "" + return 1 + fi +} + +apply_firewall_fix() { + if sudo defaults write /Library/Preferences/com.apple.alf globalstate -int 1; then + sudo pkill -HUP socketfilterfw 2> /dev/null || true + echo -e " ${GREEN}${ICON_SUCCESS}${NC} Firewall enabled" + FIREWALL_DISABLED=false + return 0 + fi + echo -e " ${YELLOW}${ICON_WARNING}${NC} Failed to enable firewall (check permissions)" + return 1 +} + +apply_gatekeeper_fix() { + if sudo spctl --master-enable 2> /dev/null; then + echo -e " ${GREEN}${ICON_SUCCESS}${NC} Gatekeeper enabled" + GATEKEEPER_DISABLED=false + return 0 + fi + echo -e " ${YELLOW}${ICON_WARNING}${NC} Failed to enable Gatekeeper" + return 1 +} + +perform_security_fixes() { + if ! ensure_sudo_session "Security changes require admin access"; then + echo -e "${YELLOW}${ICON_WARNING}${NC} Skipped security fixes (sudo denied)" + return 1 + fi + + local applied=0 + for entry in "${SECURITY_FIXES[@]}"; do + IFS='|' read -r action _ <<< "$entry" + case "$action" in + firewall) + apply_firewall_fix && ((applied++)) + ;; + gatekeeper) + apply_gatekeeper_fix && ((applied++)) + ;; + esac + done + + if ((applied > 0)); then + log_success "Security settings updated" + fi + SECURITY_FIXES=() +} + cleanup_all() { stop_sudo_session @@ -364,6 +461,9 @@ main() { local safe_count=${#safe_items[@]} local confirm_count=${#confirm_items[@]} + # Run system checks first + run_system_checks + export OPTIMIZE_SAFE_COUNT=$safe_count export OPTIMIZE_CONFIRM_COUNT=$confirm_count export OPTIMIZE_SHOW_TOUCHID_TIP="false" @@ -371,9 +471,6 @@ main() { export OPTIMIZE_SHOW_TOUCHID_TIP="true" fi - # Run system checks first - run_system_checks - # Show optimization summary at the end show_optimization_summary diff --git a/lib/autofix_manager.sh b/lib/autofix_manager.sh index c68056c..89bdf64 100644 --- a/lib/autofix_manager.sh +++ b/lib/autofix_manager.sh @@ -69,7 +69,7 @@ show_suggestions() { # Show auto-fix items if [[ ${#auto_fix_items[@]} -gt 0 ]]; then for item in "${auto_fix_items[@]}"; do - echo -e " ${YELLOW}⚠${NC} ${item} ${GREEN}[auto]${NC}" + echo -e " ${YELLOW}${ICON_WARNING}${NC} ${item} ${GREEN}[auto]${NC}" done fi @@ -78,7 +78,7 @@ show_suggestions() { for item in "${manual_items[@]}"; do local title="${item%%|*}" local hint="${item#*|}" - echo -e " ${YELLOW}⚠${NC} ${title}" + echo -e " ${YELLOW}${ICON_WARNING}${NC} ${title}" echo -e " ${GRAY}${hint}${NC}" done fi @@ -94,7 +94,7 @@ ask_for_auto_fix() { return 1 fi - echo -ne "Fix issues marked ${GREEN}[auto]${NC}? ${GRAY}Enter yes / ESC no${NC}: " + echo -ne "${PURPLE}${ICON_ARROW}${NC} Auto-fix issues now? ${GRAY}Enter confirm / ESC cancel${NC}: " local key if ! key=$(read_key); then @@ -118,6 +118,7 @@ ask_for_auto_fix() { # Returns: number of fixes applied perform_auto_fix() { local fixed_count=0 + local -a fixed_items=() # Ensure sudo access if ! has_sudo_session; then @@ -134,6 +135,7 @@ perform_auto_fix() { if sudo defaults write /Library/Preferences/com.apple.alf globalstate -int 1 2>/dev/null; then echo -e "${GREEN}✓${NC} Firewall enabled" ((fixed_count++)) + fixed_items+=("Firewall enabled") else echo -e "${RED}✗${NC} Failed to enable Firewall" fi @@ -142,13 +144,14 @@ perform_auto_fix() { # Fix Touch ID if [[ -n "${TOUCHID_NOT_CONFIGURED:-}" && "${TOUCHID_NOT_CONFIGURED}" == "true" ]]; then - echo -e "${BLUE}Configuring Touch ID for sudo...${NC}" + echo -e "${BLUE}${ICON_ARROW}${NC} Configuring Touch ID for sudo..." local pam_file="/etc/pam.d/sudo" if sudo bash -c "grep -q 'pam_tid.so' '$pam_file' 2>/dev/null || sed -i '' '2i\\ auth sufficient pam_tid.so ' '$pam_file'" 2>/dev/null; then echo -e "${GREEN}✓${NC} Touch ID configured" ((fixed_count++)) + fixed_items+=("Touch ID configured for sudo") else echo -e "${RED}✗${NC} Failed to configure Touch ID" fi @@ -161,6 +164,7 @@ auth sufficient pam_tid.so if sudo softwareupdate --install-rosetta --agree-to-license 2>&1 | grep -qE "(Installing|Installed|already installed)"; then echo -e "${GREEN}✓${NC} Rosetta 2 installed" ((fixed_count++)) + fixed_items+=("Rosetta 2 installed") else echo -e "${RED}✗${NC} Failed to install Rosetta 2" fi @@ -168,11 +172,16 @@ auth sufficient pam_tid.so fi if [[ $fixed_count -gt 0 ]]; then - echo -e "${GREEN}Fixed ${fixed_count} issue(s)${NC}" + AUTO_FIX_SUMMARY="Auto fixes applied: ${fixed_count} issue(s)" + if [[ ${#fixed_items[@]} -gt 0 ]]; then + AUTO_FIX_DETAILS=$(printf '%s\n' "${fixed_items[@]}") + else + AUTO_FIX_DETAILS="" + fi else - echo -e "${YELLOW}No issues were fixed${NC}" + AUTO_FIX_SUMMARY="Auto fixes skipped: No changes were required" + AUTO_FIX_DETAILS="" fi - echo "" - - return $fixed_count + export AUTO_FIX_SUMMARY AUTO_FIX_DETAILS + return 0 } diff --git a/lib/check_config.sh b/lib/check_config.sh index 9140efd..308fa12 100644 --- a/lib/check_config.sh +++ b/lib/check_config.sh @@ -19,7 +19,7 @@ check_touchid_sudo() { fi if [[ "$is_supported" == "true" ]]; then - echo -e " ${YELLOW}⚠${NC} Touch ID ${YELLOW}Not configured${NC} for sudo" + echo -e " ${YELLOW}${ICON_WARNING}${NC} Touch ID ${YELLOW}Not configured${NC} for sudo" export TOUCHID_NOT_CONFIGURED=true fi fi @@ -31,7 +31,7 @@ check_rosetta() { if [[ -f "/Library/Apple/usr/share/rosetta/rosetta" ]]; then echo -e " ${GREEN}✓${NC} Rosetta 2 Installed" else - echo -e " ${YELLOW}⚠${NC} Rosetta 2 ${YELLOW}Not installed${NC}" + echo -e " ${YELLOW}${ICON_WARNING}${NC} Rosetta 2 ${YELLOW}Not installed${NC}" export ROSETTA_NOT_INSTALLED=true fi fi @@ -46,7 +46,7 @@ check_git_config() { if [[ -n "$git_name" && -n "$git_email" ]]; then echo -e " ${GREEN}✓${NC} Git Config Configured" else - echo -e " ${YELLOW}⚠${NC} Git Config ${YELLOW}Not configured${NC}" + echo -e " ${YELLOW}${ICON_WARNING}${NC} Git Config ${YELLOW}Not configured${NC}" fi fi } diff --git a/lib/check_health.sh b/lib/check_health.sh index c552d19..031c096 100644 --- a/lib/check_health.sh +++ b/lib/check_health.sh @@ -12,7 +12,7 @@ check_disk_space() { if [[ $free_num -lt 20 ]]; then echo -e " ${RED}✗${NC} Disk Space ${RED}${free_gb}GB free${NC} (Critical)" elif [[ $free_num -lt 50 ]]; then - echo -e " ${YELLOW}⚠${NC} Disk Space ${YELLOW}${free_gb}GB free${NC} (Low)" + echo -e " ${YELLOW}${ICON_WARNING}${NC} Disk Space ${YELLOW}${free_gb}GB free${NC} (Low)" else echo -e " ${GREEN}✓${NC} Disk Space ${free_gb}GB free" fi @@ -58,7 +58,7 @@ check_memory_usage() { if [[ $used_percent -gt 90 ]]; then echo -e " ${RED}✗${NC} Memory ${RED}${used_percent}% used${NC} (Critical)" elif [[ $used_percent -gt 80 ]]; then - echo -e " ${YELLOW}⚠${NC} Memory ${YELLOW}${used_percent}% used${NC} (High)" + echo -e " ${YELLOW}${ICON_WARNING}${NC} Memory ${YELLOW}${used_percent}% used${NC} (High)" else echo -e " ${GREEN}✓${NC} Memory ${used_percent}% used" fi @@ -86,7 +86,7 @@ check_login_items() { fi if [[ $login_items_count -gt 15 ]]; then - echo -e " ${YELLOW}⚠${NC} Login Items ${YELLOW}${login_items_count} apps${NC} auto-start (High)" + echo -e " ${YELLOW}${ICON_WARNING}${NC} Login Items ${YELLOW}${login_items_count} apps${NC} auto-start (High)" elif [[ $login_items_count -gt 0 ]]; then echo -e " ${GREEN}✓${NC} Login Items ${login_items_count} apps auto-start" else @@ -151,9 +151,9 @@ check_cache_size() { local cache_size_int=$(echo "$cache_size_gb" | cut -d'.' -f1) if [[ $cache_size_int -gt 10 ]]; then - echo -e " ${YELLOW}⚠${NC} Cache Size ${YELLOW}${cache_size_gb}GB${NC} cleanable" + echo -e " ${YELLOW}${ICON_WARNING}${NC} Cache Size ${YELLOW}${cache_size_gb}GB${NC} cleanable" elif [[ $cache_size_int -gt 5 ]]; then - echo -e " ${YELLOW}⚠${NC} Cache Size ${YELLOW}${cache_size_gb}GB${NC} cleanable" + echo -e " ${YELLOW}${ICON_WARNING}${NC} Cache Size ${YELLOW}${cache_size_gb}GB${NC} cleanable" else echo -e " ${GREEN}✓${NC} Cache Size ${cache_size_gb}GB" fi @@ -170,7 +170,7 @@ check_swap_usage() { if [[ "$swap_used" == *"G"* ]]; then local swap_gb=${swap_num%.*} if [[ $swap_gb -gt 2 ]]; then - echo -e " ${YELLOW}⚠${NC} Swap Usage ${YELLOW}${swap_used}${NC} (High)" + echo -e " ${YELLOW}${ICON_WARNING}${NC} Swap Usage ${YELLOW}${swap_used}${NC} (High)" else echo -e " ${GREEN}✓${NC} Swap Usage ${swap_used}" fi @@ -186,7 +186,7 @@ check_timemachine() { if command -v tmutil > /dev/null 2>&1; then local tm_status=$(tmutil latestbackup 2>/dev/null || echo "") if [[ -z "$tm_status" ]]; then - echo -e " ${YELLOW}⚠${NC} Time Machine No backups found" + echo -e " ${YELLOW}${ICON_WARNING}${NC} Time Machine No backups found" echo -e " ${GRAY}Set up in System Settings → General → Time Machine (optional but recommended)${NC}" else # Get last backup time @@ -194,7 +194,7 @@ check_timemachine() { if [[ -n "$backup_date" ]]; then echo -e " ${GREEN}✓${NC} Time Machine Backup active" else - echo -e " ${YELLOW}⚠${NC} Time Machine Not configured" + echo -e " ${YELLOW}${ICON_WARNING}${NC} Time Machine Not configured" fi fi fi @@ -220,7 +220,7 @@ check_brew_health() { else local warning_count=$(echo "$brew_doctor" | grep -c "Warning:" || echo "0") if [[ $warning_count -gt 0 ]]; then - echo -e " ${YELLOW}⚠${NC} Homebrew ${YELLOW}${warning_count} warnings${NC}" + echo -e " ${YELLOW}${ICON_WARNING}${NC} Homebrew ${YELLOW}${warning_count} warnings${NC}" echo -e " ${GRAY}Run: ${GREEN}brew doctor${NC} to see fixes, then rerun until clean${NC}" export BREW_HAS_WARNINGS=true else diff --git a/lib/check_security.sh b/lib/check_security.sh index f767593..037ae9d 100644 --- a/lib/check_security.sh +++ b/lib/check_security.sh @@ -17,11 +17,12 @@ check_filevault() { 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}⚠${NC} Firewall ${YELLOW}Disabled${NC} (Consider enabling)" + 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 @@ -34,10 +35,12 @@ check_gatekeeper() { 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}⚠${NC} Gatekeeper ${YELLOW}Disabled${NC}" + 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 } @@ -49,7 +52,7 @@ check_sip() { if echo "$sip_status" | grep -q "enabled"; then echo -e " ${GREEN}✓${NC} SIP Enabled" else - echo -e " ${YELLOW}⚠${NC} SIP ${YELLOW}Disabled${NC}" + echo -e " ${YELLOW}${ICON_WARNING}${NC} SIP ${YELLOW}Disabled${NC}" echo -e " ${GRAY}Restart into Recovery → Utilities → Terminal → run: csrutil enable${NC}" fi fi diff --git a/lib/check_updates.sh b/lib/check_updates.sh index aa07ddc..0db6971 100644 --- a/lib/check_updates.sh +++ b/lib/check_updates.sh @@ -94,7 +94,7 @@ check_homebrew_updates() { elif [[ $cask_count -gt 0 ]]; then breakdown=" (${cask_count} cask)" fi - echo -e " ${YELLOW}⚠${NC} Homebrew ${YELLOW}${total_count} updates${NC}${breakdown}" + 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" @@ -134,9 +134,24 @@ get_software_updates() { } 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 ' ') @@ -145,7 +160,7 @@ check_appstore_updates() { export APPSTORE_UPDATE_COUNT=$update_count if [[ $update_count -gt 0 ]]; then - echo -e " ${YELLOW}⚠${NC} App Store ${YELLOW}${update_count} apps${NC} need update" + echo -e " ${YELLOW}${ICON_WARNING}${NC} App Store ${YELLOW}${update_count} apps${NC} need update" echo -e " ${GRAY}Run: ${GREEN}softwareupdate -i