mirror of
https://github.com/tw93/Mole.git
synced 2026-02-10 22:59:17 +00:00
System optimization and massive updates
This commit is contained in:
133
bin/optimize.sh
133
bin/optimize.sh
@@ -27,6 +27,7 @@ print_header() {
|
|||||||
|
|
||||||
# System check functions (real-time display)
|
# System check functions (real-time display)
|
||||||
run_system_checks() {
|
run_system_checks() {
|
||||||
|
unset AUTO_FIX_SUMMARY AUTO_FIX_DETAILS
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${PURPLE}System Check${NC}"
|
echo -e "${PURPLE}System Check${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -44,6 +45,9 @@ run_system_checks() {
|
|||||||
# Check security - real-time display
|
# Check security - real-time display
|
||||||
echo -e "${BLUE}${ICON_ARROW}${NC} Security posture"
|
echo -e "${BLUE}${ICON_ARROW}${NC} Security posture"
|
||||||
check_all_security
|
check_all_security
|
||||||
|
if ask_for_security_fixes; then
|
||||||
|
perform_security_fixes
|
||||||
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Check configuration - real-time display
|
# Check configuration - real-time display
|
||||||
@@ -67,30 +71,32 @@ run_system_checks() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
show_optimization_summary() {
|
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
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
local summary_title="Optimization and Check Complete"
|
local summary_title="Optimization and Check Complete"
|
||||||
local -a summary_details=()
|
local -a summary_details=()
|
||||||
|
|
||||||
# Optimization results
|
# Optimization results
|
||||||
if ((OPTIMIZE_SAFE_COUNT > 0)); then
|
summary_details+=("Optimizations: ${GREEN}${safe_count}${NC} applied, ${YELLOW}${confirm_count}${NC} manual checks")
|
||||||
summary_details+=("Applied ${GREEN}${OPTIMIZE_SAFE_COUNT}${NC} optimizations")
|
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
|
else
|
||||||
summary_details+=("System already optimized")
|
summary_line4="Mac should feel faster and more responsive"
|
||||||
fi
|
fi
|
||||||
|
summary_details+=("$summary_line4")
|
||||||
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")
|
|
||||||
|
|
||||||
if [[ "${OPTIMIZE_SHOW_TOUCHID_TIP:-false}" == "true" ]]; then
|
if [[ "${OPTIMIZE_SHOW_TOUCHID_TIP:-false}" == "true" ]]; then
|
||||||
echo -e "${YELLOW}☻${NC} Run ${GRAY}mo touchid${NC} to approve sudo via Touch ID"
|
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 ' '
|
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() {
|
cleanup_all() {
|
||||||
stop_sudo_session
|
stop_sudo_session
|
||||||
@@ -364,6 +461,9 @@ main() {
|
|||||||
local safe_count=${#safe_items[@]}
|
local safe_count=${#safe_items[@]}
|
||||||
local confirm_count=${#confirm_items[@]}
|
local confirm_count=${#confirm_items[@]}
|
||||||
|
|
||||||
|
# Run system checks first
|
||||||
|
run_system_checks
|
||||||
|
|
||||||
export OPTIMIZE_SAFE_COUNT=$safe_count
|
export OPTIMIZE_SAFE_COUNT=$safe_count
|
||||||
export OPTIMIZE_CONFIRM_COUNT=$confirm_count
|
export OPTIMIZE_CONFIRM_COUNT=$confirm_count
|
||||||
export OPTIMIZE_SHOW_TOUCHID_TIP="false"
|
export OPTIMIZE_SHOW_TOUCHID_TIP="false"
|
||||||
@@ -371,9 +471,6 @@ main() {
|
|||||||
export OPTIMIZE_SHOW_TOUCHID_TIP="true"
|
export OPTIMIZE_SHOW_TOUCHID_TIP="true"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run system checks first
|
|
||||||
run_system_checks
|
|
||||||
|
|
||||||
# Show optimization summary at the end
|
# Show optimization summary at the end
|
||||||
show_optimization_summary
|
show_optimization_summary
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ show_suggestions() {
|
|||||||
# Show auto-fix items
|
# Show auto-fix items
|
||||||
if [[ ${#auto_fix_items[@]} -gt 0 ]]; then
|
if [[ ${#auto_fix_items[@]} -gt 0 ]]; then
|
||||||
for item in "${auto_fix_items[@]}"; do
|
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
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ show_suggestions() {
|
|||||||
for item in "${manual_items[@]}"; do
|
for item in "${manual_items[@]}"; do
|
||||||
local title="${item%%|*}"
|
local title="${item%%|*}"
|
||||||
local hint="${item#*|}"
|
local hint="${item#*|}"
|
||||||
echo -e " ${YELLOW}⚠${NC} ${title}"
|
echo -e " ${YELLOW}${ICON_WARNING}${NC} ${title}"
|
||||||
echo -e " ${GRAY}${hint}${NC}"
|
echo -e " ${GRAY}${hint}${NC}"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
@@ -94,7 +94,7 @@ ask_for_auto_fix() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
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
|
local key
|
||||||
if ! key=$(read_key); then
|
if ! key=$(read_key); then
|
||||||
@@ -118,6 +118,7 @@ ask_for_auto_fix() {
|
|||||||
# Returns: number of fixes applied
|
# Returns: number of fixes applied
|
||||||
perform_auto_fix() {
|
perform_auto_fix() {
|
||||||
local fixed_count=0
|
local fixed_count=0
|
||||||
|
local -a fixed_items=()
|
||||||
|
|
||||||
# Ensure sudo access
|
# Ensure sudo access
|
||||||
if ! has_sudo_session; then
|
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
|
if sudo defaults write /Library/Preferences/com.apple.alf globalstate -int 1 2>/dev/null; then
|
||||||
echo -e "${GREEN}✓${NC} Firewall enabled"
|
echo -e "${GREEN}✓${NC} Firewall enabled"
|
||||||
((fixed_count++))
|
((fixed_count++))
|
||||||
|
fixed_items+=("Firewall enabled")
|
||||||
else
|
else
|
||||||
echo -e "${RED}✗${NC} Failed to enable Firewall"
|
echo -e "${RED}✗${NC} Failed to enable Firewall"
|
||||||
fi
|
fi
|
||||||
@@ -142,13 +144,14 @@ perform_auto_fix() {
|
|||||||
|
|
||||||
# Fix Touch ID
|
# Fix Touch ID
|
||||||
if [[ -n "${TOUCHID_NOT_CONFIGURED:-}" && "${TOUCHID_NOT_CONFIGURED}" == "true" ]]; then
|
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"
|
local pam_file="/etc/pam.d/sudo"
|
||||||
if sudo bash -c "grep -q 'pam_tid.so' '$pam_file' 2>/dev/null || sed -i '' '2i\\
|
if sudo bash -c "grep -q 'pam_tid.so' '$pam_file' 2>/dev/null || sed -i '' '2i\\
|
||||||
auth sufficient pam_tid.so
|
auth sufficient pam_tid.so
|
||||||
' '$pam_file'" 2>/dev/null; then
|
' '$pam_file'" 2>/dev/null; then
|
||||||
echo -e "${GREEN}✓${NC} Touch ID configured"
|
echo -e "${GREEN}✓${NC} Touch ID configured"
|
||||||
((fixed_count++))
|
((fixed_count++))
|
||||||
|
fixed_items+=("Touch ID configured for sudo")
|
||||||
else
|
else
|
||||||
echo -e "${RED}✗${NC} Failed to configure Touch ID"
|
echo -e "${RED}✗${NC} Failed to configure Touch ID"
|
||||||
fi
|
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
|
if sudo softwareupdate --install-rosetta --agree-to-license 2>&1 | grep -qE "(Installing|Installed|already installed)"; then
|
||||||
echo -e "${GREEN}✓${NC} Rosetta 2 installed"
|
echo -e "${GREEN}✓${NC} Rosetta 2 installed"
|
||||||
((fixed_count++))
|
((fixed_count++))
|
||||||
|
fixed_items+=("Rosetta 2 installed")
|
||||||
else
|
else
|
||||||
echo -e "${RED}✗${NC} Failed to install Rosetta 2"
|
echo -e "${RED}✗${NC} Failed to install Rosetta 2"
|
||||||
fi
|
fi
|
||||||
@@ -168,11 +172,16 @@ auth sufficient pam_tid.so
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $fixed_count -gt 0 ]]; then
|
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
|
else
|
||||||
echo -e "${YELLOW}No issues were fixed${NC}"
|
AUTO_FIX_SUMMARY="Auto fixes skipped: No changes were required"
|
||||||
|
AUTO_FIX_DETAILS=""
|
||||||
fi
|
fi
|
||||||
echo ""
|
export AUTO_FIX_SUMMARY AUTO_FIX_DETAILS
|
||||||
|
return 0
|
||||||
return $fixed_count
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ check_touchid_sudo() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$is_supported" == "true" ]]; then
|
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
|
export TOUCHID_NOT_CONFIGURED=true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -31,7 +31,7 @@ check_rosetta() {
|
|||||||
if [[ -f "/Library/Apple/usr/share/rosetta/rosetta" ]]; then
|
if [[ -f "/Library/Apple/usr/share/rosetta/rosetta" ]]; then
|
||||||
echo -e " ${GREEN}✓${NC} Rosetta 2 Installed"
|
echo -e " ${GREEN}✓${NC} Rosetta 2 Installed"
|
||||||
else
|
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
|
export ROSETTA_NOT_INSTALLED=true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -46,7 +46,7 @@ check_git_config() {
|
|||||||
if [[ -n "$git_name" && -n "$git_email" ]]; then
|
if [[ -n "$git_name" && -n "$git_email" ]]; then
|
||||||
echo -e " ${GREEN}✓${NC} Git Config Configured"
|
echo -e " ${GREEN}✓${NC} Git Config Configured"
|
||||||
else
|
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
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ check_disk_space() {
|
|||||||
if [[ $free_num -lt 20 ]]; then
|
if [[ $free_num -lt 20 ]]; then
|
||||||
echo -e " ${RED}✗${NC} Disk Space ${RED}${free_gb}GB free${NC} (Critical)"
|
echo -e " ${RED}✗${NC} Disk Space ${RED}${free_gb}GB free${NC} (Critical)"
|
||||||
elif [[ $free_num -lt 50 ]]; then
|
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
|
else
|
||||||
echo -e " ${GREEN}✓${NC} Disk Space ${free_gb}GB free"
|
echo -e " ${GREEN}✓${NC} Disk Space ${free_gb}GB free"
|
||||||
fi
|
fi
|
||||||
@@ -58,7 +58,7 @@ check_memory_usage() {
|
|||||||
if [[ $used_percent -gt 90 ]]; then
|
if [[ $used_percent -gt 90 ]]; then
|
||||||
echo -e " ${RED}✗${NC} Memory ${RED}${used_percent}% used${NC} (Critical)"
|
echo -e " ${RED}✗${NC} Memory ${RED}${used_percent}% used${NC} (Critical)"
|
||||||
elif [[ $used_percent -gt 80 ]]; then
|
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
|
else
|
||||||
echo -e " ${GREEN}✓${NC} Memory ${used_percent}% used"
|
echo -e " ${GREEN}✓${NC} Memory ${used_percent}% used"
|
||||||
fi
|
fi
|
||||||
@@ -86,7 +86,7 @@ check_login_items() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $login_items_count -gt 15 ]]; then
|
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
|
elif [[ $login_items_count -gt 0 ]]; then
|
||||||
echo -e " ${GREEN}✓${NC} Login Items ${login_items_count} apps auto-start"
|
echo -e " ${GREEN}✓${NC} Login Items ${login_items_count} apps auto-start"
|
||||||
else
|
else
|
||||||
@@ -151,9 +151,9 @@ check_cache_size() {
|
|||||||
local cache_size_int=$(echo "$cache_size_gb" | cut -d'.' -f1)
|
local cache_size_int=$(echo "$cache_size_gb" | cut -d'.' -f1)
|
||||||
|
|
||||||
if [[ $cache_size_int -gt 10 ]]; then
|
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
|
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
|
else
|
||||||
echo -e " ${GREEN}✓${NC} Cache Size ${cache_size_gb}GB"
|
echo -e " ${GREEN}✓${NC} Cache Size ${cache_size_gb}GB"
|
||||||
fi
|
fi
|
||||||
@@ -170,7 +170,7 @@ check_swap_usage() {
|
|||||||
if [[ "$swap_used" == *"G"* ]]; then
|
if [[ "$swap_used" == *"G"* ]]; then
|
||||||
local swap_gb=${swap_num%.*}
|
local swap_gb=${swap_num%.*}
|
||||||
if [[ $swap_gb -gt 2 ]]; then
|
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
|
else
|
||||||
echo -e " ${GREEN}✓${NC} Swap Usage ${swap_used}"
|
echo -e " ${GREEN}✓${NC} Swap Usage ${swap_used}"
|
||||||
fi
|
fi
|
||||||
@@ -186,7 +186,7 @@ check_timemachine() {
|
|||||||
if command -v tmutil > /dev/null 2>&1; then
|
if command -v tmutil > /dev/null 2>&1; then
|
||||||
local tm_status=$(tmutil latestbackup 2>/dev/null || echo "")
|
local tm_status=$(tmutil latestbackup 2>/dev/null || echo "")
|
||||||
if [[ -z "$tm_status" ]]; then
|
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}"
|
echo -e " ${GRAY}Set up in System Settings → General → Time Machine (optional but recommended)${NC}"
|
||||||
else
|
else
|
||||||
# Get last backup time
|
# Get last backup time
|
||||||
@@ -194,7 +194,7 @@ check_timemachine() {
|
|||||||
if [[ -n "$backup_date" ]]; then
|
if [[ -n "$backup_date" ]]; then
|
||||||
echo -e " ${GREEN}✓${NC} Time Machine Backup active"
|
echo -e " ${GREEN}✓${NC} Time Machine Backup active"
|
||||||
else
|
else
|
||||||
echo -e " ${YELLOW}⚠${NC} Time Machine Not configured"
|
echo -e " ${YELLOW}${ICON_WARNING}${NC} Time Machine Not configured"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -220,7 +220,7 @@ check_brew_health() {
|
|||||||
else
|
else
|
||||||
local warning_count=$(echo "$brew_doctor" | grep -c "Warning:" || echo "0")
|
local warning_count=$(echo "$brew_doctor" | grep -c "Warning:" || echo "0")
|
||||||
if [[ $warning_count -gt 0 ]]; then
|
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}"
|
echo -e " ${GRAY}Run: ${GREEN}brew doctor${NC} to see fixes, then rerun until clean${NC}"
|
||||||
export BREW_HAS_WARNINGS=true
|
export BREW_HAS_WARNINGS=true
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -17,11 +17,12 @@ check_filevault() {
|
|||||||
|
|
||||||
check_firewall() {
|
check_firewall() {
|
||||||
# Check firewall status
|
# Check firewall status
|
||||||
|
unset FIREWALL_DISABLED
|
||||||
local firewall_status=$(defaults read /Library/Preferences/com.apple.alf globalstate 2>/dev/null || echo "0")
|
local firewall_status=$(defaults read /Library/Preferences/com.apple.alf globalstate 2>/dev/null || echo "0")
|
||||||
if [[ "$firewall_status" == "1" || "$firewall_status" == "2" ]]; then
|
if [[ "$firewall_status" == "1" || "$firewall_status" == "2" ]]; then
|
||||||
echo -e " ${GREEN}✓${NC} Firewall Enabled"
|
echo -e " ${GREEN}✓${NC} Firewall Enabled"
|
||||||
else
|
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}System Settings → Network → Firewall, or run:${NC}"
|
||||||
echo -e " ${GRAY}sudo defaults write /Library/Preferences/com.apple.alf globalstate -int 1${NC}"
|
echo -e " ${GRAY}sudo defaults write /Library/Preferences/com.apple.alf globalstate -int 1${NC}"
|
||||||
export FIREWALL_DISABLED=true
|
export FIREWALL_DISABLED=true
|
||||||
@@ -34,10 +35,12 @@ check_gatekeeper() {
|
|||||||
local gk_status=$(spctl --status 2>/dev/null || echo "")
|
local gk_status=$(spctl --status 2>/dev/null || echo "")
|
||||||
if echo "$gk_status" | grep -q "enabled"; then
|
if echo "$gk_status" | grep -q "enabled"; then
|
||||||
echo -e " ${GREEN}✓${NC} Gatekeeper Active"
|
echo -e " ${GREEN}✓${NC} Gatekeeper Active"
|
||||||
|
unset GATEKEEPER_DISABLED
|
||||||
else
|
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}Enable via System Settings → Privacy & Security, or:${NC}"
|
||||||
echo -e " ${GRAY}sudo spctl --master-enable${NC}"
|
echo -e " ${GRAY}sudo spctl --master-enable${NC}"
|
||||||
|
export GATEKEEPER_DISABLED=true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -49,7 +52,7 @@ check_sip() {
|
|||||||
if echo "$sip_status" | grep -q "enabled"; then
|
if echo "$sip_status" | grep -q "enabled"; then
|
||||||
echo -e " ${GREEN}✓${NC} SIP Enabled"
|
echo -e " ${GREEN}✓${NC} SIP Enabled"
|
||||||
else
|
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}"
|
echo -e " ${GRAY}Restart into Recovery → Utilities → Terminal → run: csrutil enable${NC}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ check_homebrew_updates() {
|
|||||||
elif [[ $cask_count -gt 0 ]]; then
|
elif [[ $cask_count -gt 0 ]]; then
|
||||||
breakdown=" (${cask_count} cask)"
|
breakdown=" (${cask_count} cask)"
|
||||||
fi
|
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}"
|
echo -e " ${GRAY}Run: ${GREEN}brew upgrade${NC} ${GRAY}and/or${NC} ${GREEN}brew upgrade --cask${NC}"
|
||||||
else
|
else
|
||||||
echo -e " ${GREEN}✓${NC} Homebrew Up to date"
|
echo -e " ${GREEN}✓${NC} Homebrew Up to date"
|
||||||
@@ -134,9 +134,24 @@ get_software_updates() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
check_appstore_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=""
|
local update_list=""
|
||||||
update_list=$(get_software_updates | grep -v "Software Update Tool" | grep "^\*" | grep -vi "macOS" || echo "")
|
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
|
local update_count=0
|
||||||
if [[ -n "$update_list" ]]; then
|
if [[ -n "$update_list" ]]; then
|
||||||
update_count=$(echo "$update_list" | wc -l | tr -d ' ')
|
update_count=$(echo "$update_list" | wc -l | tr -d ' ')
|
||||||
@@ -145,7 +160,7 @@ check_appstore_updates() {
|
|||||||
export APPSTORE_UPDATE_COUNT=$update_count
|
export APPSTORE_UPDATE_COUNT=$update_count
|
||||||
|
|
||||||
if [[ $update_count -gt 0 ]]; then
|
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 <label>${NC}"
|
echo -e " ${GRAY}Run: ${GREEN}softwareupdate -i <label>${NC}"
|
||||||
else
|
else
|
||||||
echo -e " ${GREEN}✓${NC} App Store Up to date"
|
echo -e " ${GREEN}✓${NC} App Store Up to date"
|
||||||
@@ -153,19 +168,34 @@ check_appstore_updates() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
check_macos_update() {
|
check_macos_update() {
|
||||||
|
local spinner_started=false
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
printf " Checking macOS updates...\r"
|
||||||
|
start_inline_spinner "Checking macOS updates..."
|
||||||
|
spinner_started=true
|
||||||
|
export SOFTWAREUPDATE_SPINNER_SHOWN="external"
|
||||||
|
else
|
||||||
|
echo "Checking macOS updates..."
|
||||||
|
fi
|
||||||
|
|
||||||
# Check for macOS system update using cached list
|
# Check for macOS system update using cached list
|
||||||
local macos_update=""
|
local macos_update=""
|
||||||
macos_update=$(get_software_updates | grep -i "macOS" | head -1 || echo "")
|
macos_update=$(get_software_updates | grep -i "macOS" | head -1 || echo "")
|
||||||
|
|
||||||
|
if [[ "$spinner_started" == "true" ]]; then
|
||||||
|
stop_inline_spinner
|
||||||
|
unset SOFTWAREUPDATE_SPINNER_SHOWN
|
||||||
|
fi
|
||||||
|
|
||||||
export MACOS_UPDATE_AVAILABLE="false"
|
export MACOS_UPDATE_AVAILABLE="false"
|
||||||
|
|
||||||
if [[ -n "$macos_update" ]]; then
|
if [[ -n "$macos_update" ]]; then
|
||||||
export MACOS_UPDATE_AVAILABLE="true"
|
export MACOS_UPDATE_AVAILABLE="true"
|
||||||
local version=$(echo "$macos_update" | grep -o '[0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?' | head -1)
|
local version=$(echo "$macos_update" | grep -o '[0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?' | head -1)
|
||||||
if [[ -n "$version" ]]; then
|
if [[ -n "$version" ]]; then
|
||||||
echo -e " ${YELLOW}⚠${NC} macOS ${YELLOW}${version} available${NC}"
|
echo -e " ${YELLOW}${ICON_WARNING}${NC} macOS ${YELLOW}${version} available${NC}"
|
||||||
else
|
else
|
||||||
echo -e " ${YELLOW}⚠${NC} macOS ${YELLOW}Update available${NC}"
|
echo -e " ${YELLOW}${ICON_WARNING}${NC} macOS ${YELLOW}Update available${NC}"
|
||||||
fi
|
fi
|
||||||
echo -e " ${GRAY}Run: ${GREEN}softwareupdate -i <label>${NC}"
|
echo -e " ${GRAY}Run: ${GREEN}softwareupdate -i <label>${NC}"
|
||||||
else
|
else
|
||||||
@@ -220,7 +250,7 @@ check_mole_update() {
|
|||||||
# Compare versions
|
# Compare versions
|
||||||
if [[ "$(printf '%s\n' "$current_version" "$latest_version" | sort -V | head -1)" == "$current_version" ]]; then
|
if [[ "$(printf '%s\n' "$current_version" "$latest_version" | sort -V | head -1)" == "$current_version" ]]; then
|
||||||
export MOLE_UPDATE_AVAILABLE="true"
|
export MOLE_UPDATE_AVAILABLE="true"
|
||||||
echo -e " ${YELLOW}⚠${NC} Mole ${YELLOW}${latest_version} available${NC} (current: ${current_version})"
|
echo -e " ${YELLOW}${ICON_WARNING}${NC} Mole ${YELLOW}${latest_version} available${NC} (current: ${current_version})"
|
||||||
echo -e " ${GRAY}Run: ${GREEN}mo update${NC}"
|
echo -e " ${GRAY}Run: ${GREEN}mo update${NC}"
|
||||||
else
|
else
|
||||||
echo -e " ${GREEN}✓${NC} Mole Up to date (${current_version})"
|
echo -e " ${GREEN}✓${NC} Mole Up to date (${current_version})"
|
||||||
|
|||||||
103
lib/common.sh
103
lib/common.sh
@@ -356,6 +356,53 @@ drain_pending_input() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Shared timeout helper -------------------------------------------------------
|
||||||
|
if [[ -z "${MO_TIMEOUT_INITIALIZED:-}" ]]; then
|
||||||
|
MO_TIMEOUT_BIN=""
|
||||||
|
for candidate in gtimeout timeout; do
|
||||||
|
if command -v "$candidate" > /dev/null 2>&1; then
|
||||||
|
MO_TIMEOUT_BIN="$candidate"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
export MO_TIMEOUT_INITIALIZED=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_with_timeout() {
|
||||||
|
local duration="${1:-0}"
|
||||||
|
shift || true
|
||||||
|
|
||||||
|
if [[ ! "$duration" =~ ^[0-9]+$ ]]; then
|
||||||
|
duration=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$duration" -le 0 ]]; then
|
||||||
|
"$@"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${MO_TIMEOUT_BIN:-}" ]]; then
|
||||||
|
"$MO_TIMEOUT_BIN" "$duration" "$@"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$@" &
|
||||||
|
local cmd_pid=$!
|
||||||
|
local elapsed=0
|
||||||
|
while kill -0 "$cmd_pid" 2> /dev/null; do
|
||||||
|
if [[ $elapsed -ge $duration ]]; then
|
||||||
|
kill -TERM "$cmd_pid" 2> /dev/null || true
|
||||||
|
sleep 1
|
||||||
|
kill -KILL "$cmd_pid" 2> /dev/null || true
|
||||||
|
wait "$cmd_pid" 2> /dev/null || true
|
||||||
|
return 124
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
((elapsed++))
|
||||||
|
done
|
||||||
|
wait "$cmd_pid"
|
||||||
|
}
|
||||||
|
|
||||||
# Menu display helper
|
# Menu display helper
|
||||||
show_menu_option() {
|
show_menu_option() {
|
||||||
local number="$1"
|
local number="$1"
|
||||||
@@ -594,35 +641,41 @@ request_sudo_access() {
|
|||||||
|
|
||||||
[[ -z "$tty_path" ]] && return 1
|
[[ -z "$tty_path" ]] && return 1
|
||||||
|
|
||||||
local password=""
|
local attempts=0
|
||||||
local saved_stty=""
|
while ((attempts < 3)); do
|
||||||
if command -v stty > /dev/null 2>&1; then
|
local password=""
|
||||||
saved_stty=$(stty -g < "$tty_path" 2> /dev/null || echo "")
|
local saved_stty=""
|
||||||
stty -echo < "$tty_path" 2> /dev/null || true
|
if command -v stty > /dev/null 2>&1; then
|
||||||
fi
|
saved_stty=$(stty -g < "$tty_path" 2> /dev/null || echo "")
|
||||||
|
stty -echo < "$tty_path" 2> /dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
printf "${PURPLE}${ICON_ARROW}${NC} Enter admin password: " > "$tty_path" 2> /dev/null || true
|
printf "${PURPLE}${ICON_ARROW}${NC} Enter admin password: " > "$tty_path" 2> /dev/null || true
|
||||||
IFS= read -r password < "$tty_path" || password=""
|
IFS= read -r password < "$tty_path" || password=""
|
||||||
printf "\n" > "$tty_path" 2> /dev/null || true
|
printf "\n" > "$tty_path" 2> /dev/null || true
|
||||||
|
|
||||||
if [[ -n "$saved_stty" ]]; then
|
if [[ -n "$saved_stty" ]]; then
|
||||||
stty "$saved_stty" < "$tty_path" 2> /dev/null || stty echo < "$tty_path" 2> /dev/null || true
|
stty "$saved_stty" < "$tty_path" 2> /dev/null || stty echo < "$tty_path" 2> /dev/null || true
|
||||||
else
|
else
|
||||||
stty echo < "$tty_path" 2> /dev/null || true
|
stty echo < "$tty_path" 2> /dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$password" ]]; then
|
||||||
|
unset password
|
||||||
|
((attempts++))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if printf '%s\n' "$password" | "${sudo_stdin_cmd[@]}" > /dev/null 2>&1; then
|
||||||
|
unset password
|
||||||
|
sudo -n true 2> /dev/null || true
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -z "$password" ]]; then
|
|
||||||
unset password
|
unset password
|
||||||
return 1
|
((attempts++))
|
||||||
fi
|
echo -e "${YELLOW}${ICON_WARNING}${NC} Incorrect password, try again" > "$tty_path" 2> /dev/null || true
|
||||||
|
done
|
||||||
if printf '%s\n' "$password" | "${sudo_stdin_cmd[@]}" > /dev/null 2>&1; then
|
|
||||||
unset password
|
|
||||||
sudo -n true 2> /dev/null || true
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
unset password
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,18 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
readonly MAIL_DOWNLOADS_MIN_KB=5120 # ~5MB threshold
|
||||||
|
|
||||||
|
_opt_get_dir_size_kb() {
|
||||||
|
local path="$1"
|
||||||
|
[[ -e "$path" ]] || { echo 0; return; }
|
||||||
|
du -sk "$path" 2> /dev/null | awk '{print $1}' || echo 0
|
||||||
|
}
|
||||||
|
|
||||||
# System maintenance: rebuild databases and flush caches
|
# System maintenance: rebuild databases and flush caches
|
||||||
opt_system_maintenance() {
|
opt_system_maintenance() {
|
||||||
echo -e "${BLUE}${ICON_ARROW}${NC} Rebuilding LaunchServices database..."
|
echo -e "${BLUE}${ICON_ARROW}${NC} Rebuilding LaunchServices database..."
|
||||||
timeout 10 /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill -r -domain local -domain system -domain user > /dev/null 2>&1 || true
|
run_with_timeout 10 /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill -r -domain local -domain system -domain user > /dev/null 2>&1 || true
|
||||||
echo -e "${GREEN}${ICON_SUCCESS}${NC} LaunchServices database rebuilt"
|
echo -e "${GREEN}${ICON_SUCCESS}${NC} LaunchServices database rebuilt"
|
||||||
|
|
||||||
echo -e "${BLUE}${ICON_ARROW}${NC} Clearing DNS cache..."
|
echo -e "${BLUE}${ICON_ARROW}${NC} Clearing DNS cache..."
|
||||||
@@ -29,9 +37,30 @@ opt_system_maintenance() {
|
|||||||
echo -e "${GREEN}${ICON_SUCCESS}${NC} Font cache rebuilt"
|
echo -e "${GREEN}${ICON_SUCCESS}${NC} Font cache rebuilt"
|
||||||
|
|
||||||
echo -e "${BLUE}${ICON_ARROW}${NC} Rebuilding Spotlight index (runs in background)..."
|
echo -e "${BLUE}${ICON_ARROW}${NC} Rebuilding Spotlight index (runs in background)..."
|
||||||
# mdutil triggers background indexing - don't wait
|
local md_status
|
||||||
timeout 10 sudo mdutil -E / > /dev/null 2>&1 || true
|
md_status=$(mdutil -s / 2> /dev/null || echo "")
|
||||||
echo -e "${GREEN}${ICON_SUCCESS}${NC} Spotlight rebuild initiated"
|
if echo "$md_status" | grep -qi "Indexing disabled"; then
|
||||||
|
echo -e "${GRAY}-${NC} Spotlight indexing disabled, skipping rebuild"
|
||||||
|
else
|
||||||
|
# mdutil triggers background indexing - don't wait
|
||||||
|
run_with_timeout 10 sudo mdutil -E / > /dev/null 2>&1 || true
|
||||||
|
echo -e "${GREEN}${ICON_SUCCESS}${NC} Spotlight rebuild initiated"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}${ICON_ARROW}${NC} Refreshing Bluetooth services..."
|
||||||
|
sudo pkill -f blued 2> /dev/null || true
|
||||||
|
echo -e "${GREEN}${ICON_SUCCESS}${NC} Bluetooth controller refreshed"
|
||||||
|
|
||||||
|
if command -v log > /dev/null 2>&1 && [[ "${MO_ENABLE_LOG_CLEANUP:-0}" == "1" ]]; then
|
||||||
|
echo -e "${BLUE}${ICON_ARROW}${NC} Compressing system logs..."
|
||||||
|
if command -v has_sudo_session > /dev/null 2>&1 && ! has_sudo_session; then
|
||||||
|
echo -e "${YELLOW}!${NC} Skipped log compression ${GRAY}(admin session inactive)${NC}"
|
||||||
|
elif run_with_timeout 15 sudo -n log erase --all --force > /dev/null 2>&1; then
|
||||||
|
echo -e "${GREEN}${ICON_SUCCESS}${NC} logarchive trimmed"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}!${NC} Skipped log compression ${GRAY}(requires Full Disk Access)${NC}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cache refresh: update Finder/Safari caches
|
# Cache refresh: update Finder/Safari caches
|
||||||
@@ -68,19 +97,19 @@ opt_maintenance_scripts() {
|
|||||||
|
|
||||||
# Run periodic scripts silently with timeout
|
# Run periodic scripts silently with timeout
|
||||||
if [[ -x "$periodic_cmd" ]]; then
|
if [[ -x "$periodic_cmd" ]]; then
|
||||||
if ! timeout 180 sudo "$periodic_cmd" daily weekly monthly > /dev/null 2>&1; then
|
if ! run_with_timeout 180 sudo "$periodic_cmd" daily weekly monthly > /dev/null 2>&1; then
|
||||||
success=false
|
success=false
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run newsyslog silently with timeout
|
# Run newsyslog silently with timeout
|
||||||
if ! timeout 120 sudo newsyslog > /dev/null 2>&1; then
|
if ! run_with_timeout 120 sudo newsyslog > /dev/null 2>&1; then
|
||||||
success=false
|
success=false
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run repair_packages silently with timeout
|
# Run repair_packages silently with timeout
|
||||||
if [[ -x "/usr/libexec/repair_packages" ]]; then
|
if [[ -x "/usr/libexec/repair_packages" ]]; then
|
||||||
if ! timeout 180 sudo /usr/libexec/repair_packages --repair --standard-pkgs --volume / > /dev/null 2>&1; then
|
if ! run_with_timeout 180 sudo /usr/libexec/repair_packages --repair --standard-pkgs --volume / > /dev/null 2>&1; then
|
||||||
success=false
|
success=false
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -162,6 +191,18 @@ opt_mail_downloads() {
|
|||||||
"$HOME/Library/Mail Downloads|Mail Downloads"
|
"$HOME/Library/Mail Downloads|Mail Downloads"
|
||||||
"$HOME/Library/Containers/com.apple.mail/Data/Library/Mail Downloads|Mail Container Downloads"
|
"$HOME/Library/Containers/com.apple.mail/Data/Library/Mail Downloads|Mail Container Downloads"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
local total_kb=0
|
||||||
|
for target in "${mail_dirs[@]}"; do
|
||||||
|
IFS='|' read -r target_path _ <<< "$target"
|
||||||
|
total_kb=$((total_kb + $(_opt_get_dir_size_kb "$target_path")))
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $total_kb -lt $MAIL_DOWNLOADS_MIN_KB ]]; then
|
||||||
|
echo -e "${GRAY}-${NC} Only $(bytes_to_human $((total_kb * 1024))) detected, skipping cleanup"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
for target in "${mail_dirs[@]}"; do
|
for target in "${mail_dirs[@]}"; do
|
||||||
IFS='|' read -r target_path label <<< "$target"
|
IFS='|' read -r target_path label <<< "$target"
|
||||||
cleanup_path "$target_path" "$label"
|
cleanup_path "$target_path" "$label"
|
||||||
@@ -220,16 +261,16 @@ opt_startup_cache() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$macos_version" -ge 11 ]] || [[ "$(uname -m)" == "arm64" ]]; then
|
if [[ "$macos_version" -ge 11 ]] || [[ "$(uname -m)" == "arm64" ]]; then
|
||||||
if ! timeout 120 sudo kextcache -i / > /dev/null 2>&1; then
|
if ! run_with_timeout 120 sudo kextcache -i / > /dev/null 2>&1; then
|
||||||
success=false
|
success=false
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if ! timeout 180 sudo kextcache -i / > /dev/null 2>&1; then
|
if ! run_with_timeout 180 sudo kextcache -i / > /dev/null 2>&1; then
|
||||||
success=false
|
success=false
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sudo rm -rf /System/Library/PrelinkedKernels/* > /dev/null 2>&1 || true
|
sudo rm -rf /System/Library/PrelinkedKernels/* > /dev/null 2>&1 || true
|
||||||
timeout 120 sudo kextcache -system-prelinked-kernel > /dev/null 2>&1 || true
|
run_with_timeout 120 sudo kextcache -system-prelinked-kernel > /dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -t 1 ]]; then
|
if [[ -t 1 ]]; then
|
||||||
@@ -262,7 +303,7 @@ opt_local_snapshots() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local success=false
|
local success=false
|
||||||
if timeout 180 sudo tmutil thinlocalsnapshots / 9999999999 4 > /dev/null 2>&1; then
|
if run_with_timeout 180 sudo tmutil thinlocalsnapshots / 9999999999 4 > /dev/null 2>&1; then
|
||||||
success=true
|
success=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,17 @@ format_brew_update_label() {
|
|||||||
printf " • Homebrew %s" "$detail_str"
|
printf " • Homebrew %s" "$detail_str"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
brew_has_outdated() {
|
||||||
|
local kind="${1:-formula}"
|
||||||
|
command -v brew > /dev/null 2>&1 || return 1
|
||||||
|
|
||||||
|
if [[ "$kind" == "cask" ]]; then
|
||||||
|
brew outdated --cask --quiet 2> /dev/null | grep -q .
|
||||||
|
else
|
||||||
|
brew outdated --quiet 2> /dev/null | grep -q .
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Ask user if they want to update
|
# Ask user if they want to update
|
||||||
# Returns: 0 if yes, 1 if no
|
# Returns: 0 if yes, 1 if no
|
||||||
ask_for_updates() {
|
ask_for_updates() {
|
||||||
@@ -62,7 +73,7 @@ ask_for_updates() {
|
|||||||
echo -e "$item"
|
echo -e "$item"
|
||||||
done
|
done
|
||||||
echo ""
|
echo ""
|
||||||
echo -ne "${YELLOW}Update all now?${NC} ${GRAY}Enter yes / ESC skip${NC}: "
|
echo -ne "${YELLOW}Update all now?${NC} ${GRAY}Enter confirm / ESC cancel${NC}: "
|
||||||
|
|
||||||
local key
|
local key
|
||||||
if ! key=$(read_key); then
|
if ! key=$(read_key); then
|
||||||
@@ -124,28 +135,80 @@ perform_updates() {
|
|||||||
|
|
||||||
# Update Homebrew formulae
|
# Update Homebrew formulae
|
||||||
if ((brew_formula > 0)); then
|
if ((brew_formula > 0)); then
|
||||||
echo -e "${BLUE}Updating Homebrew formulae...${NC}"
|
if ! brew_has_outdated "formula"; then
|
||||||
if brew upgrade 2>&1 | grep -v "^==>" | grep -v "^Warning:" || true; then
|
echo -e "${GRAY}-${NC} Homebrew formulae already up to date"
|
||||||
echo -e "${GREEN}✓${NC} Homebrew formulae updated"
|
((total_count--))
|
||||||
reset_brew_cache
|
echo ""
|
||||||
((updated_count++))
|
|
||||||
else
|
else
|
||||||
echo -e "${RED}✗${NC} Homebrew formula update failed"
|
echo -e "${BLUE}Updating Homebrew formulae...${NC}"
|
||||||
|
local spinner_started=false
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
start_inline_spinner "Running brew upgrade"
|
||||||
|
spinner_started=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
local brew_output=""
|
||||||
|
local brew_status=0
|
||||||
|
if ! brew_output=$(brew upgrade 2>&1); then
|
||||||
|
brew_status=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$spinner_started" == "true" ]]; then
|
||||||
|
stop_inline_spinner
|
||||||
|
fi
|
||||||
|
|
||||||
|
local filtered_output
|
||||||
|
filtered_output=$(echo "$brew_output" | grep -Ev "^(==>|Warning:)" || true)
|
||||||
|
[[ -n "$filtered_output" ]] && echo "$filtered_output"
|
||||||
|
|
||||||
|
if [[ ${brew_status:-0} -eq 0 ]]; then
|
||||||
|
echo -e "${GREEN}✓${NC} Homebrew formulae updated"
|
||||||
|
reset_brew_cache
|
||||||
|
((updated_count++))
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗${NC} Homebrew formula update failed"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
fi
|
fi
|
||||||
echo ""
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Update Homebrew casks
|
# Update Homebrew casks
|
||||||
if ((brew_cask > 0)); then
|
if ((brew_cask > 0)); then
|
||||||
echo -e "${BLUE}Updating Homebrew casks...${NC}"
|
if ! brew_has_outdated "cask"; then
|
||||||
if brew upgrade --cask 2>&1 | grep -v "^==>" | grep -v "^Warning:" || true; then
|
echo -e "${GRAY}-${NC} Homebrew casks already up to date"
|
||||||
echo -e "${GREEN}✓${NC} Homebrew casks updated"
|
((total_count--))
|
||||||
reset_brew_cache
|
echo ""
|
||||||
((updated_count++))
|
|
||||||
else
|
else
|
||||||
echo -e "${RED}✗${NC} Homebrew cask update failed"
|
echo -e "${BLUE}Updating Homebrew casks...${NC}"
|
||||||
|
local spinner_started=false
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
start_inline_spinner "Running brew upgrade --cask"
|
||||||
|
spinner_started=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
local brew_output=""
|
||||||
|
local brew_status=0
|
||||||
|
if ! brew_output=$(brew upgrade --cask 2>&1); then
|
||||||
|
brew_status=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$spinner_started" == "true" ]]; then
|
||||||
|
stop_inline_spinner
|
||||||
|
fi
|
||||||
|
|
||||||
|
local filtered_output
|
||||||
|
filtered_output=$(echo "$brew_output" | grep -Ev "^(==>|Warning:)" || true)
|
||||||
|
[[ -n "$filtered_output" ]] && echo "$filtered_output"
|
||||||
|
|
||||||
|
if [[ ${brew_status:-0} -eq 0 ]]; then
|
||||||
|
echo -e "${GREEN}✓${NC} Homebrew casks updated"
|
||||||
|
reset_brew_cache
|
||||||
|
((updated_count++))
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗${NC} Homebrew cask update failed"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
fi
|
fi
|
||||||
echo ""
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Update App Store apps
|
# Update App Store apps
|
||||||
@@ -154,7 +217,7 @@ perform_updates() {
|
|||||||
# Check sudo access
|
# Check sudo access
|
||||||
if ! has_sudo_session; then
|
if ! has_sudo_session; then
|
||||||
if ! ensure_sudo_session "Software updates require admin access"; then
|
if ! ensure_sudo_session "Software updates require admin access"; then
|
||||||
echo -e "${YELLOW}⚠${NC} Skipping App Store updates (admin authentication required)"
|
echo -e "${YELLOW}${ICON_WARNING}${NC} Skipping App Store updates (admin authentication required)"
|
||||||
echo ""
|
echo ""
|
||||||
((total_count--))
|
((total_count--))
|
||||||
if [[ -n "${MACOS_UPDATE_AVAILABLE:-}" && "${MACOS_UPDATE_AVAILABLE}" == "true" ]]; then
|
if [[ -n "${MACOS_UPDATE_AVAILABLE:-}" && "${MACOS_UPDATE_AVAILABLE}" == "true" ]]; then
|
||||||
@@ -171,7 +234,7 @@ perform_updates() {
|
|||||||
# Update macOS
|
# Update macOS
|
||||||
if [[ -n "${MACOS_UPDATE_AVAILABLE:-}" && "${MACOS_UPDATE_AVAILABLE}" == "true" && "$macos_handled_via_appstore" != "true" ]]; then
|
if [[ -n "${MACOS_UPDATE_AVAILABLE:-}" && "${MACOS_UPDATE_AVAILABLE}" == "true" && "$macos_handled_via_appstore" != "true" ]]; then
|
||||||
if ! has_sudo_session; then
|
if ! has_sudo_session; then
|
||||||
echo -e "${YELLOW}⚠${NC} Skipping macOS updates (admin authentication required)"
|
echo -e "${YELLOW}${ICON_WARNING}${NC} Skipping macOS updates (admin authentication required)"
|
||||||
echo ""
|
echo ""
|
||||||
else
|
else
|
||||||
_perform_macos_update
|
_perform_macos_update
|
||||||
@@ -260,7 +323,7 @@ _perform_macos_update() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if grep -qi "restart" "$macos_log" 2>/dev/null; then
|
if grep -qi "restart" "$macos_log" 2>/dev/null; then
|
||||||
echo -e "${YELLOW}⚠${NC} Restart required to complete update"
|
echo -e "${YELLOW}${ICON_WARNING}${NC} Restart required to complete update"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$macos_log" 2>/dev/null || true
|
rm -f "$macos_log" 2>/dev/null || true
|
||||||
|
|||||||
26
tests/optimization_tasks.bats
Normal file
26
tests/optimization_tasks.bats
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
|
||||||
|
setup_file() {
|
||||||
|
PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)"
|
||||||
|
export PROJECT_ROOT
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "run_with_timeout succeeds without GNU timeout" {
|
||||||
|
run bash --noprofile --norc -c '
|
||||||
|
set -euo pipefail
|
||||||
|
PATH="/usr/bin:/bin"
|
||||||
|
source "'"$PROJECT_ROOT"'/lib/common.sh"
|
||||||
|
run_with_timeout 1 sleep 0.1
|
||||||
|
'
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "run_with_timeout enforces timeout and returns 124" {
|
||||||
|
run bash --noprofile --norc -c '
|
||||||
|
set -euo pipefail
|
||||||
|
PATH="/usr/bin:/bin"
|
||||||
|
source "'"$PROJECT_ROOT"'/lib/common.sh"
|
||||||
|
run_with_timeout 1 sleep 5
|
||||||
|
'
|
||||||
|
[ "$status" -eq 124 ]
|
||||||
|
}
|
||||||
@@ -97,3 +97,20 @@ setup() {
|
|||||||
fi
|
fi
|
||||||
[ "$status" -ne 0 ]
|
[ "$status" -ne 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "mo clean --whitelist persists selections" {
|
||||||
|
whitelist_file="$HOME/.config/mole/whitelist"
|
||||||
|
mkdir -p "$(dirname "$whitelist_file")"
|
||||||
|
|
||||||
|
run bash --noprofile --norc -c "cd '$PROJECT_ROOT'; printf \$'\\n' | HOME='$HOME' ./mo clean --whitelist"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
grep -q "\\.m2/repository" "$whitelist_file"
|
||||||
|
|
||||||
|
run bash --noprofile --norc -c "cd '$PROJECT_ROOT'; printf \$' \\n' | HOME='$HOME' ./mo clean --whitelist"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
! grep -q "\\.m2/repository" "$whitelist_file"
|
||||||
|
|
||||||
|
run bash --noprofile --norc -c "cd '$PROJECT_ROOT'; printf \$'\\n' | HOME='$HOME' ./mo clean --whitelist"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
! grep -q "\\.m2/repository" "$whitelist_file"
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user