mirror of
https://github.com/tw93/Mole.git
synced 2026-03-24 14:05:06 +00:00
fix(uninstall): pre-auth brew cask removals
This commit is contained in:
@@ -285,6 +285,7 @@ batch_uninstall_applications() {
|
||||
# Pre-scan: running apps, sudo needs, size.
|
||||
local -a running_apps=()
|
||||
local -a sudo_apps=()
|
||||
local -a brew_cask_apps=()
|
||||
local total_estimated_size=0
|
||||
local -a app_details=()
|
||||
|
||||
@@ -322,6 +323,10 @@ batch_uninstall_applications() {
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$is_brew_cask" == "true" ]]; then
|
||||
brew_cask_apps+=("$app_name")
|
||||
fi
|
||||
|
||||
# Check if sudo is needed
|
||||
local needs_sudo=false
|
||||
local app_owner=$(get_file_owner "$app_path")
|
||||
@@ -383,10 +388,7 @@ batch_uninstall_applications() {
|
||||
|
||||
# Warn if brew cask apps are present.
|
||||
local has_brew_cask=false
|
||||
for detail in "${app_details[@]}"; do
|
||||
IFS='|' read -r _ _ _ _ _ _ _ _ is_brew_cask_flag _ <<< "$detail"
|
||||
[[ "$is_brew_cask_flag" == "true" ]] && has_brew_cask=true
|
||||
done
|
||||
[[ ${#brew_cask_apps[@]} -gt 0 ]] && has_brew_cask=true
|
||||
|
||||
if [[ "$has_brew_cask" == "true" ]]; then
|
||||
echo -e "${GRAY}${ICON_WARNING} Homebrew apps will be fully cleaned, --zap removes configs and data${NC}"
|
||||
@@ -467,11 +469,19 @@ batch_uninstall_applications() {
|
||||
# that user explicitly chose to uninstall. System-critical components remain protected.
|
||||
export MOLE_UNINSTALL_MODE=1
|
||||
|
||||
# Request sudo if needed for non-Homebrew removal operations.
|
||||
# Note: Homebrew resets sudo timestamp at process startup, so pre-auth would
|
||||
# cause duplicate password prompts in cask-only flows.
|
||||
if [[ ${#sudo_apps[@]} -gt 0 && "${MOLE_DRY_RUN:-0}" != "1" ]]; then
|
||||
if ! ensure_sudo_session "Admin required for system apps: ${sudo_apps[*]}"; then
|
||||
# Establish sudo once before uninstalling apps that need admin access.
|
||||
# Homebrew cask removal can prompt via sudo during uninstall hooks, which
|
||||
# does not work reliably under Mole's timed non-interactive execution path.
|
||||
if [[ "${MOLE_DRY_RUN:-0}" != "1" ]] &&
|
||||
{ [[ ${#sudo_apps[@]} -gt 0 ]] || [[ ${#brew_cask_apps[@]} -gt 0 ]]; }; then
|
||||
local admin_prompt="Admin required to uninstall selected apps"
|
||||
if [[ ${#sudo_apps[@]} -gt 0 && ${#brew_cask_apps[@]} -eq 0 ]]; then
|
||||
admin_prompt="Admin required for system apps: ${sudo_apps[*]}"
|
||||
elif [[ ${#brew_cask_apps[@]} -gt 0 && ${#sudo_apps[@]} -eq 0 ]]; then
|
||||
admin_prompt="Admin required for Homebrew casks: ${brew_cask_apps[*]}"
|
||||
fi
|
||||
|
||||
if ! ensure_sudo_session "$admin_prompt"; then
|
||||
echo ""
|
||||
log_error "Admin access denied"
|
||||
_restore_uninstall_traps
|
||||
|
||||
@@ -101,6 +101,10 @@ remove_apps_from_dock() { :; }
|
||||
force_kill_app() { return 0; }
|
||||
run_with_timeout() { shift; "$@"; }
|
||||
export -f run_with_timeout
|
||||
ensure_sudo_session() {
|
||||
echo "ENSURE_SUDO:$*" >> "$HOME/brew_calls.log"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Mock brew to track calls
|
||||
brew() {
|
||||
@@ -121,13 +125,14 @@ total_size_cleaned=0
|
||||
# Simulate 'Enter' for confirmation
|
||||
printf '\n' | batch_uninstall_applications > /dev/null 2>&1
|
||||
|
||||
grep -q "ENSURE_SUDO:Admin required for Homebrew casks: BrewApp" "$HOME/brew_calls.log"
|
||||
grep -q "uninstall --cask --zap brew-app-cask" "$HOME/brew_calls.log"
|
||||
EOF
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "batch_uninstall_applications does not pre-auth sudo for brew-only casks" {
|
||||
@test "batch_uninstall_applications pre-auths sudo for brew-only casks" {
|
||||
local app_bundle="$HOME/Applications/BrewPreAuth.app"
|
||||
mkdir -p "$app_bundle"
|
||||
|
||||
@@ -149,8 +154,8 @@ run_with_timeout() { shift; "$@"; }
|
||||
export -f run_with_timeout
|
||||
|
||||
ensure_sudo_session() {
|
||||
echo "UNEXPECTED_ENSURE_SUDO:$*" >> "$HOME/order.log"
|
||||
return 1
|
||||
echo "ENSURE_SUDO:$*" >> "$HOME/order.log"
|
||||
return 0
|
||||
}
|
||||
|
||||
brew() {
|
||||
@@ -169,8 +174,9 @@ total_size_cleaned=0
|
||||
|
||||
printf '\n' | batch_uninstall_applications > /dev/null 2>&1
|
||||
|
||||
grep -q "ENSURE_SUDO:Admin required for Homebrew casks: BrewPreAuth" "$HOME/order.log"
|
||||
grep -q "BREW_CALL:uninstall --cask --zap brew-preauth-cask" "$HOME/order.log"
|
||||
! grep -q "UNEXPECTED_ENSURE_SUDO:" "$HOME/order.log"
|
||||
[[ "$(sed -n '1p' "$HOME/order.log")" == "ENSURE_SUDO:Admin required for Homebrew casks: BrewPreAuth" ]]
|
||||
EOF
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
@@ -196,6 +202,7 @@ print_summary_block() { :; }
|
||||
force_kill_app() { return 0; }
|
||||
remove_apps_from_dock() { :; }
|
||||
refresh_launch_services_after_uninstall() { echo "LS_REFRESH"; }
|
||||
ensure_sudo_session() { return 0; }
|
||||
|
||||
get_brew_cask_name() { echo "brew-timeout-cask"; return 0; }
|
||||
brew_uninstall_cask() { return 0; }
|
||||
@@ -257,6 +264,7 @@ has_sensitive_data() { return 1; }
|
||||
decode_file_list() { return 0; }
|
||||
remove_file_list() { :; }
|
||||
run_with_timeout() { shift; "$@"; }
|
||||
ensure_sudo_session() { return 0; }
|
||||
|
||||
safe_remove() {
|
||||
echo "SAFE_REMOVE:$1" >> "$HOME/remove.log"
|
||||
@@ -315,6 +323,7 @@ has_sensitive_data() { return 1; }
|
||||
decode_file_list() { return 0; }
|
||||
remove_file_list() { :; }
|
||||
run_with_timeout() { shift; "$@"; }
|
||||
ensure_sudo_session() { return 0; }
|
||||
|
||||
safe_remove() {
|
||||
echo "SAFE_REMOVE:$1" >> "$HOME/remove.log"
|
||||
@@ -344,37 +353,47 @@ EOF
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "brew_uninstall_cask does not trigger extra sudo pre-auth" {
|
||||
@test "batch_uninstall_applications skips brew sudo pre-auth in dry-run mode" {
|
||||
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/uninstall/brew.sh"
|
||||
|
||||
debug_log() { :; }
|
||||
get_path_size_kb() { echo "0"; }
|
||||
run_with_timeout() { local _timeout="$1"; shift; "$@"; }
|
||||
|
||||
sudo() {
|
||||
echo "UNEXPECTED_SUDO_CALL:$*"
|
||||
return 1
|
||||
}
|
||||
source "$PROJECT_ROOT/lib/uninstall/batch.sh"
|
||||
|
||||
brew() {
|
||||
if [[ "${1:-}" == "uninstall" ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ "${1:-}" == "list" && "${2:-}" == "--cask" ]]; then
|
||||
return 0
|
||||
fi
|
||||
echo "BREW_CALL:$*" >> "$HOME/dry_run.log"
|
||||
return 0
|
||||
}
|
||||
export -f sudo brew
|
||||
export -f brew
|
||||
|
||||
brew_uninstall_cask "mock-cask"
|
||||
echo "DONE"
|
||||
start_inline_spinner() { :; }
|
||||
stop_inline_spinner() { :; }
|
||||
get_file_owner() { whoami; }
|
||||
get_path_size_kb() { echo "100"; }
|
||||
bytes_to_human() { echo "$1"; }
|
||||
drain_pending_input() { :; }
|
||||
print_summary_block() { :; }
|
||||
remove_apps_from_dock() { :; }
|
||||
force_kill_app() { return 0; }
|
||||
ensure_sudo_session() {
|
||||
echo "UNEXPECTED_ENSURE_SUDO:$*" >> "$HOME/dry_run.log"
|
||||
return 1
|
||||
}
|
||||
run_with_timeout() { shift; "$@"; }
|
||||
export -f run_with_timeout
|
||||
|
||||
get_brew_cask_name() { echo "brew-dry-run-cask"; return 0; }
|
||||
|
||||
export MOLE_DRY_RUN=1
|
||||
selected_apps=("0|$HOME/Applications/BrewDryRun.app|BrewDryRun|com.example.brewdryrun|0|Never")
|
||||
mkdir -p "$HOME/Applications/BrewDryRun.app"
|
||||
files_cleaned=0
|
||||
total_items=0
|
||||
total_size_cleaned=0
|
||||
|
||||
printf '\n' | batch_uninstall_applications > /dev/null 2>&1
|
||||
|
||||
! grep -q "UNEXPECTED_ENSURE_SUDO:" "$HOME/dry_run.log" 2> /dev/null
|
||||
EOF
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"DONE"* ]]
|
||||
[[ "$output" != *"UNEXPECTED_SUDO_CALL:"* ]]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user