bin/optimize.sh
Rama main · réplica de lectura del repositorio original.
#!/bin/bash
# Mole - Optimize command.
# Runs system maintenance tasks.
# Supports dry-run where applicable.
set -euo pipefail
# User state and installed tools must never run with inherited root privileges.
# Individual maintenance operations request administrator access themselves.
if [[ "$EUID" -eq 0 ]]; then
printf '%s\n' 'Run Mole without sudo; it requests administrator access when needed.' >&2
exit 1
fi
# Fix locale issues.
export LC_ALL=C
export LANG=C
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
source "$SCRIPT_DIR/lib/core/common.sh"
# Clean temp files on exit.
trap cleanup_temp_files EXIT INT TERM
source "$SCRIPT_DIR/lib/core/sudo.sh"
source "$SCRIPT_DIR/lib/optimize/diagnostics.sh"
source "$SCRIPT_DIR/lib/optimize/maintenance.sh"
source "$SCRIPT_DIR/lib/optimize/catalog.sh"
source "$SCRIPT_DIR/lib/optimize/tasks.sh"
source "$SCRIPT_DIR/lib/check/health_json.sh"
source "$SCRIPT_DIR/lib/manage/whitelist.sh"
print_header() {
printf '\n'
echo -e "${PURPLE_BOLD}Optimize${NC}"
}
# Extract a simple numeric value from JSON by key without a jq dependency.
json_get_value() {
local json="$1"
local key="$2"
local value
value=$(echo "$json" | grep -o "\"${key}\"[[:space:]]*:[[:space:]]*[0-9.]*" | head -1 | sed 's/.*:[[:space:]]*//')
echo "${value:-0}"
}
# Validate JSON has expected structure (basic check).
json_validate() {
local json="$1"
# Check for required keys
[[ "$json" == *'"memory_used_gb"'* ]] &&
[[ "$json" == *'"optimizations"'* ]] &&
[[ "$json" == *'{'* ]] && [[ "$json" == *'}'* ]]
}
show_optimization_summary() {
local total
total=$(optimize_outcome_total)
if ((total == 0)); then
return
fi
local summary_title
local -a summary_details=()
local applied unchanged skipped unavailable attention failed
applied=$(optimize_outcome_count "$MOLE_OPTIMIZE_OUTCOME_APPLIED")
unchanged=$(optimize_outcome_count "$MOLE_OPTIMIZE_OUTCOME_UNCHANGED")
skipped=$(optimize_outcome_count "$MOLE_OPTIMIZE_OUTCOME_SKIPPED")
unavailable=$(optimize_outcome_count "$MOLE_OPTIMIZE_OUTCOME_UNAVAILABLE")
attention=$(optimize_outcome_count "$MOLE_OPTIMIZE_OUTCOME_ATTENTION")
failed=$(optimize_outcome_count "$MOLE_OPTIMIZE_OUTCOME_FAILED")
local -a outcome_parts=()
[[ $unchanged -gt 0 ]] && outcome_parts+=("$unchanged unchanged")
[[ $skipped -gt 0 ]] && outcome_parts+=("$skipped skipped")
[[ $unavailable -gt 0 ]] && outcome_parts+=("$unavailable unavailable")
[[ $attention -gt 0 ]] && outcome_parts+=("$attention need attention")
[[ $failed -gt 0 ]] && outcome_parts+=("$failed failed")
local outcome_line=""
if [[ ${#outcome_parts[@]} -gt 0 ]]; then
outcome_line="${outcome_parts[0]}"
local index
for ((index = 1; index < ${#outcome_parts[@]}; index++)); do
outcome_line+=" | ${outcome_parts[$index]}"
done
fi
if [[ "${MOLE_DRY_RUN:-0}" == "1" ]]; then
summary_title="Dry Run Complete, No Changes Made"
summary_details+=("Would apply ${YELLOW}${applied}${NC} optimizations")
[[ -n "$outcome_line" ]] && summary_details+=("$outcome_line")
summary_details+=("Run without ${YELLOW}--dry-run${NC} to apply these changes")
else
summary_title="Optimization Complete"
local cache_kb="${OPTIMIZE_CACHE_CLEANED_KB:-0}"
local db_count="${OPTIMIZE_DATABASES_COUNT:-0}"
local config_count="${OPTIMIZE_CONFIGS_REPAIRED:-0}"
local key_stat=""
if [[ "$cache_kb" =~ ^[0-9]+$ ]] && [[ "$cache_kb" -gt 0 ]]; then
local cache_human
cache_human=$(bytes_to_human "$((cache_kb * 1024))")
key_stat="${cache_human} cache cleaned"
elif [[ "$db_count" =~ ^[0-9]+$ ]] && [[ "$db_count" -gt 0 ]]; then
key_stat="${db_count} databases optimized"
elif [[ "$config_count" =~ ^[0-9]+$ ]] && [[ "$config_count" -gt 0 ]]; then
key_stat="${config_count} configs repaired"
fi
if [[ -n "$key_stat" ]]; then
summary_details+=("Applied ${GREEN}${applied}${NC} optimizations, ${key_stat}")
else
summary_details+=("Applied ${GREEN}${applied}${NC} optimizations")
fi
[[ -n "$outcome_line" ]] && summary_details+=("$outcome_line")
if [[ $attention -gt 0 || $failed -gt 0 ]]; then
summary_details+=("Review the warnings above")
else
summary_details+=("Optimization pass complete")
fi
fi
print_summary_block "$summary_title" "${summary_details[@]}"
}
show_system_health() {
local health_json="$1"
local mem_used=$(json_get_value "$health_json" "memory_used_gb")
local mem_total=$(json_get_value "$health_json" "memory_total_gb")
local disk_used=$(json_get_value "$health_json" "disk_used_gb")
local disk_total=$(json_get_value "$health_json" "disk_total_gb")
local disk_percent=$(json_get_value "$health_json" "disk_used_percent")
local uptime=$(json_get_value "$health_json" "uptime_days")
mem_used=${mem_used:-0}
mem_total=${mem_total:-0}
disk_used=${disk_used:-0}
disk_total=${disk_total:-0}
disk_percent=${disk_percent:-0}
uptime=${uptime:-0}
# printf parses float arguments with the locale's decimal separator, so
# comma-decimal locales reject dot values like "5.70" (#1220). Round in
# C-locale awk and print plain strings to avoid float parsing entirely.
local rounded
rounded=$(LC_ALL=C awk -v mu="$mem_used" -v mt="$mem_total" -v du="$disk_used" -v dt="$disk_total" -v ut="$uptime" \
'BEGIN { printf "%.0f %.0f %.0f %.0f %.0f", mu, mt, du, dt, ut }' 2> /dev/null || echo "0 0 0 0 0")
read -r mem_used mem_total disk_used disk_total uptime <<< "$rounded"
printf "${ICON_ADMIN} System %s/%s GB RAM | %s/%s GB Disk | Uptime %sd\n" \
"$mem_used" "$mem_total" "$disk_used" "$disk_total" "$uptime"
}
announce_action() {
local name="$1"
if [[ "${FIRST_ACTION:-true}" == "true" ]]; then
export FIRST_ACTION=false
else
echo ""
fi
echo -e "${BLUE}${ICON_ARROW} ${name}${NC}"
}
cleanup_all() {
local exit_status="${1:-0}"
stop_inline_spinner 2> /dev/null || true
stop_sudo_session
cleanup_temp_files
# Log session end
local applied=0
local failed=0
if declare -F optimize_outcome_count > /dev/null; then
applied=$(optimize_outcome_count "$MOLE_OPTIMIZE_OUTCOME_APPLIED")
failed=$(optimize_outcome_count "$MOLE_OPTIMIZE_OUTCOME_FAILED")
local failed_action
while IFS= read -r failed_action; do
[[ -n "$failed_action" ]] || continue
log_operation "optimize" "TASK_FAILED" "$failed_action" "task outcome"
done < <(optimize_failed_actions)
# Applied tasks changed system state (SQLite, defaults, Spotlight,
# DNS); a preview applied nothing, so it records none.
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
local applied_action
while IFS= read -r applied_action; do
[[ -n "$applied_action" ]] || continue
log_operation "optimize" "REBUILT" "$applied_action" "task outcome"
done < <(optimize_actions_with_outcome "$MOLE_OPTIMIZE_OUTCOME_APPLIED")
fi
fi
if [[ "$exit_status" -ne 0 && "$failed" -eq 0 ]]; then
local failure_action="session"
[[ "$exit_status" -eq 130 ]] && failure_action="interrupted"
log_operation "optimize" "TASK_FAILED" "$failure_action" "exit status $exit_status"
fi
log_operation_session_end "optimize" "$applied" "0"
}
handle_interrupt() {
trap - EXIT
cleanup_all 130
exit 130
}
main() {
# Set current command for operation logging
export MOLE_CURRENT_COMMAND="optimize"
local health_json
for arg in "$@"; do
case "$arg" in
"--help" | "-h")
show_optimize_help
exit 0
;;
"--debug")
export MO_DEBUG=1
;;
"--dry-run" | "-n")
export MOLE_DRY_RUN=1
;;
"--whitelist")
manage_whitelist "optimize"
exit 0
;;
*)
echo "Unknown optimize option: $arg" >&2
echo "Use 'mo optimize --help' for supported options." >&2
exit 1
;;
esac
done
log_operation_session_start "optimize"
trap 'cleanup_all "$?"' EXIT
trap handle_interrupt INT TERM
if [[ -t 1 ]]; then
clear_screen
fi
print_header
# Dry-run indicator.
if [[ "${MOLE_DRY_RUN:-0}" == "1" ]]; then
echo -e "${YELLOW}${ICON_DRY_RUN} DRY RUN MODE${NC}, No files will be modified\n"
fi
if ! command -v bc > /dev/null 2>&1; then
echo -e "${YELLOW}${ICON_ERROR}${NC} Missing dependency: bc"
echo -e "${GRAY}Install with: ${GREEN}brew install bc${NC}"
exit 1
fi
if [[ -t 1 ]]; then
start_inline_spinner "Collecting system info..."
fi
if ! health_json=$(generate_health_json 2> /dev/null); then
if [[ -t 1 ]]; then
stop_inline_spinner
fi
echo ""
log_error "Failed to collect system health data"
exit 1
fi
if ! json_validate "$health_json"; then
if [[ -t 1 ]]; then
stop_inline_spinner
fi
echo ""
log_error "Invalid system health data format"
echo -e "${GRAY}${ICON_REVIEW}${NC} Check if awk, sysctl, and df commands are available"
exit 1
fi
if [[ -t 1 ]]; then
stop_inline_spinner
fi
load_whitelist "optimize" || exit 1
if [[ ${#CURRENT_WHITELIST_PATTERNS[@]} -gt 0 ]]; then
local count=${#CURRENT_WHITELIST_PATTERNS[@]}
if [[ $count -le 3 ]]; then
local patterns_list=$(
IFS=', '
echo "${CURRENT_WHITELIST_PATTERNS[*]}"
)
echo -e "${ICON_ADMIN} Active Whitelist: ${patterns_list}"
fi
fi
show_system_health "$health_json"
run_optimize_diagnostics
echo ""
# Track sudo availability so individual tasks can skip cleanly when admin
# access was denied. Without this, every sudo task re-prompts for the
# password and half-runs after a refusal. Default true in dry-run so the
# task list still expands fully for inspection.
export MOLE_OPTIMIZE_SUDO_AVAILABLE="false"
if [[ "${MOLE_DRY_RUN:-0}" == "1" ]]; then
MOLE_OPTIMIZE_SUDO_AVAILABLE="true"
elif ensure_sudo_session "System optimization requires admin access"; then
MOLE_OPTIMIZE_SUDO_AVAILABLE="true"
else
opt_msg "Skipping sudo-required optimizations: admin access not granted"
fi
export FIRST_ACTION=true
optimize_outcomes_reset
local index action health_name
for ((index = 0; index < ${#MOLE_OPTIMIZE_ACTIONS[@]}; index++)); do
action=${MOLE_OPTIMIZE_ACTIONS[$index]}
health_name=${MOLE_OPTIMIZE_HEALTH_NAMES[$index]}
announce_action "$health_name"
execute_optimization "$action"
done
if [[ "$(optimize_outcome_total)" -ne ${#MOLE_OPTIMIZE_ACTIONS[@]} ]]; then
log_error "Optimize task outcomes are incomplete"
return 1
fi
show_optimization_summary
printf '\n'
optimize_outcomes_succeeded
}
main "$@"