mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 13:16:47 +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'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -357,6 +357,8 @@ 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,6 +367,9 @@ 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
|
||||||
@@ -381,6 +386,8 @@ main() {
|
|||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 OPERATIONS_LOG_FILE="${HOME}/.config/mole/operations.log"
|
||||||
readonly LOG_MAX_SIZE_DEFAULT=1048576 # 1MB
|
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
|
||||||
@@ -43,6 +48,15 @@ rotate_log_once() {
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -97,6 +111,80 @@ debug_log() {
|
|||||||
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user