mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 11:31:46 +00:00
feat: log cleanup operations for troubleshooting
This commit is contained in:
@@ -76,6 +76,7 @@ mo purge --paths # Configure project scan directories
|
|||||||
- **Safety**: Built with strict protections. See [Security Audit](SECURITY_AUDIT.md). Preview changes with `mo clean --dry-run`.
|
- **Safety**: Built with strict protections. See [Security Audit](SECURITY_AUDIT.md). Preview changes with `mo clean --dry-run`.
|
||||||
- **Be Careful**: Although safe by design, file deletion is permanent. Please review operations carefully.
|
- **Be Careful**: Although safe by design, file deletion is permanent. Please review operations carefully.
|
||||||
- **Debug Mode**: Use `--debug` for detailed logs (e.g., `mo clean --debug`). Combine with `--dry-run` for comprehensive preview including risk levels and file details.
|
- **Debug Mode**: Use `--debug` for detailed logs (e.g., `mo clean --debug`). Combine with `--dry-run` for comprehensive preview including risk levels and file details.
|
||||||
|
- **Operation Log**: File operations are logged to `~/.config/mole/operations.log` for troubleshooting. Disable with `MO_NO_OPLOG=1`.
|
||||||
- **Navigation**: Supports arrow keys and Vim bindings (`h/j/k/l`).
|
- **Navigation**: Supports arrow keys and Vim bindings (`h/j/k/l`).
|
||||||
- **Status Shortcuts**: In `mo status`, press `k` to toggle cat visibility and save preference, `q` to quit.
|
- **Status Shortcuts**: In `mo status`, press `k` to toggle cat visibility and save preference, `q` to quit.
|
||||||
- **Configuration**: Run `mo touchid` for Touch ID sudo, `mo completion` for shell tab completion, `mo clean --whitelist` to manage protected paths.
|
- **Configuration**: Run `mo touchid` for Touch ID sudo, `mo completion` for shell tab completion, `mo clean --whitelist` to manage protected paths.
|
||||||
|
|||||||
@@ -365,6 +365,7 @@ safe_clean() {
|
|||||||
if should_protect_path "$path"; then
|
if should_protect_path "$path"; then
|
||||||
skip=true
|
skip=true
|
||||||
((skipped_count++))
|
((skipped_count++))
|
||||||
|
log_operation "clean" "SKIPPED" "$path" "protected"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[[ "$skip" == "true" ]] && continue
|
[[ "$skip" == "true" ]] && continue
|
||||||
@@ -372,6 +373,7 @@ safe_clean() {
|
|||||||
if is_path_whitelisted "$path"; then
|
if is_path_whitelisted "$path"; then
|
||||||
skip=true
|
skip=true
|
||||||
((skipped_count++))
|
((skipped_count++))
|
||||||
|
log_operation "clean" "SKIPPED" "$path" "whitelist"
|
||||||
fi
|
fi
|
||||||
[[ "$skip" == "true" ]] && continue
|
[[ "$skip" == "true" ]] && continue
|
||||||
[[ -e "$path" ]] && existing_paths+=("$path")
|
[[ -e "$path" ]] && existing_paths+=("$path")
|
||||||
@@ -699,6 +701,10 @@ safe_clean() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
start_cleanup() {
|
start_cleanup() {
|
||||||
|
# Set current command for operation logging
|
||||||
|
export MOLE_CURRENT_COMMAND="clean"
|
||||||
|
log_operation_session_start "clean"
|
||||||
|
|
||||||
if [[ -t 1 ]]; then
|
if [[ -t 1 ]]; then
|
||||||
printf '\033[2J\033[H'
|
printf '\033[2J\033[H'
|
||||||
fi
|
fi
|
||||||
@@ -1065,6 +1071,9 @@ perform_cleanup() {
|
|||||||
set -e
|
set -e
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Log session end with summary
|
||||||
|
log_operation_session_end "clean" "$files_cleaned" "$total_size_cleaned"
|
||||||
|
|
||||||
print_summary_block "$summary_heading" "${summary_details[@]}"
|
print_summary_block "$summary_heading" "${summary_details[@]}"
|
||||||
printf '\n'
|
printf '\n'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,12 +139,12 @@ show_optimization_summary() {
|
|||||||
show_system_health() {
|
show_system_health() {
|
||||||
local health_json="$1"
|
local health_json="$1"
|
||||||
|
|
||||||
local mem_used=$(echo "$health_json" | jq -r '.memory_used_gb // 0' 2> /dev/null || echo "0")
|
local mem_used=$(echo "$health_json" | jq -r '.memory_used_gb // 0' 2>/dev/null || echo "0")
|
||||||
local mem_total=$(echo "$health_json" | jq -r '.memory_total_gb // 0' 2> /dev/null || echo "0")
|
local mem_total=$(echo "$health_json" | jq -r '.memory_total_gb // 0' 2>/dev/null || echo "0")
|
||||||
local disk_used=$(echo "$health_json" | jq -r '.disk_used_gb // 0' 2> /dev/null || echo "0")
|
local disk_used=$(echo "$health_json" | jq -r '.disk_used_gb // 0' 2>/dev/null || echo "0")
|
||||||
local disk_total=$(echo "$health_json" | jq -r '.disk_total_gb // 0' 2> /dev/null || echo "0")
|
local disk_total=$(echo "$health_json" | jq -r '.disk_total_gb // 0' 2>/dev/null || echo "0")
|
||||||
local disk_percent=$(echo "$health_json" | jq -r '.disk_used_percent // 0' 2> /dev/null || echo "0")
|
local disk_percent=$(echo "$health_json" | jq -r '.disk_used_percent // 0' 2>/dev/null || echo "0")
|
||||||
local uptime=$(echo "$health_json" | jq -r '.uptime_days // 0' 2> /dev/null || echo "0")
|
local uptime=$(echo "$health_json" | jq -r '.uptime_days // 0' 2>/dev/null || echo "0")
|
||||||
|
|
||||||
mem_used=${mem_used:-0}
|
mem_used=${mem_used:-0}
|
||||||
mem_total=${mem_total:-0}
|
mem_total=${mem_total:-0}
|
||||||
@@ -159,7 +159,7 @@ show_system_health() {
|
|||||||
|
|
||||||
parse_optimizations() {
|
parse_optimizations() {
|
||||||
local health_json="$1"
|
local health_json="$1"
|
||||||
echo "$health_json" | jq -c '.optimizations[]' 2> /dev/null
|
echo "$health_json" | jq -c '.optimizations[]' 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
announce_action() {
|
announce_action() {
|
||||||
@@ -177,12 +177,12 @@ announce_action() {
|
|||||||
|
|
||||||
touchid_configured() {
|
touchid_configured() {
|
||||||
local pam_file="/etc/pam.d/sudo"
|
local pam_file="/etc/pam.d/sudo"
|
||||||
[[ -f "$pam_file" ]] && grep -q "pam_tid.so" "$pam_file" 2> /dev/null
|
[[ -f "$pam_file" ]] && grep -q "pam_tid.so" "$pam_file" 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
touchid_supported() {
|
touchid_supported() {
|
||||||
if command -v bioutil > /dev/null 2>&1; then
|
if command -v bioutil >/dev/null 2>&1; then
|
||||||
if bioutil -r 2> /dev/null | grep -qi "Touch ID"; then
|
if bioutil -r 2>/dev/null | grep -qi "Touch ID"; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -272,7 +272,7 @@ ask_for_security_fixes() {
|
|||||||
echo ""
|
echo ""
|
||||||
echo -e "${BLUE}SECURITY FIXES${NC}"
|
echo -e "${BLUE}SECURITY FIXES${NC}"
|
||||||
for entry in "${SECURITY_FIXES[@]}"; do
|
for entry in "${SECURITY_FIXES[@]}"; do
|
||||||
IFS='|' read -r _ label <<< "$entry"
|
IFS='|' read -r _ label <<<"$entry"
|
||||||
echo -e " ${ICON_LIST} $label"
|
echo -e " ${ICON_LIST} $label"
|
||||||
done
|
done
|
||||||
echo ""
|
echo ""
|
||||||
@@ -299,7 +299,7 @@ ask_for_security_fixes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply_firewall_fix() {
|
apply_firewall_fix() {
|
||||||
if sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate on > /dev/null 2>&1; then
|
if sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate on >/dev/null 2>&1; then
|
||||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Firewall enabled"
|
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Firewall enabled"
|
||||||
FIREWALL_DISABLED=false
|
FIREWALL_DISABLED=false
|
||||||
return 0
|
return 0
|
||||||
@@ -309,7 +309,7 @@ apply_firewall_fix() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply_gatekeeper_fix() {
|
apply_gatekeeper_fix() {
|
||||||
if sudo spctl --master-enable 2> /dev/null; then
|
if sudo spctl --master-enable 2>/dev/null; then
|
||||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Gatekeeper enabled"
|
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Gatekeeper enabled"
|
||||||
GATEKEEPER_DISABLED=false
|
GATEKEEPER_DISABLED=false
|
||||||
return 0
|
return 0
|
||||||
@@ -333,17 +333,17 @@ perform_security_fixes() {
|
|||||||
|
|
||||||
local applied=0
|
local applied=0
|
||||||
for entry in "${SECURITY_FIXES[@]}"; do
|
for entry in "${SECURITY_FIXES[@]}"; do
|
||||||
IFS='|' read -r action _ <<< "$entry"
|
IFS='|' read -r action _ <<<"$entry"
|
||||||
case "$action" in
|
case "$action" in
|
||||||
firewall)
|
firewall)
|
||||||
apply_firewall_fix && ((applied++))
|
apply_firewall_fix && ((applied++))
|
||||||
;;
|
;;
|
||||||
gatekeeper)
|
gatekeeper)
|
||||||
apply_gatekeeper_fix && ((applied++))
|
apply_gatekeeper_fix && ((applied++))
|
||||||
;;
|
;;
|
||||||
touchid)
|
touchid)
|
||||||
apply_touchid_fix && ((applied++))
|
apply_touchid_fix && ((applied++))
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -354,9 +354,11 @@ perform_security_fixes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cleanup_all() {
|
cleanup_all() {
|
||||||
stop_inline_spinner 2> /dev/null || true
|
stop_inline_spinner 2>/dev/null || true
|
||||||
stop_sudo_session
|
stop_sudo_session
|
||||||
cleanup_temp_files
|
cleanup_temp_files
|
||||||
|
# Log session end
|
||||||
|
log_operation_session_end "optimize" "${OPTIMIZE_SAFE_COUNT:-0}" "0"
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_interrupt() {
|
handle_interrupt() {
|
||||||
@@ -365,22 +367,27 @@ handle_interrupt() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
|
# Set current command for operation logging
|
||||||
|
export MOLE_CURRENT_COMMAND="optimize"
|
||||||
|
|
||||||
local health_json
|
local health_json
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
"--debug")
|
"--debug")
|
||||||
export MO_DEBUG=1
|
export MO_DEBUG=1
|
||||||
;;
|
;;
|
||||||
"--dry-run")
|
"--dry-run")
|
||||||
export MOLE_DRY_RUN=1
|
export MOLE_DRY_RUN=1
|
||||||
;;
|
;;
|
||||||
"--whitelist")
|
"--whitelist")
|
||||||
manage_whitelist "optimize"
|
manage_whitelist "optimize"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
log_operation_session_start "optimize"
|
||||||
|
|
||||||
trap cleanup_all EXIT
|
trap cleanup_all EXIT
|
||||||
trap handle_interrupt INT TERM
|
trap handle_interrupt INT TERM
|
||||||
|
|
||||||
@@ -394,13 +401,13 @@ main() {
|
|||||||
echo -e "${YELLOW}${ICON_DRY_RUN} DRY RUN MODE${NC}, No files will be modified\n"
|
echo -e "${YELLOW}${ICON_DRY_RUN} DRY RUN MODE${NC}, No files will be modified\n"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! command -v jq > /dev/null 2>&1; then
|
if ! command -v jq >/dev/null 2>&1; then
|
||||||
echo -e "${YELLOW}${ICON_ERROR}${NC} Missing dependency: jq"
|
echo -e "${YELLOW}${ICON_ERROR}${NC} Missing dependency: jq"
|
||||||
echo -e "${GRAY}Install with: ${GREEN}brew install jq${NC}"
|
echo -e "${GRAY}Install with: ${GREEN}brew install jq${NC}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! command -v bc > /dev/null 2>&1; then
|
if ! command -v bc >/dev/null 2>&1; then
|
||||||
echo -e "${YELLOW}${ICON_ERROR}${NC} Missing dependency: bc"
|
echo -e "${YELLOW}${ICON_ERROR}${NC} Missing dependency: bc"
|
||||||
echo -e "${GRAY}Install with: ${GREEN}brew install bc${NC}"
|
echo -e "${GRAY}Install with: ${GREEN}brew install bc${NC}"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -410,7 +417,7 @@ main() {
|
|||||||
start_inline_spinner "Collecting system info..."
|
start_inline_spinner "Collecting system info..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! health_json=$(generate_health_json 2> /dev/null); then
|
if ! health_json=$(generate_health_json 2>/dev/null); then
|
||||||
if [[ -t 1 ]]; then
|
if [[ -t 1 ]]; then
|
||||||
stop_inline_spinner
|
stop_inline_spinner
|
||||||
fi
|
fi
|
||||||
@@ -419,7 +426,7 @@ main() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! echo "$health_json" | jq empty 2> /dev/null; then
|
if ! echo "$health_json" | jq empty 2>/dev/null; then
|
||||||
if [[ -t 1 ]]; then
|
if [[ -t 1 ]]; then
|
||||||
stop_inline_spinner
|
stop_inline_spinner
|
||||||
fi
|
fi
|
||||||
@@ -451,7 +458,7 @@ main() {
|
|||||||
local -a confirm_items=()
|
local -a confirm_items=()
|
||||||
local opts_file
|
local opts_file
|
||||||
opts_file=$(mktemp_file)
|
opts_file=$(mktemp_file)
|
||||||
parse_optimizations "$health_json" > "$opts_file"
|
parse_optimizations "$health_json" >"$opts_file"
|
||||||
|
|
||||||
while IFS= read -r opt_json; do
|
while IFS= read -r opt_json; do
|
||||||
[[ -z "$opt_json" ]] && continue
|
[[ -z "$opt_json" ]] && continue
|
||||||
@@ -469,7 +476,7 @@ main() {
|
|||||||
else
|
else
|
||||||
confirm_items+=("$item")
|
confirm_items+=("$item")
|
||||||
fi
|
fi
|
||||||
done < "$opts_file"
|
done <"$opts_file"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
|
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]]; then
|
||||||
@@ -479,7 +486,7 @@ main() {
|
|||||||
export FIRST_ACTION=true
|
export FIRST_ACTION=true
|
||||||
if [[ ${#safe_items[@]} -gt 0 ]]; then
|
if [[ ${#safe_items[@]} -gt 0 ]]; then
|
||||||
for item in "${safe_items[@]}"; do
|
for item in "${safe_items[@]}"; do
|
||||||
IFS='|' read -r name desc action path <<< "$item"
|
IFS='|' read -r name desc action path <<<"$item"
|
||||||
announce_action "$name" "$desc" "safe"
|
announce_action "$name" "$desc" "safe"
|
||||||
execute_optimization "$action" "$path"
|
execute_optimization "$action" "$path"
|
||||||
done
|
done
|
||||||
@@ -487,7 +494,7 @@ main() {
|
|||||||
|
|
||||||
if [[ ${#confirm_items[@]} -gt 0 ]]; then
|
if [[ ${#confirm_items[@]} -gt 0 ]]; then
|
||||||
for item in "${confirm_items[@]}"; do
|
for item in "${confirm_items[@]}"; do
|
||||||
IFS='|' read -r name desc action path <<< "$item"
|
IFS='|' read -r name desc action path <<<"$item"
|
||||||
announce_action "$name" "$desc" "confirm"
|
announce_action "$name" "$desc" "confirm"
|
||||||
execute_optimization "$action" "$path"
|
execute_optimization "$action" "$path"
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ note_activity() {
|
|||||||
|
|
||||||
# Main purge function
|
# Main purge function
|
||||||
start_purge() {
|
start_purge() {
|
||||||
|
# Set current command for operation logging
|
||||||
|
export MOLE_CURRENT_COMMAND="purge"
|
||||||
|
log_operation_session_start "purge"
|
||||||
|
|
||||||
# Clear screen for better UX
|
# Clear screen for better UX
|
||||||
if [[ -t 1 ]]; then
|
if [[ -t 1 ]]; then
|
||||||
printf '\033[2J\033[H'
|
printf '\033[2J\033[H'
|
||||||
@@ -214,6 +218,9 @@ perform_purge() {
|
|||||||
summary_details+=("Free space now: $(get_free_space)")
|
summary_details+=("Free space now: $(get_free_space)")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Log session end
|
||||||
|
log_operation_session_end "purge" "${total_items_cleaned:-0}" "${total_size_cleaned:-0}"
|
||||||
|
|
||||||
print_summary_block "$summary_heading" "${summary_details[@]}"
|
print_summary_block "$summary_heading" "${summary_details[@]}"
|
||||||
printf '\n'
|
printf '\n'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -374,6 +374,8 @@ cleanup() {
|
|||||||
wait "$sudo_keepalive_pid" 2> /dev/null || true
|
wait "$sudo_keepalive_pid" 2> /dev/null || true
|
||||||
sudo_keepalive_pid=""
|
sudo_keepalive_pid=""
|
||||||
fi
|
fi
|
||||||
|
# Log session end
|
||||||
|
log_operation_session_end "uninstall" "${files_cleaned:-0}" "${total_size_cleaned:-0}"
|
||||||
show_cursor
|
show_cursor
|
||||||
exit "${1:-0}"
|
exit "${1:-0}"
|
||||||
}
|
}
|
||||||
@@ -381,6 +383,10 @@ cleanup() {
|
|||||||
trap cleanup EXIT INT TERM
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
|
# Set current command for operation logging
|
||||||
|
export MOLE_CURRENT_COMMAND="uninstall"
|
||||||
|
log_operation_session_start "uninstall"
|
||||||
|
|
||||||
local force_rescan=false
|
local force_rescan=false
|
||||||
# Global flags
|
# Global flags
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ validate_path_for_deletion() {
|
|||||||
# Check symlink target if path is a symbolic link
|
# Check symlink target if path is a symbolic link
|
||||||
if [[ -L "$path" ]]; then
|
if [[ -L "$path" ]]; then
|
||||||
local link_target
|
local link_target
|
||||||
link_target=$(readlink "$path" 2> /dev/null) || {
|
link_target=$(readlink "$path" 2>/dev/null) || {
|
||||||
log_error "Cannot read symlink: $path"
|
log_error "Cannot read symlink: $path"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@@ -52,16 +52,16 @@ validate_path_for_deletion() {
|
|||||||
if [[ "$link_target" != /* ]]; then
|
if [[ "$link_target" != /* ]]; then
|
||||||
local link_dir
|
local link_dir
|
||||||
link_dir=$(dirname "$path")
|
link_dir=$(dirname "$path")
|
||||||
resolved_target=$(cd "$link_dir" 2> /dev/null && cd "$(dirname "$link_target")" 2> /dev/null && pwd)/$(basename "$link_target") || resolved_target=""
|
resolved_target=$(cd "$link_dir" 2>/dev/null && cd "$(dirname "$link_target")" 2>/dev/null && pwd)/$(basename "$link_target") || resolved_target=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate resolved target against protected paths
|
# Validate resolved target against protected paths
|
||||||
if [[ -n "$resolved_target" ]]; then
|
if [[ -n "$resolved_target" ]]; then
|
||||||
case "$resolved_target" in
|
case "$resolved_target" in
|
||||||
/System/* | /usr/bin/* | /usr/lib/* | /bin/* | /sbin/* | /private/etc/*)
|
/System/* | /usr/bin/* | /usr/lib/* | /bin/* | /sbin/* | /private/etc/*)
|
||||||
log_error "Symlink points to protected system path: $path -> $resolved_target"
|
log_error "Symlink points to protected system path: $path -> $resolved_target"
|
||||||
return 1
|
return 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -88,48 +88,48 @@ validate_path_for_deletion() {
|
|||||||
|
|
||||||
# Allow deletion of coresymbolicationd cache (safe system cache that can be rebuilt)
|
# Allow deletion of coresymbolicationd cache (safe system cache that can be rebuilt)
|
||||||
case "$path" in
|
case "$path" in
|
||||||
/System/Library/Caches/com.apple.coresymbolicationd/data | /System/Library/Caches/com.apple.coresymbolicationd/data/*)
|
/System/Library/Caches/com.apple.coresymbolicationd/data | /System/Library/Caches/com.apple.coresymbolicationd/data/*)
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Allow known safe paths under /private
|
# Allow known safe paths under /private
|
||||||
case "$path" in
|
case "$path" in
|
||||||
/private/tmp | /private/tmp/* | \
|
/private/tmp | /private/tmp/* | \
|
||||||
/private/var/tmp | /private/var/tmp/* | \
|
/private/var/tmp | /private/var/tmp/* | \
|
||||||
/private/var/log | /private/var/log/* | \
|
/private/var/log | /private/var/log/* | \
|
||||||
/private/var/folders | /private/var/folders/* | \
|
/private/var/folders | /private/var/folders/* | \
|
||||||
/private/var/db/diagnostics | /private/var/db/diagnostics/* | \
|
/private/var/db/diagnostics | /private/var/db/diagnostics/* | \
|
||||||
/private/var/db/DiagnosticPipeline | /private/var/db/DiagnosticPipeline/* | \
|
/private/var/db/DiagnosticPipeline | /private/var/db/DiagnosticPipeline/* | \
|
||||||
/private/var/db/powerlog | /private/var/db/powerlog/* | \
|
/private/var/db/powerlog | /private/var/db/powerlog/* | \
|
||||||
/private/var/db/reportmemoryexception | /private/var/db/reportmemoryexception/* | \
|
/private/var/db/reportmemoryexception | /private/var/db/reportmemoryexception/* | \
|
||||||
/private/var/db/receipts/*.bom | /private/var/db/receipts/*.plist)
|
/private/var/db/receipts/*.bom | /private/var/db/receipts/*.plist)
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Check path isn't critical system directory
|
# Check path isn't critical system directory
|
||||||
case "$path" in
|
case "$path" in
|
||||||
/ | /bin | /bin/* | /sbin | /sbin/* | /usr | /usr/bin | /usr/bin/* | /usr/sbin | /usr/sbin/* | /usr/lib | /usr/lib/* | /System | /System/* | /Library/Extensions)
|
/ | /bin | /bin/* | /sbin | /sbin/* | /usr | /usr/bin | /usr/bin/* | /usr/sbin | /usr/sbin/* | /usr/lib | /usr/lib/* | /System | /System/* | /Library/Extensions)
|
||||||
log_error "Path validation failed: critical system directory: $path"
|
log_error "Path validation failed: critical system directory: $path"
|
||||||
return 1
|
return 1
|
||||||
;;
|
;;
|
||||||
/private)
|
/private)
|
||||||
log_error "Path validation failed: critical system directory: $path"
|
log_error "Path validation failed: critical system directory: $path"
|
||||||
return 1
|
return 1
|
||||||
;;
|
;;
|
||||||
/etc | /etc/* | /private/etc | /private/etc/*)
|
/etc | /etc/* | /private/etc | /private/etc/*)
|
||||||
log_error "Path validation failed: /etc contains critical system files: $path"
|
log_error "Path validation failed: /etc contains critical system files: $path"
|
||||||
return 1
|
return 1
|
||||||
;;
|
;;
|
||||||
/var | /var/db | /var/db/* | /private/var | /private/var/db | /private/var/db/*)
|
/var | /var/db | /var/db/* | /private/var | /private/var/db | /private/var/db/*)
|
||||||
log_error "Path validation failed: /var/db contains system databases: $path"
|
log_error "Path validation failed: /var/db contains system databases: $path"
|
||||||
return 1
|
return 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Check if path is protected (keychains, system settings, etc)
|
# Check if path is protected (keychains, system settings, etc)
|
||||||
if declare -f should_protect_path > /dev/null 2>&1; then
|
if declare -f should_protect_path >/dev/null 2>&1; then
|
||||||
if should_protect_path "$path"; then
|
if should_protect_path "$path"; then
|
||||||
if [[ "${MO_DEBUG:-0}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-0}" == "1" ]]; then
|
||||||
log_warning "Path validation: protected path skipped: $path"
|
log_warning "Path validation: protected path skipped: $path"
|
||||||
@@ -172,16 +172,16 @@ safe_remove() {
|
|||||||
|
|
||||||
if [[ -e "$path" ]]; then
|
if [[ -e "$path" ]]; then
|
||||||
local size_kb
|
local size_kb
|
||||||
size_kb=$(get_path_size_kb "$path" 2> /dev/null || echo "0")
|
size_kb=$(get_path_size_kb "$path" 2>/dev/null || echo "0")
|
||||||
if [[ "$size_kb" -gt 0 ]]; then
|
if [[ "$size_kb" -gt 0 ]]; then
|
||||||
file_size=$(bytes_to_human "$((size_kb * 1024))")
|
file_size=$(bytes_to_human "$((size_kb * 1024))")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -f "$path" || -d "$path" ]] && ! [[ -L "$path" ]]; then
|
if [[ -f "$path" || -d "$path" ]] && ! [[ -L "$path" ]]; then
|
||||||
local mod_time
|
local mod_time
|
||||||
mod_time=$(stat -f%m "$path" 2> /dev/null || echo "0")
|
mod_time=$(stat -f%m "$path" 2>/dev/null || echo "0")
|
||||||
local now
|
local now
|
||||||
now=$(date +%s 2> /dev/null || echo "0")
|
now=$(date +%s 2>/dev/null || echo "0")
|
||||||
if [[ "$mod_time" -gt 0 && "$now" -gt 0 ]]; then
|
if [[ "$mod_time" -gt 0 && "$now" -gt 0 ]]; then
|
||||||
file_age=$(((now - mod_time) / 86400))
|
file_age=$(((now - mod_time) / 86400))
|
||||||
fi
|
fi
|
||||||
@@ -197,6 +197,18 @@ safe_remove() {
|
|||||||
|
|
||||||
debug_log "Removing: $path"
|
debug_log "Removing: $path"
|
||||||
|
|
||||||
|
# Calculate size before deletion for logging
|
||||||
|
local size_kb=0
|
||||||
|
local size_human=""
|
||||||
|
if oplog_enabled; then
|
||||||
|
if [[ -e "$path" ]]; then
|
||||||
|
size_kb=$(get_path_size_kb "$path" 2>/dev/null || echo "0")
|
||||||
|
if [[ "$size_kb" =~ ^[0-9]+$ ]] && [[ "$size_kb" -gt 0 ]]; then
|
||||||
|
size_human=$(bytes_to_human "$((size_kb * 1024))" 2>/dev/null || echo "${size_kb}KB")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Perform the deletion
|
# Perform the deletion
|
||||||
# Use || to capture the exit code so set -e won't abort on rm failures
|
# Use || to capture the exit code so set -e won't abort on rm failures
|
||||||
local error_msg
|
local error_msg
|
||||||
@@ -204,6 +216,8 @@ safe_remove() {
|
|||||||
error_msg=$(rm -rf "$path" 2>&1) || rm_exit=$? # safe_remove
|
error_msg=$(rm -rf "$path" 2>&1) || rm_exit=$? # safe_remove
|
||||||
|
|
||||||
if [[ $rm_exit -eq 0 ]]; then
|
if [[ $rm_exit -eq 0 ]]; then
|
||||||
|
# Log successful removal
|
||||||
|
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "REMOVED" "$path" "$size_human"
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
# Check if it's a permission error
|
# Check if it's a permission error
|
||||||
@@ -212,8 +226,10 @@ safe_remove() {
|
|||||||
MOLE_PERMISSION_DENIED_COUNT=$((MOLE_PERMISSION_DENIED_COUNT + 1))
|
MOLE_PERMISSION_DENIED_COUNT=$((MOLE_PERMISSION_DENIED_COUNT + 1))
|
||||||
export MOLE_PERMISSION_DENIED_COUNT
|
export MOLE_PERMISSION_DENIED_COUNT
|
||||||
debug_log "Permission denied: $path, may need Full Disk Access"
|
debug_log "Permission denied: $path, may need Full Disk Access"
|
||||||
|
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "FAILED" "$path" "permission denied"
|
||||||
else
|
else
|
||||||
[[ "$silent" != "true" ]] && log_error "Failed to remove: $path"
|
[[ "$silent" != "true" ]] && log_error "Failed to remove: $path"
|
||||||
|
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "FAILED" "$path" "error"
|
||||||
fi
|
fi
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@@ -249,18 +265,18 @@ safe_sudo_remove() {
|
|||||||
local file_size=""
|
local file_size=""
|
||||||
local file_age=""
|
local file_age=""
|
||||||
|
|
||||||
if sudo test -e "$path" 2> /dev/null; then
|
if sudo test -e "$path" 2>/dev/null; then
|
||||||
local size_kb
|
local size_kb
|
||||||
size_kb=$(sudo du -sk "$path" 2> /dev/null | awk '{print $1}' || echo "0")
|
size_kb=$(sudo du -sk "$path" 2>/dev/null | awk '{print $1}' || echo "0")
|
||||||
if [[ "$size_kb" -gt 0 ]]; then
|
if [[ "$size_kb" -gt 0 ]]; then
|
||||||
file_size=$(bytes_to_human "$((size_kb * 1024))")
|
file_size=$(bytes_to_human "$((size_kb * 1024))")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if sudo test -f "$path" 2> /dev/null || sudo test -d "$path" 2> /dev/null; then
|
if sudo test -f "$path" 2>/dev/null || sudo test -d "$path" 2>/dev/null; then
|
||||||
local mod_time
|
local mod_time
|
||||||
mod_time=$(sudo stat -f%m "$path" 2> /dev/null || echo "0")
|
mod_time=$(sudo stat -f%m "$path" 2>/dev/null || echo "0")
|
||||||
local now
|
local now
|
||||||
now=$(date +%s 2> /dev/null || echo "0")
|
now=$(date +%s 2>/dev/null || echo "0")
|
||||||
if [[ "$mod_time" -gt 0 && "$now" -gt 0 ]]; then
|
if [[ "$mod_time" -gt 0 && "$now" -gt 0 ]]; then
|
||||||
file_age=$(((now - mod_time) / 86400))
|
file_age=$(((now - mod_time) / 86400))
|
||||||
fi
|
fi
|
||||||
@@ -276,11 +292,25 @@ safe_sudo_remove() {
|
|||||||
|
|
||||||
debug_log "Removing, sudo: $path"
|
debug_log "Removing, sudo: $path"
|
||||||
|
|
||||||
|
# Calculate size before deletion for logging
|
||||||
|
local size_kb=0
|
||||||
|
local size_human=""
|
||||||
|
if oplog_enabled; then
|
||||||
|
if sudo test -e "$path" 2>/dev/null; then
|
||||||
|
size_kb=$(sudo du -sk "$path" 2>/dev/null | awk '{print $1}' || echo "0")
|
||||||
|
if [[ "$size_kb" =~ ^[0-9]+$ ]] && [[ "$size_kb" -gt 0 ]]; then
|
||||||
|
size_human=$(bytes_to_human "$((size_kb * 1024))" 2>/dev/null || echo "${size_kb}KB")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Perform the deletion
|
# Perform the deletion
|
||||||
if sudo rm -rf "$path" 2> /dev/null; then # SAFE: safe_sudo_remove implementation
|
if sudo rm -rf "$path" 2>/dev/null; then # SAFE: safe_sudo_remove implementation
|
||||||
|
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "REMOVED" "$path" "$size_human"
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
log_error "Failed to remove, sudo: $path"
|
log_error "Failed to remove, sudo: $path"
|
||||||
|
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "FAILED" "$path" "sudo error"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -326,7 +356,7 @@ safe_find_delete() {
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
safe_remove "$match" true || true
|
safe_remove "$match" true || true
|
||||||
done < <(command find "$base_dir" "${find_args[@]}" -print0 2> /dev/null || true)
|
done < <(command find "$base_dir" "${find_args[@]}" -print0 2>/dev/null || true)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -339,12 +369,12 @@ safe_sudo_find_delete() {
|
|||||||
local type_filter="${4:-f}"
|
local type_filter="${4:-f}"
|
||||||
|
|
||||||
# Validate base directory (use sudo for permission-restricted dirs)
|
# Validate base directory (use sudo for permission-restricted dirs)
|
||||||
if ! sudo test -d "$base_dir" 2> /dev/null; then
|
if ! sudo test -d "$base_dir" 2>/dev/null; then
|
||||||
debug_log "Directory does not exist, skipping: $base_dir"
|
debug_log "Directory does not exist, skipping: $base_dir"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if sudo test -L "$base_dir" 2> /dev/null; then
|
if sudo test -L "$base_dir" 2>/dev/null; then
|
||||||
log_error "Refusing to search symlinked directory: $base_dir"
|
log_error "Refusing to search symlinked directory: $base_dir"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@@ -368,7 +398,7 @@ safe_sudo_find_delete() {
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
safe_sudo_remove "$match" || true
|
safe_sudo_remove "$match" || true
|
||||||
done < <(sudo find "$base_dir" "${find_args[@]}" -print0 2> /dev/null || true)
|
done < <(sudo find "$base_dir" "${find_args[@]}" -print0 2>/dev/null || true)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -388,7 +418,7 @@ get_path_size_kb() {
|
|||||||
# Use || echo 0 to ensure failure in du (e.g. permission error) doesn't exit script under set -e
|
# Use || echo 0 to ensure failure in du (e.g. permission error) doesn't exit script under set -e
|
||||||
# Pipefail would normally cause the pipeline to fail if du fails, but || handle catches it.
|
# Pipefail would normally cause the pipeline to fail if du fails, but || handle catches it.
|
||||||
local size
|
local size
|
||||||
size=$(command du -sk "$path" 2> /dev/null | awk 'NR==1 {print $1; exit}' || true)
|
size=$(command du -sk "$path" 2>/dev/null | awk 'NR==1 {print $1; exit}' || true)
|
||||||
|
|
||||||
# Ensure size is a valid number (fix for non-numeric du output)
|
# Ensure size is a valid number (fix for non-numeric du output)
|
||||||
if [[ "$size" =~ ^[0-9]+$ ]]; then
|
if [[ "$size" =~ ^[0-9]+$ ]]; then
|
||||||
@@ -409,7 +439,7 @@ calculate_total_size() {
|
|||||||
size_kb=$(get_path_size_kb "$file")
|
size_kb=$(get_path_size_kb "$file")
|
||||||
((total_kb += size_kb))
|
((total_kb += size_kb))
|
||||||
fi
|
fi
|
||||||
done <<< "$files"
|
done <<<"$files"
|
||||||
|
|
||||||
echo "$total_kb"
|
echo "$total_kb"
|
||||||
}
|
}
|
||||||
|
|||||||
138
lib/core/log.sh
138
lib/core/log.sh
@@ -23,10 +23,15 @@ fi
|
|||||||
|
|
||||||
readonly LOG_FILE="${HOME}/.config/mole/mole.log"
|
readonly LOG_FILE="${HOME}/.config/mole/mole.log"
|
||||||
readonly DEBUG_LOG_FILE="${HOME}/.config/mole/mole_debug_session.log"
|
readonly DEBUG_LOG_FILE="${HOME}/.config/mole/mole_debug_session.log"
|
||||||
readonly LOG_MAX_SIZE_DEFAULT=1048576 # 1MB
|
readonly OPERATIONS_LOG_FILE="${HOME}/.config/mole/operations.log"
|
||||||
|
readonly LOG_MAX_SIZE_DEFAULT=1048576 # 1MB
|
||||||
|
readonly OPLOG_MAX_SIZE_DEFAULT=5242880 # 5MB
|
||||||
|
|
||||||
# Ensure log directory and file exist with correct ownership
|
# Ensure log directory and file exist with correct ownership
|
||||||
ensure_user_file "$LOG_FILE"
|
ensure_user_file "$LOG_FILE"
|
||||||
|
if [[ "${MO_NO_OPLOG:-}" != "1" ]]; then
|
||||||
|
ensure_user_file "$OPERATIONS_LOG_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Log Rotation
|
# Log Rotation
|
||||||
@@ -40,9 +45,18 @@ rotate_log_once() {
|
|||||||
|
|
||||||
local max_size="$LOG_MAX_SIZE_DEFAULT"
|
local max_size="$LOG_MAX_SIZE_DEFAULT"
|
||||||
if [[ -f "$LOG_FILE" ]] && [[ $(get_file_size "$LOG_FILE") -gt "$max_size" ]]; then
|
if [[ -f "$LOG_FILE" ]] && [[ $(get_file_size "$LOG_FILE") -gt "$max_size" ]]; then
|
||||||
mv "$LOG_FILE" "${LOG_FILE}.old" 2> /dev/null || true
|
mv "$LOG_FILE" "${LOG_FILE}.old" 2>/dev/null || true
|
||||||
ensure_user_file "$LOG_FILE"
|
ensure_user_file "$LOG_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Rotate operations log (5MB limit)
|
||||||
|
if [[ "${MO_NO_OPLOG:-}" != "1" ]]; then
|
||||||
|
local oplog_max_size="$OPLOG_MAX_SIZE_DEFAULT"
|
||||||
|
if [[ -f "$OPERATIONS_LOG_FILE" ]] && [[ $(get_file_size "$OPERATIONS_LOG_FILE") -gt "$oplog_max_size" ]]; then
|
||||||
|
mv "$OPERATIONS_LOG_FILE" "${OPERATIONS_LOG_FILE}.old" 2>/dev/null || true
|
||||||
|
ensure_user_file "$OPERATIONS_LOG_FILE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -53,9 +67,9 @@ rotate_log_once() {
|
|||||||
log_info() {
|
log_info() {
|
||||||
echo -e "${BLUE}$1${NC}"
|
echo -e "${BLUE}$1${NC}"
|
||||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
echo "[$timestamp] INFO: $1" >> "$LOG_FILE" 2> /dev/null || true
|
echo "[$timestamp] INFO: $1" >>"$LOG_FILE" 2>/dev/null || true
|
||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
echo "[$timestamp] INFO: $1" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
echo "[$timestamp] INFO: $1" >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,9 +77,9 @@ log_info() {
|
|||||||
log_success() {
|
log_success() {
|
||||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $1"
|
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $1"
|
||||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
echo "[$timestamp] SUCCESS: $1" >> "$LOG_FILE" 2> /dev/null || true
|
echo "[$timestamp] SUCCESS: $1" >>"$LOG_FILE" 2>/dev/null || true
|
||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
echo "[$timestamp] SUCCESS: $1" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
echo "[$timestamp] SUCCESS: $1" >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,9 +87,9 @@ log_success() {
|
|||||||
log_warning() {
|
log_warning() {
|
||||||
echo -e "${YELLOW}$1${NC}"
|
echo -e "${YELLOW}$1${NC}"
|
||||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
echo "[$timestamp] WARNING: $1" >> "$LOG_FILE" 2> /dev/null || true
|
echo "[$timestamp] WARNING: $1" >>"$LOG_FILE" 2>/dev/null || true
|
||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
echo "[$timestamp] WARNING: $1" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
echo "[$timestamp] WARNING: $1" >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,9 +97,9 @@ log_warning() {
|
|||||||
log_error() {
|
log_error() {
|
||||||
echo -e "${YELLOW}${ICON_ERROR}${NC} $1" >&2
|
echo -e "${YELLOW}${ICON_ERROR}${NC} $1" >&2
|
||||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
echo "[$timestamp] ERROR: $1" >> "$LOG_FILE" 2> /dev/null || true
|
echo "[$timestamp] ERROR: $1" >>"$LOG_FILE" 2>/dev/null || true
|
||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
echo "[$timestamp] ERROR: $1" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
echo "[$timestamp] ERROR: $1" >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,10 +107,84 @@ log_error() {
|
|||||||
debug_log() {
|
debug_log() {
|
||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
echo -e "${GRAY}[DEBUG]${NC} $*" >&2
|
echo -e "${GRAY}[DEBUG]${NC} $*" >&2
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] DEBUG: $*" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] DEBUG: $*" >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Operation Logging (Enabled by default)
|
||||||
|
# ============================================================================
|
||||||
|
# Records all file operations for user troubleshooting
|
||||||
|
# Disable with MO_NO_OPLOG=1
|
||||||
|
|
||||||
|
oplog_enabled() {
|
||||||
|
[[ "${MO_NO_OPLOG:-}" != "1" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Log an operation to the operations log file
|
||||||
|
# Usage: log_operation <command> <action> <path> [detail]
|
||||||
|
# Example: log_operation "clean" "REMOVED" "/path/to/file" "15.2MB"
|
||||||
|
# Example: log_operation "clean" "SKIPPED" "/path/to/file" "whitelist"
|
||||||
|
# Example: log_operation "uninstall" "REMOVED" "/Applications/App.app" "150MB"
|
||||||
|
log_operation() {
|
||||||
|
# Allow disabling via environment variable
|
||||||
|
oplog_enabled || return 0
|
||||||
|
|
||||||
|
local command="${1:-unknown}" # clean/uninstall/optimize/purge
|
||||||
|
local action="${2:-UNKNOWN}" # REMOVED/SKIPPED/FAILED/REBUILT
|
||||||
|
local path="${3:-}"
|
||||||
|
local detail="${4:-}"
|
||||||
|
|
||||||
|
# Skip if no path provided
|
||||||
|
[[ -z "$path" ]] && return 0
|
||||||
|
|
||||||
|
local timestamp
|
||||||
|
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
local log_line="[$timestamp] [$command] $action $path"
|
||||||
|
[[ -n "$detail" ]] && log_line+=" ($detail)"
|
||||||
|
|
||||||
|
echo "$log_line" >>"$OPERATIONS_LOG_FILE" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Log session start marker
|
||||||
|
# Usage: log_operation_session_start <command>
|
||||||
|
log_operation_session_start() {
|
||||||
|
oplog_enabled || return 0
|
||||||
|
|
||||||
|
local command="${1:-mole}"
|
||||||
|
local timestamp
|
||||||
|
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo "# ========== $command session started at $timestamp =========="
|
||||||
|
} >>"$OPERATIONS_LOG_FILE" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Log session end with summary
|
||||||
|
# Usage: log_operation_session_end <command> <items_count> <total_size>
|
||||||
|
log_operation_session_end() {
|
||||||
|
oplog_enabled || return 0
|
||||||
|
|
||||||
|
local command="${1:-mole}"
|
||||||
|
local items="${2:-0}"
|
||||||
|
local size="${3:-0}"
|
||||||
|
local timestamp
|
||||||
|
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
local size_human=""
|
||||||
|
if [[ "$size" =~ ^[0-9]+$ ]] && [[ "$size" -gt 0 ]]; then
|
||||||
|
size_human=$(bytes_to_human "$((size * 1024))" 2>/dev/null || echo "${size}KB")
|
||||||
|
else
|
||||||
|
size_human="0B"
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# ========== $command session ended at $timestamp, $items items, $size_human =========="
|
||||||
|
} >>"$OPERATIONS_LOG_FILE" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
# Enhanced debug logging for operations
|
# Enhanced debug logging for operations
|
||||||
debug_operation_start() {
|
debug_operation_start() {
|
||||||
local operation_name="$1"
|
local operation_name="$1"
|
||||||
@@ -112,7 +200,7 @@ debug_operation_start() {
|
|||||||
echo ""
|
echo ""
|
||||||
echo "=== $operation_name ==="
|
echo "=== $operation_name ==="
|
||||||
[[ -n "$operation_desc" ]] && echo "Description: $operation_desc"
|
[[ -n "$operation_desc" ]] && echo "Description: $operation_desc"
|
||||||
} >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
} >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +214,7 @@ debug_operation_detail() {
|
|||||||
echo -e "${GRAY}[DEBUG] $detail_type: $detail_value${NC}" >&2
|
echo -e "${GRAY}[DEBUG] $detail_type: $detail_value${NC}" >&2
|
||||||
|
|
||||||
# Also log to file
|
# Also log to file
|
||||||
echo "$detail_type: $detail_value" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
echo "$detail_type: $detail_value" >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +234,7 @@ debug_file_action() {
|
|||||||
echo -e "${GRAY}[DEBUG] $action: $msg${NC}" >&2
|
echo -e "${GRAY}[DEBUG] $action: $msg${NC}" >&2
|
||||||
|
|
||||||
# Also log to file
|
# Also log to file
|
||||||
echo "$action: $msg" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
echo "$action: $msg" >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,16 +246,16 @@ debug_risk_level() {
|
|||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
local color="$GRAY"
|
local color="$GRAY"
|
||||||
case "$risk_level" in
|
case "$risk_level" in
|
||||||
LOW) color="$GREEN" ;;
|
LOW) color="$GREEN" ;;
|
||||||
MEDIUM) color="$YELLOW" ;;
|
MEDIUM) color="$YELLOW" ;;
|
||||||
HIGH) color="$RED" ;;
|
HIGH) color="$RED" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Output to stderr with color
|
# Output to stderr with color
|
||||||
echo -e "${GRAY}[DEBUG] Risk Level: ${color}${risk_level}${GRAY}, $reason${NC}" >&2
|
echo -e "${GRAY}[DEBUG] Risk Level: ${color}${risk_level}${GRAY}, $reason${NC}" >&2
|
||||||
|
|
||||||
# Also log to file
|
# Also log to file
|
||||||
echo "Risk Level: $risk_level, $reason" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
echo "Risk Level: $risk_level, $reason" >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +267,7 @@ log_system_info() {
|
|||||||
|
|
||||||
# Reset debug log file for this new session
|
# Reset debug log file for this new session
|
||||||
ensure_user_file "$DEBUG_LOG_FILE"
|
ensure_user_file "$DEBUG_LOG_FILE"
|
||||||
if ! : > "$DEBUG_LOG_FILE" 2> /dev/null; then
|
if ! : >"$DEBUG_LOG_FILE" 2>/dev/null; then
|
||||||
echo -e "${YELLOW}${ICON_WARNING}${NC} Debug log not writable: $DEBUG_LOG_FILE" >&2
|
echo -e "${YELLOW}${ICON_WARNING}${NC} Debug log not writable: $DEBUG_LOG_FILE" >&2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -192,19 +280,19 @@ log_system_info() {
|
|||||||
echo "Hostname: $(hostname)"
|
echo "Hostname: $(hostname)"
|
||||||
echo "Architecture: $(uname -m)"
|
echo "Architecture: $(uname -m)"
|
||||||
echo "Kernel: $(uname -r)"
|
echo "Kernel: $(uname -r)"
|
||||||
if command -v sw_vers > /dev/null; then
|
if command -v sw_vers >/dev/null; then
|
||||||
echo "macOS: $(sw_vers -productVersion), $(sw_vers -buildVersion)"
|
echo "macOS: $(sw_vers -productVersion), $(sw_vers -buildVersion)"
|
||||||
fi
|
fi
|
||||||
echo "Shell: ${SHELL:-unknown}, ${TERM:-unknown}"
|
echo "Shell: ${SHELL:-unknown}, ${TERM:-unknown}"
|
||||||
|
|
||||||
# Check sudo status non-interactively
|
# Check sudo status non-interactively
|
||||||
if sudo -n true 2> /dev/null; then
|
if sudo -n true 2>/dev/null; then
|
||||||
echo "Sudo Access: Active"
|
echo "Sudo Access: Active"
|
||||||
else
|
else
|
||||||
echo "Sudo Access: Required"
|
echo "Sudo Access: Required"
|
||||||
fi
|
fi
|
||||||
echo "----------------------------------------------------------------------"
|
echo "----------------------------------------------------------------------"
|
||||||
} >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
} >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
|
|
||||||
# Notification to stderr
|
# Notification to stderr
|
||||||
echo -e "${GRAY}[DEBUG] Debug logging enabled. Session log: $DEBUG_LOG_FILE${NC}" >&2
|
echo -e "${GRAY}[DEBUG] Debug logging enabled. Session log: $DEBUG_LOG_FILE${NC}" >&2
|
||||||
@@ -216,7 +304,7 @@ log_system_info() {
|
|||||||
|
|
||||||
# Run command silently (ignore errors)
|
# Run command silently (ignore errors)
|
||||||
run_silent() {
|
run_silent() {
|
||||||
"$@" > /dev/null 2>&1 || true
|
"$@" >/dev/null 2>&1 || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Run command with error logging
|
# Run command with error logging
|
||||||
@@ -224,12 +312,12 @@ run_logged() {
|
|||||||
local cmd="$1"
|
local cmd="$1"
|
||||||
# Log to main file, and also to debug file if enabled
|
# Log to main file, and also to debug file if enabled
|
||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
if ! "$@" 2>&1 | tee -a "$LOG_FILE" | tee -a "$DEBUG_LOG_FILE" > /dev/null; then
|
if ! "$@" 2>&1 | tee -a "$LOG_FILE" | tee -a "$DEBUG_LOG_FILE" >/dev/null; then
|
||||||
log_warning "Command failed: $cmd"
|
log_warning "Command failed: $cmd"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if ! "$@" 2>&1 | tee -a "$LOG_FILE" > /dev/null; then
|
if ! "$@" 2>&1 | tee -a "$LOG_FILE" >/dev/null; then
|
||||||
log_warning "Command failed: $cmd"
|
log_warning "Command failed: $cmd"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user