Repository navigation
macOS FMA uninstalls: move trashed files as the user, not root (1/11) - #57086
Conversation
Script Diff Resultsee/maintained-apps/outputs/010-editor/darwin.json=== Install Script (no changes) ===
=== Uninstall // 849c40bd -> 58d6202b ===
--- /tmp/old.V9xREF 2026-10-31 18:26:44.744546262 +0000
+++ /tmp/new.bB44G1 2026-10-31 18:26:44.744546262 +0000
@@ -57,6 +57,25 @@
sudo rmdir "$dir" 2>/dev/null || true
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -64,19 +83,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -95,11 +113,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -113,9 +131,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/1password/darwin.json=== Install Script (no changes) ===
=== Uninstall // 91ae9d61 -> 4a4bdb86 ===
--- /tmp/old.zzLl5P 2026-10-31 18:26:44.778546315 +0000
+++ /tmp/new.Keb24Y 2026-10-31 18:26:44.779546317 +0000
@@ -143,6 +143,25 @@
done
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -150,19 +169,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -181,11 +199,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -199,9 +217,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/4k-slideshow-maker/darwin.json=== Install Script (no changes) ===
=== Uninstall // 6df6a6df -> fbde5bd7 ===
--- /tmp/old.pEX1Nb 2026-10-31 18:26:44.811546367 +0000
+++ /tmp/new.SiaMtU 2026-10-31 18:26:44.811546367 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/4k-stogram/darwin.json=== Install Script (no changes) ===
=== Uninstall // fd83e2a6 -> 28cd97a1 ===
--- /tmp/old.cQF8jR 2026-10-31 18:26:44.844546419 +0000
+++ /tmp/new.UEcPfg 2026-10-31 18:26:44.844546419 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/4k-video-downloader/darwin.json=== Install Script (no changes) ===
=== Uninstall // e95017d9 -> dba3da33 ===
--- /tmp/old.saeEtG 2026-10-31 18:26:44.876546470 +0000
+++ /tmp/new.j4Shsh 2026-10-31 18:26:44.876546470 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/4k-video-to-mp3/darwin.json=== Install Script (no changes) ===
=== Uninstall // fe3bd02b -> 2f98ebd2 ===
--- /tmp/old.SbnGIC 2026-10-31 18:26:44.908546520 +0000
+++ /tmp/new.cWnpDV 2026-10-31 18:26:44.908546520 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/4k-youtube-to-mp3/darwin.json=== Install Script (no changes) ===
=== Uninstall // 04a9d533 -> 0c249725 ===
--- /tmp/old.mx9AGH 2026-10-31 18:26:44.944546577 +0000
+++ /tmp/new.mHg6sO 2026-10-31 18:26:44.944546577 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/8x8-work/darwin.json=== Install Script (no changes) ===
=== Uninstall // 53727df6 -> 0922e23f ===
--- /tmp/old.gltlXJ 2026-10-31 18:26:44.981546635 +0000
+++ /tmp/new.TSePh6 2026-10-31 18:26:44.981546635 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/a-better-finder-rename/darwin.json=== Install Script (no changes) ===
=== Uninstall // 4ccd271d -> 71b38f2a ===
--- /tmp/old.hRRN5g 2026-10-31 18:26:45.013546686 +0000
+++ /tmp/new.fOXmIT 2026-10-31 18:26:45.013546686 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/ableton-live-suite/darwin.json=== Install Script (no changes) ===
=== Uninstall // 0f6ca42d -> b933edb1 ===
--- /tmp/old.kfmdmg 2026-10-31 18:26:45.045546736 +0000
+++ /tmp/new.NgbCxb 2026-10-31 18:26:45.045546736 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/acorn/darwin.json=== Install Script (no changes) ===
=== Uninstall // 83ec2d07 -> 6b6899e1 ===
--- /tmp/old.GytcBb 2026-10-31 18:26:45.076546785 +0000
+++ /tmp/new.LBKpL0 2026-10-31 18:26:45.077546787 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/activedock/darwin.json=== Install Script (no changes) ===
=== Uninstall // 06db0cc2 -> cda958ce ===
--- /tmp/old.vRtbXH 2026-10-31 18:26:45.110546839 +0000
+++ /tmp/new.mbweUP 2026-10-31 18:26:45.110546839 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/activitywatch/darwin.json=== Install Script (no changes) ===
=== Uninstall // 35bc8a92 -> 26e5747b ===
--- /tmp/old.c8yNcR 2026-10-31 18:26:45.143546891 +0000
+++ /tmp/new.0Gy89G 2026-10-31 18:26:45.143546891 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/actual/darwin.json=== Install Script (no changes) ===
=== Uninstall // cb140292 -> 34618eb1 ===
--- /tmp/old.tQL3LG 2026-10-31 18:26:45.175546941 +0000
+++ /tmp/new.1Lv4So 2026-10-31 18:26:45.175546941 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/adguard/darwin.json=== Install Script (no changes) ===
=== Uninstall // 7779731c -> 81490d6a ===
--- /tmp/old.MGF4Da 2026-10-31 18:26:45.208546993 +0000
+++ /tmp/new.QhIKZn 2026-10-31 18:26:45.208546993 +0000
@@ -218,6 +218,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -225,19 +244,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -256,11 +274,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -274,9 +292,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/adlock/darwin.json=== Install Script (no changes) ===
=== Uninstall // 77a66f0c -> 59f03c9f ===
--- /tmp/old.M1b7S6 2026-10-31 18:26:45.241547045 +0000
+++ /tmp/new.Ct85TY 2026-10-31 18:26:45.241547045 +0000
@@ -206,6 +206,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -213,19 +232,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -244,11 +262,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -262,9 +280,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/adobe-acrobat-pro/darwin.json=== Install Script (no changes) ===
=== Uninstall // b050ed0e -> 1e117bdc ===
--- /tmp/old.7lBevS 2026-10-31 18:26:45.274547097 +0000
+++ /tmp/new.WRvSpi 2026-10-31 18:26:45.274547097 +0000
@@ -206,6 +206,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -213,19 +232,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -244,11 +262,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -262,9 +280,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/adobe-acrobat-reader/darwin.json=== Install Script (no changes) ===
=== Uninstall // 7179c3a1 -> aab0372b ===
--- /tmp/old.vKiX0N 2026-10-31 18:26:45.310547154 +0000
+++ /tmp/new.nDiHsP 2026-10-31 18:26:45.310547154 +0000
@@ -206,6 +206,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -213,19 +232,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -244,11 +262,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -262,9 +280,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/adobe-digital-editions/darwin.json=== Install Script (no changes) ===
=== Uninstall // 48744f71 -> 03cbb2b0 ===
--- /tmp/old.wYuuaj 2026-10-31 18:26:45.343547206 +0000
+++ /tmp/new.zxQNUX 2026-10-31 18:26:45.343547206 +0000
@@ -111,6 +111,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -118,19 +137,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -149,11 +167,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -167,9 +185,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/adobe-dng-converter/darwin.json=== Install Script (no changes) ===
=== Uninstall // 74580991 -> cc65eb0e ===
--- /tmp/old.KToNEm 2026-10-31 18:26:45.375547257 +0000
+++ /tmp/new.CB9Xmp 2026-10-31 18:26:45.375547257 +0000
@@ -111,6 +111,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -118,19 +137,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -149,11 +167,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -167,9 +185,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/advanced-renamer/darwin.json=== Install Script (no changes) ===
=== Uninstall // 29568fa2 -> 825d73bd ===
--- /tmp/old.l0tsSX 2026-10-31 18:26:45.407547307 +0000
+++ /tmp/new.b3fp8d 2026-10-31 18:26:45.407547307 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/affinity-designer/darwin.json=== Install Script (no changes) ===
=== Uninstall // d2c02c71 -> 8db4838c ===
--- /tmp/old.z3eHIs 2026-10-31 18:26:45.439547357 +0000
+++ /tmp/new.YEX8t7 2026-10-31 18:26:45.439547357 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/affinity-designer@1/darwin.json=== Install Script (no changes) ===
=== Uninstall // 02df3487 -> e85c80af ===
--- /tmp/old.FqYH7g 2026-10-31 18:26:45.474547413 +0000
+++ /tmp/new.dlp63B 2026-10-31 18:26:45.474547413 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/affinity-photo/darwin.json=== Install Script (no changes) ===
=== Uninstall // 642ed154 -> c9d151f9 ===
--- /tmp/old.ggL0ky 2026-10-31 18:26:45.506547463 +0000
+++ /tmp/new.i8fNFu 2026-10-31 18:26:45.506547463 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/affinity-photo@1/darwin.json=== Install Script (no changes) ===
=== Uninstall // 7adb91a2 -> 0a468663 ===
--- /tmp/old.Pmxa1x 2026-10-31 18:26:45.539547515 +0000
+++ /tmp/new.ZL41tB 2026-10-31 18:26:45.539547515 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/affinity-publisher/darwin.json=== Install Script (no changes) ===
=== Uninstall // d26ee6ad -> a3bbcbb0 ===
--- /tmp/old.ymE1sl 2026-10-31 18:26:45.576547573 +0000
+++ /tmp/new.TaGfYK 2026-10-31 18:26:45.576547573 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/affinity-publisher@1/darwin.json=== Install Script (no changes) ===
=== Uninstall // 3ce50edd -> 11ffabee ===
--- /tmp/old.C2kFy1 2026-10-31 18:26:45.608547624 +0000
+++ /tmp/new.qZIwcY 2026-10-31 18:26:45.608547624 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/affinity/darwin.json=== Install Script (no changes) ===
=== Uninstall // 778320cb -> 1fff133d ===
--- /tmp/old.kE784z 2026-10-31 18:26:45.640547674 +0000
+++ /tmp/new.0eHdQk 2026-10-31 18:26:45.640547674 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/airbuddy/darwin.json=== Install Script (no changes) ===
=== Uninstall // c4abe31c -> a753c19a ===
--- /tmp/old.XFUbyE 2026-10-31 18:26:45.671547723 +0000
+++ /tmp/new.CrOBqp 2026-10-31 18:26:45.671547723 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/aircall/darwin.json=== Install Script (no changes) ===
=== Uninstall // ca14cb0a -> 03acc2bb ===
--- /tmp/old.nut9pC 2026-10-31 18:26:45.703547774 +0000
+++ /tmp/new.7qTfoH 2026-10-31 18:26:45.703547774 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/airdroid/darwin.json=== Install Script (no changes) ===
=== Uninstall // 121f3035 -> 9f70c2c3 ===
--- /tmp/old.AnFPTM 2026-10-31 18:26:45.735547824 +0000
+++ /tmp/new.lsk0lY 2026-10-31 18:26:45.735547824 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/airparrot/darwin.json=== Install Script (no changes) ===
=== Uninstall // ed9bc6f3 -> 21104ec0 ===
--- /tmp/old.VBFBda 2026-10-31 18:26:45.767547875 +0000
+++ /tmp/new.CyLEuH 2026-10-31 18:26:45.767547875 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/airserver/darwin.json=== Install Script (no changes) ===
=== Uninstall // 5f07d184 -> 44ba3f40 ===
--- /tmp/old.29rUAE 2026-10-31 18:26:45.803547931 +0000
+++ /tmp/new.k2GzTD 2026-10-31 18:26:45.803547931 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/airtable/darwin.json=== Install Script (no changes) ===
=== Uninstall // cfbe4ea7 -> 09c3fe3c ===
--- /tmp/old.p3jYia 2026-10-31 18:26:45.835547982 +0000
+++ /tmp/new.u9VVaH 2026-10-31 18:26:45.836547983 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/airtame/darwin.json=== Install Script (no changes) ===
=== Uninstall // e261b627 -> 7f3a4b16 ===
--- /tmp/old.qXy3LF 2026-10-31 18:26:45.868548034 +0000
+++ /tmp/new.ERsF86 2026-10-31 18:26:45.868548034 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/airy/darwin.json=== Install Script (no changes) ===
=== Uninstall // 1bdd53bf -> bd142597 ===
--- /tmp/old.bOjKG3 2026-10-31 18:26:45.900548084 +0000
+++ /tmp/new.HkCJu4 2026-10-31 18:26:45.900548084 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/akiflow/darwin.json=== Install Script (no changes) ===
=== Uninstall // c7ccf1d0 -> d6e318c3 ===
--- /tmp/old.9LDWIF 2026-10-31 18:26:45.932548135 +0000
+++ /tmp/new.PJtEKY 2026-10-31 18:26:45.933548136 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/alcove/darwin.json=== Install Script (no changes) ===
=== Uninstall // 27aeb336 -> 560258cc ===
--- /tmp/old.G5HI7q 2026-10-31 18:26:45.996548236 +0000
+++ /tmp/new.I3Q5R1 2026-10-31 18:26:45.996548236 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/avast-secure-browser/darwin.json=== Install Script (no changes) ===
=== Uninstall // 767278d7 -> ecd7cd9e ===
--- /tmp/old.T6hq39 2026-10-31 18:26:46.033548294 +0000
+++ /tmp/new.PDDEKV 2026-10-31 18:26:46.033548294 +0000
@@ -123,6 +123,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -130,19 +149,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -161,11 +179,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -179,9 +197,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/camtasia/darwin.json=== Install Script (no changes) ===
=== Uninstall // 20e151f4 -> df5ce3c4 ===
--- /tmp/old.gGicQm 2026-10-31 18:26:46.067548348 +0000
+++ /tmp/new.4T2hXf 2026-10-31 18:26:46.067548348 +0000
@@ -5,6 +5,25 @@
LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }')
# functions
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -12,19 +31,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -43,11 +61,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -61,9 +79,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/cisco-jabber/darwin.json=== Install Script (no changes) ===
=== Uninstall // fb3996c9 -> b6c38756 ===
--- /tmp/old.Dw3GEr 2026-10-31 18:26:46.102548403 +0000
+++ /tmp/new.gUNgY3 2026-10-31 18:26:46.102548403 +0000
@@ -111,6 +111,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -118,19 +137,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -149,11 +167,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -167,9 +185,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/citrix-workspace/darwin.json=== Install Script (no changes) ===
=== Uninstall // b077b2e9 -> 610684e2 ===
--- /tmp/old.MBRpn5 2026-10-31 18:26:46.134548453 +0000
+++ /tmp/new.2Dd7YB 2026-10-31 18:26:46.135548455 +0000
@@ -206,6 +206,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -213,19 +232,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -244,11 +262,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -262,9 +280,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/cleanmymac/darwin.json=== Install Script (no changes) ===
=== Uninstall // a2214fc2 -> 3bce6742 ===
--- /tmp/old.IFZBlb 2026-10-31 18:26:46.167548505 +0000
+++ /tmp/new.zie91X 2026-10-31 18:26:46.167548505 +0000
@@ -140,6 +140,25 @@
done
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -147,19 +166,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -178,11 +196,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -196,9 +214,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/comet/darwin.json=== Install Script (no changes) ===
=== Uninstall // 1cbb381b -> 072a8cee ===
--- /tmp/old.UiQ5OZ 2026-10-31 18:26:46.200548558 +0000
+++ /tmp/new.wUEbx5 2026-10-31 18:26:46.200548558 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/crashplan/darwin.json=== Install Script (no changes) ===
=== Uninstall // 98dc4b5b -> b6ef5882 ===
--- /tmp/old.twJ7DZ 2026-10-31 18:26:46.233548610 +0000
+++ /tmp/new.EsdZeQ 2026-10-31 18:26:46.234548611 +0000
@@ -245,6 +245,25 @@
sleep 3
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -252,19 +271,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -283,11 +301,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -301,9 +319,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/cycling74-max/darwin.json=== Install Script (no changes) ===
=== Uninstall // ddb0bd80 -> 81adddbe ===
--- /tmp/old.tbOXRM 2026-10-31 18:26:46.267548663 +0000
+++ /tmp/new.Ufx2bB 2026-10-31 18:26:46.267548663 +0000
@@ -5,6 +5,25 @@
LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }')
# functions
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -12,19 +31,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -43,11 +61,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -61,9 +79,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/downie/darwin.json=== Install Script (no changes) ===
=== Uninstall // 8d3773ec -> 4a9b4cc6 ===
--- /tmp/old.mZ5ggC 2026-10-31 18:26:46.300548715 +0000
+++ /tmp/new.2RCZuN 2026-10-31 18:26:46.300548715 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/dymo-connect/darwin.json=== Install Script (no changes) ===
=== Uninstall // cfe50ddd -> aa3d5a72 ===
--- /tmp/old.jJrJVo 2026-10-31 18:26:46.334548769 +0000
+++ /tmp/new.D9oAC8 2026-10-31 18:26:46.334548769 +0000
@@ -245,6 +245,25 @@
sleep 3
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -252,19 +271,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -283,11 +301,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -301,9 +319,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/evernote/darwin.json=== Install Script (no changes) ===
=== Uninstall // 0e526451 -> 7f9aa1a9 ===
--- /tmp/old.1iUNZK 2026-10-31 18:26:46.368548822 +0000
+++ /tmp/new.zKKGRV 2026-10-31 18:26:46.369548824 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/forklift/darwin.json=== Install Script (no changes) ===
=== Uninstall // 3e061d0c -> 2cb443a5 ===
--- /tmp/old.4UCtL5 2026-10-31 18:26:46.401548874 +0000
+++ /tmp/new.5KIsnF 2026-10-31 18:26:46.401548874 +0000
@@ -140,6 +140,25 @@
done
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -147,19 +166,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -178,11 +196,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -196,9 +214,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/freefilesync/darwin.json=== Install Script (no changes) ===
=== Uninstall // ee1d08f3 -> 3d6bcfb1 ===
--- /tmp/old.OywCkv 2026-10-31 18:26:46.434548926 +0000
+++ /tmp/new.d0NjJf 2026-10-31 18:26:46.434548926 +0000
@@ -111,6 +111,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -118,19 +137,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -149,11 +167,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -167,9 +185,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/google-gemini/darwin.json=== Install Script (no changes) ===
=== Uninstall // 605aa85c -> fce44a81 ===
--- /tmp/old.OfWTrY 2026-10-31 18:26:46.468548980 +0000
+++ /tmp/new.BTadSD 2026-10-31 18:26:46.468548980 +0000
@@ -207,6 +207,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -214,19 +233,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -245,11 +263,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -263,9 +281,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/gpg-suite/darwin.json=== Install Script (no changes) ===
=== Uninstall // 4c789744 -> f4179907 ===
--- /tmp/old.kxshhi 2026-10-31 18:26:46.501549032 +0000
+++ /tmp/new.z7SrbW 2026-10-31 18:26:46.502549034 +0000
@@ -206,6 +206,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -213,19 +232,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -244,11 +262,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -262,9 +280,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/homebrew-app/darwin.json=== Install Script (no changes) ===
=== Uninstall // fd995ba0 -> 353138cb ===
--- /tmp/old.qQfyGM 2026-10-31 18:26:46.536549087 +0000
+++ /tmp/new.q1RK0H 2026-10-31 18:26:46.536549087 +0000
@@ -5,6 +5,25 @@
LOGGED_IN_USER=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }')
# functions
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -12,19 +31,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -43,11 +61,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -61,9 +79,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/intune-company-portal/darwin.json=== Install Script (no changes) ===
=== Uninstall // c44baeb6 -> 82b077a5 ===
--- /tmp/old.GDyHrT 2026-10-31 18:26:46.570549141 +0000
+++ /tmp/new.viy69v 2026-10-31 18:26:46.570549141 +0000
@@ -206,6 +206,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -213,19 +232,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -244,11 +262,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -262,9 +280,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/itsycal/darwin.json=== Install Script (no changes) ===
=== Uninstall // 70221fd9 -> 3321751a ===
--- /tmp/old.QS4YlG 2026-10-31 18:26:46.605549196 +0000
+++ /tmp/new.9jymM6 2026-10-31 18:26:46.605549196 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/keeper-password-manager/darwin.json=== Install Script (no changes) ===
=== Uninstall // efff3b92 -> fb583df8 ===
--- /tmp/old.fWys6a 2026-10-31 18:26:46.637549247 +0000
+++ /tmp/new.bwSAHZ 2026-10-31 18:26:46.637549247 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/kicad/darwin.json=== Install Script (no changes) ===
=== Uninstall // 437634ef -> fff2dc2f ===
--- /tmp/old.hDV99X 2026-10-31 18:26:46.672549302 +0000
+++ /tmp/new.4yiCgE 2026-10-31 18:26:46.672549302 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/krisp/darwin.json=== Install Script (no changes) ===
=== Uninstall // 10cabb7a -> 39e0e845 ===
--- /tmp/old.5rRxr1 2026-10-31 18:26:46.706549355 +0000
+++ /tmp/new.VKd1L4 2026-10-31 18:26:46.706549355 +0000
@@ -245,6 +245,25 @@
sleep 3
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -252,19 +271,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -283,11 +301,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -301,9 +319,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/microsoft-auto-update/darwin.json=== Install Script (no changes) ===
=== Uninstall // 911768de -> 91a8b766 ===
--- /tmp/old.pKKAso 2026-10-31 18:26:46.740549409 +0000
+++ /tmp/new.TM5RJs 2026-10-31 18:26:46.740549409 +0000
@@ -218,6 +218,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -225,19 +244,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -256,11 +274,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -274,9 +292,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/microsoft-azure-storage-explorer/darwin.json=== Install Script (no changes) ===
=== Uninstall // ebe9806f -> ec318db3 ===
--- /tmp/old.hgUVHu 2026-10-31 18:26:46.772549460 +0000
+++ /tmp/new.KamgoL 2026-10-31 18:26:46.772549460 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/microsoft-defender/darwin.json=== Install Script (no changes) ===
=== Uninstall // cb7b9d11 -> 8bbf0b48 ===
--- /tmp/old.S2bIMi 2026-10-31 18:26:46.805549512 +0000
+++ /tmp/new.lSi2CY 2026-10-31 18:26:46.806549513 +0000
@@ -111,6 +111,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -118,19 +137,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -149,11 +167,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -167,9 +185,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/microsoft-word/darwin.json=== Install Script (no changes) ===
=== Uninstall // f9fa325d -> a05f7a87 ===
--- /tmp/old.yOVdTY 2026-10-31 18:26:46.839549565 +0000
+++ /tmp/new.RxQTEW 2026-10-31 18:26:46.839549565 +0000
@@ -206,6 +206,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -213,19 +232,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -244,11 +262,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -262,9 +280,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/tableau-prep/darwin.json=== Install Script (no changes) ===
=== Uninstall // fe815c96 -> 71945cf2 ===
--- /tmp/old.TQMlyA 2026-10-31 18:26:46.873549619 +0000
+++ /tmp/new.4XOcNd 2026-10-31 18:26:46.873549619 +0000
@@ -111,6 +111,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -118,19 +137,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -149,11 +167,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -167,9 +185,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/tableau/darwin.json=== Install Script (no changes) ===
=== Uninstall // 192278a1 -> dcc28c47 ===
--- /tmp/old.AJajrG 2026-10-31 18:26:46.906549671 +0000
+++ /tmp/new.fh5g5k 2026-10-31 18:26:46.906549671 +0000
@@ -111,6 +111,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -118,19 +137,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -149,11 +167,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -167,9 +185,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/teleport-suite/darwin.json=== Install Script (no changes) ===
=== Uninstall // 0fdc0ba0 -> fc44df6d ===
--- /tmp/old.8hEGen 2026-10-31 18:26:46.942549728 +0000
+++ /tmp/new.19bu4d 2026-10-31 18:26:46.942549728 +0000
@@ -73,6 +73,25 @@
fi
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -80,19 +99,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -111,11 +129,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -129,9 +147,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/termius/darwin.json=== Install Script (no changes) ===
=== Uninstall // 0272f4cf -> 02d96b6b ===
--- /tmp/old.vFkut3 2026-10-31 18:26:46.975549780 +0000
+++ /tmp/new.D8hckc 2026-10-31 18:26:46.975549780 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/twobird/darwin.json=== Install Script (no changes) ===
=== Uninstall // 74f516da -> ac42231b ===
--- /tmp/old.01R6O8 2026-10-31 18:26:47.010549835 +0000
+++ /tmp/new.8fvdWP 2026-10-31 18:26:47.010549835 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/usage-app/darwin.json=== Install Script (no changes) ===
=== Uninstall // 4b90c863 -> 2c804413 ===
--- /tmp/old.Mvo7D3 2026-10-31 18:26:47.045549890 +0000
+++ /tmp/new.pgjDzk 2026-10-31 18:26:47.045549890 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/visual-paradigm/darwin.json=== Install Script (no changes) ===
=== Uninstall // 0ca7d6e3 -> c336c0cf ===
--- /tmp/old.pWSGHG 2026-10-31 18:26:47.079549944 +0000
+++ /tmp/new.rIHMrG 2026-10-31 18:26:47.079549944 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/webex/darwin.json=== Install Script (no changes) ===
=== Uninstall // ae33236a -> 141a750e ===
--- /tmp/old.NLy9fh 2026-10-31 18:26:47.111549994 +0000
+++ /tmp/new.6CmabV 2026-10-31 18:26:47.111549994 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
send_signal() {
local signal="$1"
local bundle_id="$2"
@@ -91,19 +110,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -122,11 +140,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -140,9 +158,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/wezterm/darwin.json=== Install Script (no changes) ===
=== Uninstall // ef8ecccd -> 854418e2 ===
--- /tmp/old.K3Vugg 2026-10-31 18:26:47.146550049 +0000
+++ /tmp/new.ZfsnAf 2026-10-31 18:26:47.146550049 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist."ee/maintained-apps/outputs/whispering/darwin.json=== Install Script (no changes) ===
=== Uninstall // e0aaf940 -> b20cc11a ===
--- /tmp/old.nJtED5 2026-10-31 18:26:47.180550103 +0000
+++ /tmp/new.vRyhOO 2026-10-31 18:26:47.181550105 +0000
@@ -45,6 +45,25 @@
}
+remove_system_path() {
+ local target="$1"
+ local parent name rel top expected
+ parent="$(dirname "$target")"
+ name="$(basename "$target")"
+ rel="${parent#/}"
+ top="/${rel%%/*}"
+ expected="$(cd -P "$top" 2>/dev/null && pwd -P)${parent#"$top"}"
+
+ (
+ cd -P "$parent" 2>/dev/null || exit 1
+ if [[ "$(pwd -P)" != "$expected" ]]; then
+ echo "Skipped $target: a folder in its path is a symlink."
+ exit 0
+ fi
+ rm -rf "./$name"
+ ) || echo "Failed to remove $target."
+}
+
trash() {
local logged_in_user="$1"
local target_file="$2"
@@ -52,19 +71,18 @@
local rand="$(jot -r 1 0 99999)"
local trash=""
- # At the login window there's no user Trash to move files into, so user
- # paths are skipped and system paths are deleted outright. Removal is
- # best-effort and never fails the uninstall.
- if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
- if [[ "$target_file" == ~* ]]; then
+ # Paths in the user's home are moved to their Trash as the user: the user
+ # controls those paths and ~/.Trash, so as root the move could be redirected
+ # into a root-only directory. Other paths are deleted as root by
+ # remove_system_path. At the login window there's no user Trash, so home
+ # paths are skipped. Removal is best-effort and never fails the uninstall.
+ if [[ "$target_file" == ~* ]]; then
+ if [[ -z "$logged_in_user" || "$logged_in_user" == "root" || "$logged_in_user" == "loginwindow" ]]; then
echo "Not logged into a non-root GUI; skipping $target_file."
return
fi
- else
# replace ~ with /Users/$logged_in_user
- if [[ "$target_file" == ~* ]]; then
- target_file="/Users/$logged_in_user${target_file:1}"
- fi
+ target_file="/Users/$logged_in_user${target_file:1}"
trash="/Users/$logged_in_user/.Trash"
fi
@@ -83,11 +101,11 @@
file_name="$(basename "$file")"
echo "removing $file."
if [[ -z "$trash" ]]; then
- rm -rf "$file" || echo "Failed to remove $file."
+ remove_system_path "$file"
else
# The per-match counter keeps matches that share a basename from
# overwriting each other in the trash.
- mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$file" "$trash/${file_name}_${timestamp}_${rand}_${i}" || echo "Failed to move $file to the Trash."
fi
done < <(compgen -G "$target_file" 2>/dev/null)
if [[ "$matched" == false ]]; then
@@ -101,9 +119,9 @@
if [[ -e "$target_file" ]]; then
echo "removing $target_file."
if [[ -z "$trash" ]]; then
- rm -rf "$target_file" || echo "Failed to remove $target_file."
+ remove_system_path "$target_file"
else
- mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
+ sudo -u "$logged_in_user" mv -f "$target_file" "$trash/${file_name}_${timestamp}_${rand}" || echo "Failed to move $target_file to the Trash."
fi
else
echo "$target_file doesn't exist." |
…#57087) Part 2 of 11, split from #56846. That PR changes 992 files, and the auto-approval only covers PRs under 100 files. Part 1, #57086, has the generator change, the tests, the custom scripts, and the frozen outputs; see it for the full explanation. The 11 PRs touch disjoint files, so they merge in any order without conflicts, and together they match #56846's tree exactly. Merge #57086 first. ## Change Generated macOS FMA uninstall scripts now move `~` paths from a cask's `zap trash:` into the console user's Trash as that user, with `sudo -u`, instead of as root. Other paths are deleted as root by a new `remove_system_path` helper, which skips a path when a folder in it below the top level is a symlink. This PR regenerates 91 macOS outputs, `aldente` through `captain`. Each changes only its uninstall script and `uninstall_script_ref`: no versions, SHAs, URLs, install scripts or queries. The darwin validator runs on these apps, so expect some failures from apps that had already drifted upstream. The Check Script Diff job will fail here: its comment is over GitHub's 65,536-character limit at this size. It isn't a required check. # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. ## Testing - [x] Added/updated automated tests - The tests are in #57086. `go test ./cmd/maintained-apps/... ./ee/maintained-apps/...` passes on this branch alone. - [ ] QA'd all new/changed functionality manually - Not yet run as root by fleetd on a real Mac. ## AI **AI:** Claude Code (claude-opus-5-5)
…#57088) Part 3 of 11, split from #56846. That PR changes 992 files, and the auto-approval only covers PRs under 100 files. Part 1, #57086, has the generator change, the tests, the custom scripts, and the frozen outputs; see it for the full explanation. The 11 PRs touch disjoint files, so they merge in any order without conflicts, and together they match #56846's tree exactly. Merge #57086 first. ## Change Generated macOS FMA uninstall scripts now move `~` paths from a cask's `zap trash:` into the console user's Trash as that user, with `sudo -u`, instead of as root. Other paths are deleted as root by a new `remove_system_path` helper, which skips a path when a folder in it below the top level is a symlink. This PR regenerates 90 macOS outputs, `capto` through `dia`. Each changes only its uninstall script and `uninstall_script_ref`: no versions, SHAs, URLs, install scripts or queries. The darwin validator runs on these apps, so expect some failures from apps that had already drifted upstream. The Check Script Diff job will fail here: its comment is over GitHub's 65,536-character limit at this size. It isn't a required check. # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. ## Testing - [x] Added/updated automated tests - The tests are in #57086. `go test ./cmd/maintained-apps/... ./ee/maintained-apps/...` passes on this branch alone. - [ ] QA'd all new/changed functionality manually - Not yet run as root by fleetd on a real Mac. ## AI **AI:** Claude Code (claude-opus-5-5)
…#57089) Part 4 of 11, split from #56846. That PR changes 992 files, and the auto-approval only covers PRs under 100 files. Part 1, #57086, has the generator change, the tests, the custom scripts, and the frozen outputs; see it for the full explanation. The 11 PRs touch disjoint files, so they merge in any order without conflicts, and together they match #56846's tree exactly. Merge #57086 first. ## Change Generated macOS FMA uninstall scripts now move `~` paths from a cask's `zap trash:` into the console user's Trash as that user, with `sudo -u`, instead of as root. Other paths are deleted as root by a new `remove_system_path` helper, which skips a path when a folder in it below the top level is a symlink. This PR regenerates 90 macOS outputs, `dialpad` through `free-download-manager`. Each changes only its uninstall script and `uninstall_script_ref`: no versions, SHAs, URLs, install scripts or queries. The darwin validator runs on these apps, so expect some failures from apps that had already drifted upstream. The Check Script Diff job will fail here: its comment is over GitHub's 65,536-character limit at this size. It isn't a required check. # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. ## Testing - [x] Added/updated automated tests - The tests are in #57086. `go test ./cmd/maintained-apps/... ./ee/maintained-apps/...` passes on this branch alone. - [ ] QA'd all new/changed functionality manually - Not yet run as root by fleetd on a real Mac. ## AI **AI:** Claude Code (claude-opus-5-5)
…#57090) Part 5 of 11, split from #56846. That PR changes 992 files, and the auto-approval only covers PRs under 100 files. Part 1, #57086, has the generator change, the tests, the custom scripts, and the frozen outputs; see it for the full explanation. The 11 PRs touch disjoint files, so they merge in any order without conflicts, and together they match #56846's tree exactly. Merge #57086 first. ## Change Generated macOS FMA uninstall scripts now move `~` paths from a cask's `zap trash:` into the console user's Trash as that user, with `sudo -u`, instead of as root. Other paths are deleted as root by a new `remove_system_path` helper, which skips a path when a folder in it below the top level is a symlink. This PR regenerates 90 macOS outputs, `front` through `julia-app`. Each changes only its uninstall script and `uninstall_script_ref`: no versions, SHAs, URLs, install scripts or queries. The darwin validator runs on these apps, so expect some failures from apps that had already drifted upstream. The Check Script Diff job will fail here: its comment is over GitHub's 65,536-character limit at this size. It isn't a required check. # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. ## Testing - [x] Added/updated automated tests - The tests are in #57086. `go test ./cmd/maintained-apps/... ./ee/maintained-apps/...` passes on this branch alone. - [ ] QA'd all new/changed functionality manually - Not yet run as root by fleetd on a real Mac. ## AI **AI:** Claude Code (claude-opus-5-5)
…#57091) Part 6 of 11, split from #56846. That PR changes 992 files, and the auto-approval only covers PRs under 100 files. Part 1, #57086, has the generator change, the tests, the custom scripts, and the frozen outputs; see it for the full explanation. The 11 PRs touch disjoint files, so they merge in any order without conflicts, and together they match #56846's tree exactly. Merge #57086 first. ## Change Generated macOS FMA uninstall scripts now move `~` paths from a cask's `zap trash:` into the console user's Trash as that user, with `sudo -u`, instead of as root. Other paths are deleted as root by a new `remove_system_path` helper, which skips a path when a folder in it below the top level is a symlink. This PR regenerates 90 macOS outputs, `jump-desktop` through `microsoft-excel`. Each changes only its uninstall script and `uninstall_script_ref`: no versions, SHAs, URLs, install scripts or queries. The darwin validator runs on these apps, so expect some failures from apps that had already drifted upstream. The Check Script Diff job will fail here: its comment is over GitHub's 65,536-character limit at this size. It isn't a required check. # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. ## Testing - [x] Added/updated automated tests - The tests are in #57086. `go test ./cmd/maintained-apps/... ./ee/maintained-apps/...` passes on this branch alone. - [ ] QA'd all new/changed functionality manually - Not yet run as root by fleetd on a real Mac. ## AI **AI:** Claude Code (claude-opus-5-5)
…#57092) Part 7 of 11, split from #56846. That PR changes 992 files, and the auto-approval only covers PRs under 100 files. Part 1, #57086, has the generator change, the tests, the custom scripts, and the frozen outputs; see it for the full explanation. The 11 PRs touch disjoint files, so they merge in any order without conflicts, and together they match #56846's tree exactly. Merge #57086 first. ## Change Generated macOS FMA uninstall scripts now move `~` paths from a cask's `zap trash:` into the console user's Trash as that user, with `sudo -u`, instead of as root. Other paths are deleted as root by a new `remove_system_path` helper, which skips a path when a folder in it below the top level is a symlink. This PR regenerates 90 macOS outputs, `microsoft-onenote` through `openboard`. Each changes only its uninstall script and `uninstall_script_ref`: no versions, SHAs, URLs, install scripts or queries. The darwin validator runs on these apps, so expect some failures from apps that had already drifted upstream. The Check Script Diff job will fail here: its comment is over GitHub's 65,536-character limit at this size. It isn't a required check. # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. ## Testing - [x] Added/updated automated tests - The tests are in #57086. `go test ./cmd/maintained-apps/... ./ee/maintained-apps/...` passes on this branch alone. - [ ] QA'd all new/changed functionality manually - Not yet run as root by fleetd on a real Mac. ## AI **AI:** Claude Code (claude-opus-5-5)
…#57093) Part 8 of 11, split from #56846. That PR changes 992 files, and the auto-approval only covers PRs under 100 files. Part 1, #57086, has the generator change, the tests, the custom scripts, and the frozen outputs; see it for the full explanation. The 11 PRs touch disjoint files, so they merge in any order without conflicts, and together they match #56846's tree exactly. Merge #57086 first. ## Change Generated macOS FMA uninstall scripts now move `~` paths from a cask's `zap trash:` into the console user's Trash as that user, with `sudo -u`, instead of as root. Other paths are deleted as root by a new `remove_system_path` helper, which skips a path when a folder in it below the top level is a symlink. This PR regenerates 90 macOS outputs, `opencloud` through `raycast`. Each changes only its uninstall script and `uninstall_script_ref`: no versions, SHAs, URLs, install scripts or queries. The darwin validator runs on these apps, so expect some failures from apps that had already drifted upstream. The Check Script Diff job will fail here: its comment is over GitHub's 65,536-character limit at this size. It isn't a required check. # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. ## Testing - [x] Added/updated automated tests - The tests are in #57086. `go test ./cmd/maintained-apps/... ./ee/maintained-apps/...` passes on this branch alone. - [ ] QA'd all new/changed functionality manually - Not yet run as root by fleetd on a real Mac. ## AI **AI:** Claude Code (claude-opus-5-5)
There was a problem hiding this comment.
🔵 Needs a closer look
Root-executed deletion and user-owned Trash moves lack real fleetd-on-macOS validation, while sudo-stubbed tests do not establish TCC behavior or symlink-race safety.
No open issues 🎉
What changed in this PR
This PR begins an 11-part hardening update for Fleet-maintained macOS app uninstalls. It updates the shared generator, custom uninstall scripts, and selected outputs so user-home cleanup runs as the console user rather than root.
Changes:
- Moves home-directory files to Trash with
sudo -u, without falling back to root when a move fails. - Deletes system cleanup paths through a helper that rejects symlinked parent folders below the top level.
- Expands regression tests for user moves, system deletion, symlink protection, failure handling, and custom-script consistency.
| File | Description |
|---|---|
ee/maintained-apps/outputs/wezterm/darwin.json |
Updates embedded uninstall helpers and script reference. |
ee/maintained-apps/outputs/visual-paradigm/darwin.json |
Updates embedded uninstall helpers and script reference. |
ee/maintained-apps/outputs/usage-app/darwin.json |
Updates embedded uninstall helpers and script reference. |
ee/maintained-apps/outputs/twobird/darwin.json |
Updates embedded uninstall helpers and script reference. |
ee/maintained-apps/outputs/termius/darwin.json |
Updates embedded uninstall helpers and script reference. |
ee/maintained-apps/outputs/microsoft-azure-storage-explorer/darwin.json |
Updates embedded uninstall helpers and script reference. |
ee/maintained-apps/outputs/cycling74-max/darwin.json |
Embeds hardened home and shared-directory cleanup. |
ee/maintained-apps/outputs/comet/darwin.json |
Updates embedded uninstall helpers and script reference. |
ee/maintained-apps/outputs/alcove/darwin.json |
Updates embedded uninstall helpers and script reference. |
ee/maintained-apps/outputs/akiflow/darwin.json |
Updates embedded uninstall helpers and script reference. |
ee/maintained-apps/outputs/affinity-publisher@1/darwin.json |
Updates embedded uninstall helpers and script reference. |
ee/maintained-apps/outputs/affinity-photo/darwin.json |
Updates embedded uninstall helpers and script reference. |
ee/maintained-apps/outputs/activitywatch/darwin.json |
Updates embedded uninstall helpers and script reference. |
ee/maintained-apps/outputs/4k-youtube-to-mp3/darwin.json |
Updates embedded uninstall helpers, including glob cleanup. |
ee/maintained-apps/inputs/homebrew/scripts/wezterm-uninstall.sh |
Adopts hardened cleanup helpers. |
ee/maintained-apps/inputs/homebrew/scripts/teleport-suite-uninstall.sh |
Adopts hardened cleanup helpers. |
ee/maintained-apps/inputs/homebrew/scripts/microsoft-auto-update-uninstall.sh |
Adopts hardened cleanup helpers. |
ee/maintained-apps/inputs/homebrew/scripts/microsoft_word_uninstall.sh |
Adopts hardened cleanup helpers. |
ee/maintained-apps/inputs/homebrew/scripts/krisp-uninstall.sh |
Adopts hardened cleanup helpers. |
ee/maintained-apps/inputs/homebrew/scripts/kicad-uninstall.sh |
Adopts hardened cleanup helpers. |
ee/maintained-apps/inputs/homebrew/scripts/homebrew-app-uninstall.sh |
Adopts hardened cleanup helpers. |
ee/maintained-apps/inputs/homebrew/scripts/gpg-suite-uninstall.sh |
Adopts hardened cleanup helpers. |
ee/maintained-apps/inputs/homebrew/scripts/forklift-uninstall.sh |
Adopts hardened cleanup helpers. |
ee/maintained-apps/inputs/homebrew/scripts/dymo-connect-uninstall.sh |
Adopts hardened cleanup helpers. |
ee/maintained-apps/inputs/homebrew/scripts/cycling74-max-uninstall.sh |
Hardens home and shared-directory cleanup. |
ee/maintained-apps/inputs/homebrew/scripts/crashplan-uninstall.sh |
Adopts hardened cleanup helpers. |
ee/maintained-apps/inputs/homebrew/scripts/cleanmymac-uninstall.sh |
Adopts hardened cleanup helpers. |
ee/maintained-apps/inputs/homebrew/scripts/citrix-workspace-uninstall.sh |
Adopts hardened cleanup helpers. |
ee/maintained-apps/inputs/homebrew/scripts/camtasia_uninstall.sh |
Adopts hardened cleanup helpers. |
ee/maintained-apps/inputs/homebrew/scripts/1password_uninstall.sh |
Adopts hardened cleanup helpers. |
ee/maintained-apps/ingesters/homebrew/scripts.go |
Generates user-scoped Trash moves and guarded system deletion. |
ee/maintained-apps/ingesters/homebrew/scripts_test.go |
Adds cleanup behavior checks and helper consistency assertions. |
Review effort: Balanced
…#57094) Part 9 of 11, split from #56846. That PR changes 992 files, and the auto-approval only covers PRs under 100 files. Part 1, #57086, has the generator change, the tests, the custom scripts, and the frozen outputs; see it for the full explanation. The 11 PRs touch disjoint files, so they merge in any order without conflicts, and together they match #56846's tree exactly. Merge #57086 first. ## Change Generated macOS FMA uninstall scripts now move `~` paths from a cask's `zap trash:` into the console user's Trash as that user, with `sudo -u`, instead of as root. Other paths are deleted as root by a new `remove_system_path` helper, which skips a path when a folder in it below the top level is a symlink. This PR regenerates 90 macOS outputs, `readest` through `soundsource`. Each changes only its uninstall script and `uninstall_script_ref`: no versions, SHAs, URLs, install scripts or queries. The darwin validator runs on these apps, so expect some failures from apps that had already drifted upstream. The Check Script Diff job will fail here: its comment is over GitHub's 65,536-character limit at this size. It isn't a required check. # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. ## Testing - [x] Added/updated automated tests - The tests are in #57086. `go test ./cmd/maintained-apps/... ./ee/maintained-apps/...` passes on this branch alone. - [ ] QA'd all new/changed functionality manually - Not yet run as root by fleetd on a real Mac. ## AI **AI:** Claude Code (claude-opus-5-5)
#57095) Part 10 of 11, split from #56846. That PR changes 992 files, and the auto-approval only covers PRs under 100 files. Part 1, #57086, has the generator change, the tests, the custom scripts, and the frozen outputs; see it for the full explanation. The 11 PRs touch disjoint files, so they merge in any order without conflicts, and together they match #56846's tree exactly. Merge #57086 first. ## Change Generated macOS FMA uninstall scripts now move `~` paths from a cask's `zap trash:` into the console user's Trash as that user, with `sudo -u`, instead of as root. Other paths are deleted as root by a new `remove_system_path` helper, which skips a path when a folder in it below the top level is a symlink. This PR regenerates 90 macOS outputs, `sourcetree` through `twingate`. Each changes only its uninstall script and `uninstall_script_ref`: no versions, SHAs, URLs, install scripts or queries. The darwin validator runs on these apps, so expect some failures from apps that had already drifted upstream. The Check Script Diff job will fail here: its comment is over GitHub's 65,536-character limit at this size. It isn't a required check. # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. ## Testing - [x] Added/updated automated tests - The tests are in #57086. `go test ./cmd/maintained-apps/... ./ee/maintained-apps/...` passes on this branch alone. - [ ] QA'd all new/changed functionality manually - Not yet run as root by fleetd on a real Mac. ## AI **AI:** Claude Code (claude-opus-5-5)
#57096) Part 11 of 11, split from #56846. That PR changes 992 files, and the auto-approval only covers PRs under 100 files. Part 1, #57086, has the generator change, the tests, the custom scripts, and the frozen outputs; see it for the full explanation. The 11 PRs touch disjoint files, so they merge in any order without conflicts, and together they match #56846's tree exactly. Merge #57086 first. ## Change Generated macOS FMA uninstall scripts now move `~` paths from a cask's `zap trash:` into the console user's Trash as that user, with `sudo -u`, instead of as root. Other paths are deleted as root by a new `remove_system_path` helper, which skips a path when a folder in it below the top level is a symlink. This PR regenerates 90 macOS outputs, `typeface` through `zwift`. Each changes only its uninstall script and `uninstall_script_ref`: no versions, SHAs, URLs, install scripts or queries. The darwin validator runs on these apps, so expect some failures from apps that had already drifted upstream. The Check Script Diff job will fail here: its comment is over GitHub's 65,536-character limit at this size. It isn't a required check. # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. ## Testing - [x] Added/updated automated tests - The tests are in #57086. `go test ./cmd/maintained-apps/... ./ee/maintained-apps/...` passes on this branch alone. - [ ] QA'd all new/changed functionality manually - Not yet run as root by fleetd on a real Mac. ## AI **AI:** Claude Code (claude-opus-5-5)
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #57086 +/- ##
==========================================
- Coverage 78.14% 78.12% -0.02%
==========================================
Files 4428 4428
Lines 279336 279345 +9
Branches 16431 16431
==========================================
- Hits 218278 218246 -32
- Misses 60862 60903 +41
Partials 196 196
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Part 1 of 11, split from #56846. That PR changes 992 files, and the auto-approval only covers PRs under 100 files. The 11 PRs touch disjoint files, so they merge in any order without conflicts, and together they match #56846's tree exactly. Merge this one first so the generator change lands before, or with, the rest of the outputs.
Other parts:
aldentethroughcaptaincaptothroughdiadialpadthroughfree-download-managerfrontthroughjulia-appjump-desktopthroughmicrosoft-excelmicrosoft-onenotethroughopenboardopencloudthroughraycastreadestthroughsoundsourcesourcetreethroughtwingatetypefacethroughzwiftSummary
Hardening for the
trash()helper in generated macOS FMA uninstall scripts. Fleet runs these scripts as root.trash()moved the cask'szappaths into the console user's~/.Trashwith a rootmv -f. Those paths and~/.Trashboth belong to that user, so root shouldn't be the one writing there.~/...) are moved to the Trash as the user, withsudo -u "$logged_in_user" mv -f. If the user's move fails, the script logsFailed to move <path> to the Trash.and root doesn't take over.remove_system_pathhelper instead of being moved into the user's Trash, such as/Library/LaunchDaemons/*.plist,/Library/PrivilegedHelperTools/*and/Users/Shared/*. The helper already deleted these at the login window (macOS FMA uninstalls: stop when the app won't quit, and work when no one is logged in #56457).rm -rfdoesn't follow a symlink at the end of a path, but it does follow one in a parent folder. For example, a user who creates/Users/Shared/TechSmithas a symlink first could aim Camtasia's/Users/Shared/TechSmith/Camtasiadelete at a root-only folder.remove_system_pathusescd -Pto enter the real parent folder, checks that it's the folder the path names, and deletes./<name>from there, so a symlink swapped in after the check has no effect. Only top-level symlinks such as/tmpand/etcare followed, because users can't write to/. A path that fails the check is left in place and logged asSkipped <path>: a folder in its path is a symlink.~paths are still skipped, and cleanup stays best-effort:trash()never fails the uninstall.Behavior change: system paths listed in a cask's
zap trash:are deleted, not moved to the user's Trash. A system path with a symlink in its parent folders below the top level is skipped.A shell without Full Disk Access can still move files into
~/.Trashby name (only listing it is blocked), so the move as the user doesn't depend on fleetd's TCC grants.Outputs
Across all 11 parts, 974 macOS outputs change. Each changes only its uninstall script and
uninstall_script_ref. Version, URL, sha256, install script and queries are byte-identical tomain.This part has the generator, its tests, the 16 custom uninstall scripts, and 73 outputs:
trash(), now the new one plusremove_system_path(), including Citrix Workspace's, added on main in Citrix Workspace FMA: remove the runtime-copied updater on uninstall #57081.TestCustomUninstallScriptsUseCurrentTrashkeeps both identical to the generator.010-editorthroughalcove.Parts 2–11 have the other 901 generated outputs. The outputs were built and cross-checked against
cmd/maintained-appsregeneration in #56846.Because these outputs change, the darwin validator runs on each part's apps. Expect some failures from apps that had already drifted upstream. The Check Script Diff job will fail here: its comment is over GitHub's 65,536-character limit at this size. It isn't a required check.
Checklist for submitter
SELECT *is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.Testing
TestUninstallScriptTrash(renamed fromTestUninstallScriptTrashWithoutGUIUser) runs generatedtrashcalls in bash with the user's home under a temp dir. A baremvis stubbed to fail, so the test fails if root ever moves a file. It checks that:~/.Trashthroughsudo -uThe test fails against the old helper, and also fails with the symlink check in
remove_system_pathturned off.Also checked:
go test ./cmd/maintained-apps/... ./ee/maintained-apps/...passes on this branch alone, and on each of parts 2–11 alonebash -nremove_system_pathdeletes a normal path, skips one under a symlinked folder, removes a symlink at the end of the path without touching its target, and follows/tmpNot yet run as root by fleetd on a real Mac.
AI
AI: Claude Code (claude-opus-5-5)