mirror of
https://github.com/tw93/Mole.git
synced 2026-03-22 17:55:08 +00:00
fix(log): preserve Mole runtime logs during clean
This commit is contained in:
@@ -90,7 +90,7 @@ Review [SECURITY.md](SECURITY.md) and [SECURITY_AUDIT.md](SECURITY_AUDIT.md) for
|
|||||||
## Tips
|
## Tips
|
||||||
|
|
||||||
- Video tutorial: Watch the [Mole tutorial video](https://www.youtube.com/watch?v=UEe9-w4CcQ0), thanks to PAPAYA 電腦教室.
|
- Video tutorial: Watch the [Mole tutorial video](https://www.youtube.com/watch?v=UEe9-w4CcQ0), thanks to PAPAYA 電腦教室.
|
||||||
- Safety and logs: `clean`, `uninstall`, `purge`, `installer`, and `remove` are destructive. Review with `--dry-run` first, and add `--debug` when needed. File operations are logged to `~/.config/mole/operations.log`. Disable with `MO_NO_OPLOG=1`. Review [SECURITY.md](SECURITY.md) and [SECURITY_AUDIT.md](SECURITY_AUDIT.md).
|
- Safety and logs: `clean`, `uninstall`, `purge`, `installer`, and `remove` are destructive. Review with `--dry-run` first, and add `--debug` when needed. File operations are logged to `~/Library/Logs/mole/operations.log`. Disable with `MO_NO_OPLOG=1`. Review [SECURITY.md](SECURITY.md) and [SECURITY_AUDIT.md](SECURITY_AUDIT.md).
|
||||||
- Navigation: Mole supports arrow keys and Vim bindings `h/j/k/l`.
|
- Navigation: Mole supports arrow keys and Vim bindings `h/j/k/l`.
|
||||||
|
|
||||||
## Features in Detail
|
## Features in Detail
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ Mole exposes multiple safety controls before and during destructive actions:
|
|||||||
- interactive high-risk flows require explicit confirmation before deletion
|
- interactive high-risk flows require explicit confirmation before deletion
|
||||||
- purge marks recent projects conservatively and leaves them unselected by default
|
- purge marks recent projects conservatively and leaves them unselected by default
|
||||||
- analyzer delete uses Finder Trash rather than direct permanent removal
|
- analyzer delete uses Finder Trash rather than direct permanent removal
|
||||||
- operation logs are written to `~/.config/mole/operations.log` unless disabled with `MO_NO_OPLOG=1`
|
- operation logs are written to `~/Library/Logs/mole/operations.log` unless disabled with `MO_NO_OPLOG=1`
|
||||||
- timeouts bound external commands so stalled discovery or uninstall operations do not silently hang the entire flow
|
- timeouts bound external commands so stalled discovery or uninstall operations do not silently hang the entire flow
|
||||||
|
|
||||||
Relevant timeout behavior includes:
|
Relevant timeout behavior includes:
|
||||||
|
|||||||
@@ -802,6 +802,10 @@ should_protect_path() {
|
|||||||
*/Library/Preferences/com.apple.dock.plist | */Library/Preferences/com.apple.finder.plist)
|
*/Library/Preferences/com.apple.dock.plist | */Library/Preferences/com.apple.finder.plist)
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
|
# Protect Mole's own runtime logs so cleanup cannot delete its active log targets.
|
||||||
|
*/Library/Logs/mole | */Library/Logs/mole/ | */Library/Logs/mole/*)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
# Bluetooth and WiFi configurations
|
# Bluetooth and WiFi configurations
|
||||||
*/ByHost/com.apple.bluetooth.* | */ByHost/com.apple.wifi.*)
|
*/ByHost/com.apple.bluetooth.* | */ByHost/com.apple.wifi.*)
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -37,6 +37,22 @@ fi
|
|||||||
# Log Rotation
|
# Log Rotation
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
append_log_line() {
|
||||||
|
local file_path="$1"
|
||||||
|
local line="${2:-}"
|
||||||
|
|
||||||
|
ensure_user_file "$file_path"
|
||||||
|
printf '%s\n' "$line" >> "$file_path" 2> /dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
append_log_lines() {
|
||||||
|
local file_path="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
ensure_user_file "$file_path"
|
||||||
|
printf '%s\n' "$@" >> "$file_path" 2> /dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
# Rotate log file if it exceeds maximum size
|
# Rotate log file if it exceeds maximum size
|
||||||
rotate_log_once() {
|
rotate_log_once() {
|
||||||
# Skip if already checked this session
|
# Skip if already checked this session
|
||||||
@@ -81,9 +97,9 @@ log_info() {
|
|||||||
echo -e "${BLUE}$1${NC}"
|
echo -e "${BLUE}$1${NC}"
|
||||||
local timestamp
|
local timestamp
|
||||||
timestamp=$(get_timestamp)
|
timestamp=$(get_timestamp)
|
||||||
echo "[$timestamp] INFO: $1" >> "$LOG_FILE" 2> /dev/null || true
|
append_log_line "$LOG_FILE" "[$timestamp] INFO: $1"
|
||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
echo "[$timestamp] INFO: $1" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
append_log_line "$DEBUG_LOG_FILE" "[$timestamp] INFO: $1"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,9 +108,9 @@ log_success() {
|
|||||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $1"
|
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $1"
|
||||||
local timestamp
|
local timestamp
|
||||||
timestamp=$(get_timestamp)
|
timestamp=$(get_timestamp)
|
||||||
echo "[$timestamp] SUCCESS: $1" >> "$LOG_FILE" 2> /dev/null || true
|
append_log_line "$LOG_FILE" "[$timestamp] SUCCESS: $1"
|
||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
echo "[$timestamp] SUCCESS: $1" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
append_log_line "$DEBUG_LOG_FILE" "[$timestamp] SUCCESS: $1"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,9 +119,9 @@ log_warning() {
|
|||||||
echo -e "${YELLOW}$1${NC}"
|
echo -e "${YELLOW}$1${NC}"
|
||||||
local timestamp
|
local timestamp
|
||||||
timestamp=$(get_timestamp)
|
timestamp=$(get_timestamp)
|
||||||
echo "[$timestamp] WARNING: $1" >> "$LOG_FILE" 2> /dev/null || true
|
append_log_line "$LOG_FILE" "[$timestamp] WARNING: $1"
|
||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
echo "[$timestamp] WARNING: $1" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
append_log_line "$DEBUG_LOG_FILE" "[$timestamp] WARNING: $1"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,9 +130,9 @@ log_error() {
|
|||||||
echo -e "${YELLOW}${ICON_ERROR}${NC} $1" >&2
|
echo -e "${YELLOW}${ICON_ERROR}${NC} $1" >&2
|
||||||
local timestamp
|
local timestamp
|
||||||
timestamp=$(get_timestamp)
|
timestamp=$(get_timestamp)
|
||||||
echo "[$timestamp] ERROR: $1" >> "$LOG_FILE" 2> /dev/null || true
|
append_log_line "$LOG_FILE" "[$timestamp] ERROR: $1"
|
||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
echo "[$timestamp] ERROR: $1" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
append_log_line "$DEBUG_LOG_FILE" "[$timestamp] ERROR: $1"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +142,7 @@ debug_log() {
|
|||||||
echo -e "${GRAY}[DEBUG]${NC} $*" >&2
|
echo -e "${GRAY}[DEBUG]${NC} $*" >&2
|
||||||
local timestamp
|
local timestamp
|
||||||
timestamp=$(get_timestamp)
|
timestamp=$(get_timestamp)
|
||||||
echo "[$timestamp] DEBUG: $*" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
append_log_line "$DEBUG_LOG_FILE" "[$timestamp] DEBUG: $*"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +179,7 @@ log_operation() {
|
|||||||
local log_line="[$timestamp] [$command] $action $path"
|
local log_line="[$timestamp] [$command] $action $path"
|
||||||
[[ -n "$detail" ]] && log_line+=" ($detail)"
|
[[ -n "$detail" ]] && log_line+=" ($detail)"
|
||||||
|
|
||||||
echo "$log_line" >> "$OPERATIONS_LOG_FILE" 2> /dev/null || true
|
append_log_line "$OPERATIONS_LOG_FILE" "$log_line"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Log session start marker
|
# Log session start marker
|
||||||
@@ -175,10 +191,10 @@ log_operation_session_start() {
|
|||||||
local timestamp
|
local timestamp
|
||||||
timestamp=$(get_timestamp)
|
timestamp=$(get_timestamp)
|
||||||
|
|
||||||
{
|
append_log_lines \
|
||||||
echo ""
|
"$OPERATIONS_LOG_FILE" \
|
||||||
echo "# ========== $command session started at $timestamp =========="
|
"" \
|
||||||
} >> "$OPERATIONS_LOG_FILE" 2> /dev/null || true
|
"# ========== $command session started at $timestamp =========="
|
||||||
}
|
}
|
||||||
|
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
@@ -198,9 +214,9 @@ log_operation_session_end() {
|
|||||||
size_human="0B"
|
size_human="0B"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
{
|
append_log_line \
|
||||||
echo "# ========== $command session ended at $timestamp, $items items, $size_human =========="
|
"$OPERATIONS_LOG_FILE" \
|
||||||
} >> "$OPERATIONS_LOG_FILE" 2> /dev/null || true
|
"# ========== $command session ended at $timestamp, $items items, $size_human =========="
|
||||||
}
|
}
|
||||||
|
|
||||||
# Enhanced debug logging for operations
|
# Enhanced debug logging for operations
|
||||||
@@ -214,11 +230,18 @@ debug_operation_start() {
|
|||||||
[[ -n "$operation_desc" ]] && echo -e "${GRAY}[DEBUG] $operation_desc${NC}" >&2
|
[[ -n "$operation_desc" ]] && echo -e "${GRAY}[DEBUG] $operation_desc${NC}" >&2
|
||||||
|
|
||||||
# Also log to file
|
# Also log to file
|
||||||
{
|
if [[ -n "$operation_desc" ]]; then
|
||||||
echo ""
|
append_log_lines \
|
||||||
echo "=== $operation_name ==="
|
"$DEBUG_LOG_FILE" \
|
||||||
[[ -n "$operation_desc" ]] && echo "Description: $operation_desc"
|
"" \
|
||||||
} >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
"=== $operation_name ===" \
|
||||||
|
"Description: $operation_desc"
|
||||||
|
else
|
||||||
|
append_log_lines \
|
||||||
|
"$DEBUG_LOG_FILE" \
|
||||||
|
"" \
|
||||||
|
"=== $operation_name ==="
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +255,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
|
append_log_line "$DEBUG_LOG_FILE" "$detail_type: $detail_value"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +275,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
|
append_log_line "$DEBUG_LOG_FILE" "$action: $msg"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,41 @@ EOF
|
|||||||
[[ "$output" == *"Trash · emptied, 2 items"* ]]
|
[[ "$output" == *"Trash · emptied, 2 items"* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "clean_user_essentials keeps Mole runtime logs while cleaning other user logs" {
|
||||||
|
mkdir -p "$HOME/Library/Logs/mole"
|
||||||
|
mkdir -p "$HOME/Library/Logs/OtherApp"
|
||||||
|
touch "$HOME/Library/Logs/mole/operations.log"
|
||||||
|
touch "$HOME/Library/Logs/OtherApp/old.log"
|
||||||
|
|
||||||
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/clean/user.sh"
|
||||||
|
DRY_RUN=false
|
||||||
|
start_section_spinner() { :; }
|
||||||
|
stop_section_spinner() { :; }
|
||||||
|
note_activity() { :; }
|
||||||
|
is_path_whitelisted() { return 1; }
|
||||||
|
safe_clean() {
|
||||||
|
local path=""
|
||||||
|
for path in "${@:1:$#-1}"; do
|
||||||
|
if should_protect_path "$path"; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
/bin/rm -rf "$path"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
clean_user_essentials
|
||||||
|
|
||||||
|
[[ -d "$HOME/Library/Logs/mole" ]]
|
||||||
|
[[ -f "$HOME/Library/Logs/mole/operations.log" ]]
|
||||||
|
[[ ! -e "$HOME/Library/Logs/OtherApp/old.log" ]]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
@test "clean_app_caches includes macOS system caches" {
|
@test "clean_app_caches includes macOS system caches" {
|
||||||
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|||||||
@@ -69,6 +69,25 @@ setup() {
|
|||||||
grep -q "ERROR: $message" "$log_file"
|
grep -q "ERROR: $message" "$log_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "log_operation recreates operations log if the log directory disappears mid-session" {
|
||||||
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
rm -rf "$HOME/Library/Logs/mole"
|
||||||
|
log_operation "clean" "REMOVED" "/tmp/example" "1KB"
|
||||||
|
EOF
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
|
local oplog="$HOME/Library/Logs/mole/operations.log"
|
||||||
|
[[ -f "$oplog" ]]
|
||||||
|
grep -Fq "[clean] REMOVED /tmp/example (1KB)" "$oplog"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "should_protect_path protects Mole runtime logs" {
|
||||||
|
result="$(HOME="$HOME" bash --noprofile --norc -c "source '$PROJECT_ROOT/lib/core/common.sh'; should_protect_path '$HOME/Library/Logs/mole/operations.log' && echo protected || echo not-protected")"
|
||||||
|
[ "$result" = "protected" ]
|
||||||
|
}
|
||||||
|
|
||||||
@test "rotate_log_once only checks log size once per session" {
|
@test "rotate_log_once only checks log size once per session" {
|
||||||
local log_file="$HOME/Library/Logs/mole/mole.log"
|
local log_file="$HOME/Library/Logs/mole/mole.log"
|
||||||
mkdir -p "$(dirname "$log_file")"
|
mkdir -p "$(dirname "$log_file")"
|
||||||
|
|||||||
Reference in New Issue
Block a user