|
#!/usr/bin/env bash |
|
set -u |
|
set -o pipefail |
|
|
|
# ============================================================ |
|
# GREAT DELETE |
|
# macOS developer-cruft auditor + cleaner |
|
# ============================================================ |
|
# |
|
# Default: DRY RUN. |
|
# |
|
# Safety model: |
|
# - Never deletes source repositories. |
|
# - Never deletes Docker volumes. |
|
# - Never deletes Xcode Archives. |
|
# - Never deletes Maven ~/.m2/repository. |
|
# - Never auto-removes Rust toolchains. |
|
# - Skips explicitly protected projects. |
|
# - Skips Git repos with uncommitted work. |
|
# - Skips Git repos with commits newer than --days. |
|
# - Skips project artifacts if an active process appears to reference the repo. |
|
# |
|
# Examples: |
|
# ./great-delete.sh |
|
# ./great-delete.sh --days 4 |
|
# ./great-delete.sh --days 4 --apply --aggressive --docker |
|
# ./great-delete.sh --all --apply --aggressive --docker |
|
# ./great-delete.sh --days 4 --protect /path/to/project --apply |
|
|
|
STALE_DAYS=14 |
|
APPLY=0 |
|
AGGRESSIVE=0 |
|
DOCKER=0 |
|
ALL_PROJECT_ARTIFACTS=0 |
|
CLEAR_USER_CACHES=0 |
|
|
|
# Nothing is protected by default. |
|
# Add protection manually with: |
|
# --protect /path/to/project |
|
PROTECTED_PATHS=() |
|
|
|
TOTAL_FOUND_KB=0 |
|
TOTAL_DELETED_KB=0 |
|
TOTAL_SKIPPED_KB=0 |
|
RUST_KB=0 |
|
NODE_KB=0 |
|
FRAMEWORK_KB=0 |
|
TOOLCACHE_KB=0 |
|
APP_CACHE_KB=0 |
|
|
|
usage() { |
|
cat <<'USAGE' |
|
Usage: great-delete.sh [options] |
|
|
|
Options: |
|
--days N Project is eligible only if its Git history is older than N |
|
days and it has no uncommitted work. Default: 14. |
|
|
|
--all Ignore project age, but STILL honor protected paths, |
|
dirty Git repos, and active-process safeguards. |
|
|
|
--protect PATH Never delete project artifacts inside PATH. |
|
May be repeated. |
|
|
|
--apply Actually delete. |
|
Without this, the script is a dry run. |
|
|
|
--aggressive Clear deeper reproducible language/tool caches. |
|
|
|
--docker Prune unused Docker images, containers, networks, |
|
and build cache. |
|
Docker volumes are NEVER deleted. |
|
|
|
--user-caches Also clear the CONTENTS of ~/Library/Caches. |
|
More aggressive; applications will rebuild caches. |
|
|
|
-h, --help Show help. |
|
|
|
Recommended aggressive cleanup: |
|
|
|
./great-delete.sh --days 4 --apply --aggressive --docker |
|
|
|
Maximum project-artifact cleanup with safeguards: |
|
|
|
./great-delete.sh --all --apply --aggressive --docker |
|
|
|
Maximum cleanup including user caches: |
|
|
|
./great-delete.sh --all --apply --aggressive --docker --user-caches |
|
USAGE |
|
} |
|
|
|
while [ "$#" -gt 0 ]; do |
|
case "$1" in |
|
|
|
--days) |
|
shift |
|
|
|
[ "$#" -gt 0 ] || { |
|
echo "Missing value for --days" >&2 |
|
exit 2 |
|
} |
|
|
|
STALE_DAYS="$1" |
|
;; |
|
|
|
--all) |
|
ALL_PROJECT_ARTIFACTS=1 |
|
;; |
|
|
|
--protect) |
|
shift |
|
|
|
[ "$#" -gt 0 ] || { |
|
echo "Missing value for --protect" >&2 |
|
exit 2 |
|
} |
|
|
|
PROTECTED_PATHS+=("${1%/}") |
|
;; |
|
|
|
--apply) |
|
APPLY=1 |
|
;; |
|
|
|
--aggressive) |
|
AGGRESSIVE=1 |
|
;; |
|
|
|
--docker) |
|
DOCKER=1 |
|
;; |
|
|
|
--user-caches) |
|
CLEAR_USER_CACHES=1 |
|
;; |
|
|
|
-h|--help) |
|
usage |
|
exit 0 |
|
;; |
|
|
|
*) |
|
echo "Unknown argument: $1" >&2 |
|
usage |
|
exit 2 |
|
;; |
|
|
|
esac |
|
|
|
shift |
|
done |
|
|
|
case "$STALE_DAYS" in |
|
''|*[!0-9]*) |
|
echo "--days must be a non-negative integer." >&2 |
|
exit 2 |
|
;; |
|
esac |
|
|
|
# ============================================================ |
|
# KUJO BINARY SAFETY |
|
# ============================================================ |
|
# |
|
# If `kujo` itself resolves inside a repo target directory, |
|
# automatically protect that repo. |
|
# |
|
# Example: |
|
# |
|
# /some/repo/target/release/kujo |
|
# |
|
# If Kujo instead resolves to: |
|
# |
|
# ~/.cargo/bin/kujo |
|
# /usr/local/bin/kujo |
|
# /opt/homebrew/bin/kujo |
|
# |
|
# then the repo target directory is not required to execute |
|
# the installed Kujo binary. |
|
|
|
KUJO_BIN="" |
|
|
|
if command -v kujo >/dev/null 2>&1; then |
|
KUJO_BIN="$(command -v kujo)" |
|
|
|
case "$KUJO_BIN" in |
|
*/target/*) |
|
PROTECTED_PATHS+=("${KUJO_BIN%%/target/*}") |
|
;; |
|
esac |
|
fi |
|
|
|
NOW="$(date +%s)" |
|
CUTOFF=$(( NOW - STALE_DAYS * 86400 )) |
|
|
|
MODE="DRY RUN" |
|
|
|
[ "$APPLY" -eq 1 ] && MODE="DELETE" |
|
|
|
before_free_kb="$( |
|
df -Pk "$HOME" | |
|
awk 'NR==2 {print $4}' |
|
)" |
|
|
|
# ============================================================ |
|
# HELPERS |
|
# ============================================================ |
|
|
|
human_kb() { |
|
awk -v kb="${1:-0}" 'BEGIN { |
|
|
|
bytes = kb * 1024 |
|
|
|
split("B KB MB GB TB", u, " ") |
|
|
|
i = 1 |
|
|
|
while (bytes >= 1024 && i < 5) { |
|
bytes /= 1024 |
|
i++ |
|
} |
|
|
|
printf "%.1f %s", bytes, u[i] |
|
}' |
|
} |
|
|
|
dir_kb() { |
|
du -sk "$1" 2>/dev/null | |
|
awk '{print $1+0}' |
|
} |
|
|
|
command_exists() { |
|
command -v "$1" >/dev/null 2>&1 |
|
} |
|
|
|
canonical_path() { |
|
local p="$1" |
|
|
|
if [ -d "$p" ]; then |
|
|
|
( |
|
cd "$p" 2>/dev/null && |
|
pwd -P |
|
) || printf '%s\n' "$p" |
|
|
|
else |
|
|
|
printf '%s\n' "$p" |
|
|
|
fi |
|
} |
|
|
|
is_protected() { |
|
local path="$1" |
|
local protected |
|
|
|
for protected in "${PROTECTED_PATHS[@]}"; do |
|
|
|
[ -n "$protected" ] || continue |
|
|
|
protected="$(canonical_path "$protected")" |
|
|
|
case "$path" in |
|
|
|
"$protected"|"$protected"/*) |
|
return 0 |
|
;; |
|
|
|
esac |
|
|
|
done |
|
|
|
return 1 |
|
} |
|
|
|
find_project_root() { |
|
local start="$1" |
|
local dir |
|
local fallback |
|
|
|
dir="$(dirname "$start")" |
|
fallback="" |
|
|
|
# Prefer an actual Git root. |
|
if command_exists git; then |
|
|
|
local gitroot |
|
|
|
gitroot="$( |
|
git -C "$dir" \ |
|
rev-parse \ |
|
--show-toplevel \ |
|
2>/dev/null || true |
|
)" |
|
|
|
if [ -n "$gitroot" ]; then |
|
printf '%s\n' "$gitroot" |
|
return 0 |
|
fi |
|
|
|
fi |
|
|
|
# Non-Git project fallback. |
|
while [ "$dir" != "$HOME" ] && |
|
[ "$dir" != "/" ]; do |
|
|
|
if [ -z "$fallback" ] && { |
|
[ -f "$dir/Cargo.toml" ] || |
|
[ -f "$dir/package.json" ] || |
|
[ -f "$dir/pyproject.toml" ] || |
|
[ -f "$dir/go.mod" ] |
|
}; then |
|
|
|
fallback="$dir" |
|
|
|
fi |
|
|
|
dir="$(dirname "$dir")" |
|
|
|
done |
|
|
|
if [ -n "$fallback" ]; then |
|
printf '%s\n' "$fallback" |
|
else |
|
printf '%s\n' "$(dirname "$start")" |
|
fi |
|
} |
|
|
|
repo_has_uncommitted_work() { |
|
local root="$1" |
|
|
|
[ -e "$root/.git" ] || return 1 |
|
|
|
command_exists git || return 1 |
|
|
|
[ |
|
-n "$( |
|
git -C "$root" \ |
|
status \ |
|
--porcelain \ |
|
--untracked-files=normal \ |
|
2>/dev/null |
|
)" |
|
] |
|
} |
|
|
|
repo_is_recent() { |
|
local root="$1" |
|
local ts |
|
|
|
# --all bypasses age only. |
|
[ "$ALL_PROJECT_ARTIFACTS" -eq 1 ] && |
|
return 1 |
|
|
|
[ -e "$root/.git" ] || |
|
return 1 |
|
|
|
command_exists git || |
|
return 1 |
|
|
|
ts="$( |
|
git -C "$root" \ |
|
log \ |
|
-1 \ |
|
--format=%ct \ |
|
2>/dev/null || true |
|
)" |
|
|
|
[ -n "$ts" ] || |
|
return 1 |
|
|
|
[ "$ts" -ge "$CUTOFF" ] |
|
} |
|
|
|
repo_has_active_process() { |
|
local root="$1" |
|
|
|
# Heuristic safety check: |
|
# if an active process command references the repository |
|
# path, leave its project artifacts alone. |
|
|
|
ps ax -o command= 2>/dev/null | |
|
grep -F -- "$root" | |
|
grep -v -F -- "great-delete.sh" \ |
|
>/dev/null 2>&1 |
|
} |
|
|
|
category_for_path() { |
|
local path="$1" |
|
|
|
case "$path" in |
|
|
|
*/target) |
|
echo "RUST" |
|
;; |
|
|
|
*/node_modules) |
|
echo "NODE" |
|
;; |
|
|
|
*/.next|*/.nuxt|*/.turbo|*/.parcel-cache|*/.vite|*/.svelte-kit|*/coverage) |
|
echo "FRAMEWORK" |
|
;; |
|
|
|
*) |
|
echo "PROJECT" |
|
;; |
|
|
|
esac |
|
} |
|
|
|
add_category_kb() { |
|
local category="$1" |
|
local kb="$2" |
|
|
|
case "$category" in |
|
|
|
RUST) |
|
RUST_KB=$(( RUST_KB + kb )) |
|
;; |
|
|
|
NODE) |
|
NODE_KB=$(( NODE_KB + kb )) |
|
;; |
|
|
|
FRAMEWORK) |
|
FRAMEWORK_KB=$(( FRAMEWORK_KB + kb )) |
|
;; |
|
|
|
TOOLCACHE) |
|
TOOLCACHE_KB=$(( TOOLCACHE_KB + kb )) |
|
;; |
|
|
|
APPCACHE) |
|
APP_CACHE_KB=$(( APP_CACHE_KB + kb )) |
|
;; |
|
|
|
esac |
|
} |
|
|
|
# ============================================================ |
|
# PROJECT ARTIFACT REPORT / DELETE |
|
# ============================================================ |
|
|
|
report_project_artifact() { |
|
local path="$1" |
|
local root |
|
local category |
|
local kb |
|
local reason |
|
|
|
path="$(canonical_path "$path")" |
|
|
|
root="$(find_project_root "$path")" |
|
root="$(canonical_path "$root")" |
|
|
|
category="$(category_for_path "$path")" |
|
|
|
kb="$(dir_kb "$path")" |
|
|
|
[ -n "$kb" ] || |
|
kb=0 |
|
|
|
TOTAL_FOUND_KB=$(( TOTAL_FOUND_KB + kb )) |
|
|
|
add_category_kb "$category" "$kb" |
|
|
|
reason="" |
|
|
|
if is_protected "$path" || |
|
is_protected "$root"; then |
|
|
|
reason="PROTECTED" |
|
|
|
elif repo_has_uncommitted_work "$root"; then |
|
|
|
reason="DIRTY GIT" |
|
|
|
elif repo_has_active_process "$root"; then |
|
|
|
reason="ACTIVE" |
|
|
|
elif repo_is_recent "$root"; then |
|
|
|
reason="RECENT" |
|
|
|
fi |
|
|
|
if [ -n "$reason" ]; then |
|
|
|
TOTAL_SKIPPED_KB=$(( TOTAL_SKIPPED_KB + kb )) |
|
|
|
printf ' %-10s %-10s %10s %s\n' \ |
|
"$category" \ |
|
"$reason" \ |
|
"$(human_kb "$kb")" \ |
|
"$path" |
|
|
|
return 0 |
|
|
|
fi |
|
|
|
printf ' %-10s %-10s %10s %s\n' \ |
|
"$category" \ |
|
"$MODE" \ |
|
"$(human_kb "$kb")" \ |
|
"$path" |
|
|
|
if [ "$APPLY" -eq 1 ]; then |
|
|
|
rm -rf -- "$path" |
|
|
|
if [ ! -e "$path" ]; then |
|
TOTAL_DELETED_KB=$(( TOTAL_DELETED_KB + kb )) |
|
fi |
|
|
|
fi |
|
} |
|
|
|
# ============================================================ |
|
# CACHE REPORT / DELETE |
|
# ============================================================ |
|
|
|
report_cache_path() { |
|
local label="$1" |
|
local path="$2" |
|
local kb |
|
|
|
[ -e "$path" ] || |
|
return 0 |
|
|
|
kb="$(dir_kb "$path")" |
|
|
|
[ -n "$kb" ] || |
|
kb=0 |
|
|
|
TOTAL_FOUND_KB=$(( TOTAL_FOUND_KB + kb )) |
|
TOOLCACHE_KB=$(( TOOLCACHE_KB + kb )) |
|
|
|
printf ' %-12s %-10s %10s %s\n' \ |
|
"$label" \ |
|
"$MODE" \ |
|
"$(human_kb "$kb")" \ |
|
"$path" |
|
|
|
if [ "$APPLY" -eq 1 ]; then |
|
|
|
rm -rf -- "$path" |
|
|
|
if [ ! -e "$path" ]; then |
|
TOTAL_DELETED_KB=$(( TOTAL_DELETED_KB + kb )) |
|
fi |
|
|
|
fi |
|
} |
|
|
|
run_cleanup() { |
|
local label="$1" |
|
|
|
shift |
|
|
|
printf '\n[%s]\n' "$label" |
|
|
|
if [ "$APPLY" -eq 0 ]; then |
|
|
|
printf ' DRY RUN command:' |
|
printf ' %q' "$@" |
|
printf '\n' |
|
|
|
return 0 |
|
|
|
fi |
|
|
|
"$@" || |
|
printf \ |
|
' warning: cleanup command exited non-zero\n' \ |
|
>&2 |
|
} |
|
|
|
# ============================================================ |
|
# HEADER |
|
# ============================================================ |
|
|
|
print_header() { |
|
|
|
echo "============================================================" |
|
echo " GREAT DELETE" |
|
echo " Mode: $MODE" |
|
|
|
if [ "$ALL_PROJECT_ARTIFACTS" -eq 1 ]; then |
|
|
|
echo " Project cutoff: ALL ages (safeguards still active)" |
|
|
|
else |
|
|
|
echo " Project cutoff: older than $STALE_DAYS days" |
|
|
|
fi |
|
|
|
echo " Aggressive caches: $AGGRESSIVE" |
|
echo " Docker prune: $DOCKER" |
|
echo " User caches: $CLEAR_USER_CACHES" |
|
echo " Home: $HOME" |
|
|
|
if [ -n "$KUJO_BIN" ]; then |
|
echo " Kujo binary: $KUJO_BIN" |
|
fi |
|
|
|
echo "============================================================" |
|
|
|
if [ "${#PROTECTED_PATHS[@]}" -gt 0 ]; then |
|
|
|
echo |
|
echo "Protected paths:" |
|
|
|
local p |
|
|
|
for p in "${PROTECTED_PATHS[@]}"; do |
|
echo " $p" |
|
done |
|
|
|
else |
|
|
|
echo |
|
echo "Protected paths: none" |
|
|
|
fi |
|
|
|
echo |
|
} |
|
|
|
print_header |
|
|
|
if [ "$APPLY" -eq 0 ]; then |
|
|
|
echo "Nothing will be deleted in this run." |
|
echo |
|
|
|
fi |
|
|
|
# ============================================================ |
|
# 1. PROJECT BUILD / DEPENDENCY ARTIFACTS |
|
# ============================================================ |
|
|
|
echo "[Project artifacts]" |
|
|
|
printf ' %-10s %-10s %10s %s\n' \ |
|
"CATEGORY" \ |
|
"ACTION" \ |
|
"SIZE" \ |
|
"PATH" |
|
|
|
while IFS= read -r -d '' path; do |
|
|
|
report_project_artifact "$path" |
|
|
|
done < <( |
|
|
|
find "$HOME" \ |
|
\( \ |
|
-path "$HOME/Library" -o \ |
|
-path "$HOME/.Trash" -o \ |
|
-path "$HOME/.cargo" -o \ |
|
-path "$HOME/.rustup" -o \ |
|
-path "$HOME/.npm" -o \ |
|
-path "$HOME/.pnpm-store" -o \ |
|
-path "$HOME/.cache" -o \ |
|
-path "$HOME/.gradle" -o \ |
|
-path "$HOME/.m2" -o \ |
|
-path "$HOME/.hermes" -o \ |
|
-path "$HOME/.pi" -o \ |
|
-path "$HOME/.pi-lens" -o \ |
|
-path "$HOME/.codex" -o \ |
|
-path "$HOME/.vscode" -o \ |
|
-path "$HOME/.nvm" \ |
|
\) \ |
|
-prune -o \ |
|
-type d \ |
|
\( \ |
|
-name node_modules -o \ |
|
-name target -o \ |
|
-name .next -o \ |
|
-name .nuxt -o \ |
|
-name .turbo -o \ |
|
-name .parcel-cache -o \ |
|
-name .vite -o \ |
|
-name .svelte-kit -o \ |
|
-name coverage \ |
|
\) \ |
|
-prune \ |
|
-print0 \ |
|
2>/dev/null |
|
|
|
) |
|
|
|
# ============================================================ |
|
# 2. KNOWN REPRODUCIBLE TOOL CACHES |
|
# ============================================================ |
|
|
|
echo |
|
echo "[Known reproducible caches]" |
|
|
|
printf ' %-12s %-10s %10s %s\n' \ |
|
"CATEGORY" \ |
|
"ACTION" \ |
|
"SIZE" \ |
|
"PATH" |
|
|
|
report_cache_path \ |
|
"TSCACHE" \ |
|
"$HOME/Library/Caches/typescript" |
|
|
|
report_cache_path \ |
|
"NPXTEMP" \ |
|
"$HOME/.npm/_npx" |
|
|
|
report_cache_path \ |
|
"COREPACK" \ |
|
"$HOME/.cache/node/corepack" |
|
|
|
report_cache_path \ |
|
"CODEXTMP" \ |
|
"$HOME/.codex/.tmp" |
|
|
|
report_cache_path \ |
|
"CODEXCACHE" \ |
|
"$HOME/.codex/plugins/cache" |
|
|
|
report_cache_path \ |
|
"CODEXRUNTIME" \ |
|
"$HOME/.cache/codex-runtimes" |
|
|
|
report_cache_path \ |
|
"PLAYWRIGHT" \ |
|
"$HOME/Library/Caches/ms-playwright" |
|
|
|
report_cache_path \ |
|
"PLAYWRIGHT" \ |
|
"$HOME/.cache/ms-playwright" |
|
|
|
report_cache_path \ |
|
"XCODE" \ |
|
"$HOME/Library/Developer/Xcode/DerivedData" |
|
|
|
if [ "$AGGRESSIVE" -eq 1 ]; then |
|
|
|
report_cache_path \ |
|
"CARGO" \ |
|
"$HOME/.cargo/registry/cache" |
|
|
|
report_cache_path \ |
|
"CARGO" \ |
|
"$HOME/.cargo/registry/src" |
|
|
|
report_cache_path \ |
|
"CARGO" \ |
|
"$HOME/.cargo/git/checkouts" |
|
|
|
report_cache_path \ |
|
"CARGO" \ |
|
"$HOME/.cargo/git/db" |
|
|
|
report_cache_path \ |
|
"RUSTUP" \ |
|
"$HOME/.rustup/downloads" |
|
|
|
report_cache_path \ |
|
"RUSTUP" \ |
|
"$HOME/.rustup/tmp" |
|
|
|
report_cache_path \ |
|
"GRADLE" \ |
|
"$HOME/.gradle/caches" |
|
|
|
report_cache_path \ |
|
"GRADLE" \ |
|
"$HOME/.gradle/daemon" |
|
|
|
fi |
|
|
|
# ============================================================ |
|
# 3. PACKAGE MANAGER CLEANUPS |
|
# ============================================================ |
|
|
|
if command_exists npm; then |
|
|
|
run_cleanup \ |
|
"npm cache" \ |
|
npm cache clean --force |
|
|
|
fi |
|
|
|
if command_exists pnpm; then |
|
|
|
run_cleanup \ |
|
"pnpm store" \ |
|
pnpm store prune |
|
|
|
fi |
|
|
|
if command_exists yarn; then |
|
|
|
run_cleanup \ |
|
"Yarn cache" \ |
|
yarn cache clean |
|
|
|
fi |
|
|
|
if command_exists bun; then |
|
|
|
run_cleanup \ |
|
"Bun cache" \ |
|
bun pm cache rm |
|
|
|
fi |
|
|
|
if command_exists python3; then |
|
|
|
run_cleanup \ |
|
"pip cache" \ |
|
python3 -m pip cache purge |
|
|
|
fi |
|
|
|
if command_exists uv; then |
|
|
|
run_cleanup \ |
|
"uv cache" \ |
|
uv cache clean |
|
|
|
fi |
|
|
|
if command_exists go; then |
|
|
|
run_cleanup \ |
|
"Go build/test cache" \ |
|
go clean -cache -testcache |
|
|
|
if [ "$AGGRESSIVE" -eq 1 ]; then |
|
|
|
run_cleanup \ |
|
"Go module cache" \ |
|
go clean -modcache |
|
|
|
fi |
|
|
|
fi |
|
|
|
if command_exists composer; then |
|
|
|
run_cleanup \ |
|
"Composer cache" \ |
|
composer clear-cache |
|
|
|
fi |
|
|
|
if command_exists brew; then |
|
|
|
run_cleanup \ |
|
"Homebrew cleanup" \ |
|
brew cleanup -s |
|
|
|
fi |
|
|
|
if command_exists xcrun; then |
|
|
|
run_cleanup \ |
|
"Unavailable iOS simulators" \ |
|
xcrun simctl delete unavailable |
|
|
|
fi |
|
|
|
# ============================================================ |
|
# 4. OPTIONAL USER APPLICATION CACHES |
|
# ============================================================ |
|
|
|
if [ "$CLEAR_USER_CACHES" -eq 1 ] && |
|
[ -d "$HOME/Library/Caches" ]; then |
|
|
|
echo |
|
echo "[User application caches]" |
|
|
|
cache_kb="$(dir_kb "$HOME/Library/Caches")" |
|
|
|
[ -n "$cache_kb" ] || |
|
cache_kb=0 |
|
|
|
APP_CACHE_KB=$(( APP_CACHE_KB + cache_kb )) |
|
|
|
echo \ |
|
" Current ~/Library/Caches size: $(human_kb "$cache_kb")" |
|
|
|
echo \ |
|
" NOTE: applications will recreate these caches." |
|
|
|
if [ "$APPLY" -eq 1 ]; then |
|
|
|
find "$HOME/Library/Caches" \ |
|
-mindepth 1 \ |
|
-maxdepth 1 \ |
|
-exec rm -rf -- {} + \ |
|
2>/dev/null || true |
|
|
|
else |
|
|
|
echo \ |
|
" DRY RUN: would clear contents of ~/Library/Caches" |
|
|
|
fi |
|
|
|
fi |
|
|
|
# ============================================================ |
|
# 5. DOCKER |
|
# ============================================================ |
|
|
|
if [ "$DOCKER" -eq 1 ] && |
|
command_exists docker; then |
|
|
|
echo |
|
echo "[Docker before prune]" |
|
|
|
docker system df 2>/dev/null || true |
|
|
|
run_cleanup \ |
|
"Docker unused data" \ |
|
docker system prune -af |
|
|
|
echo \ |
|
" Docker volumes were NOT touched." |
|
|
|
echo \ |
|
" Inspect them separately with: docker system df -v" |
|
|
|
else |
|
|
|
echo |
|
echo "[Docker]" |
|
echo " skipped (use --docker)" |
|
|
|
fi |
|
|
|
# ============================================================ |
|
# 6. REPORT-ONLY AREAS |
|
# ============================================================ |
|
|
|
echo |
|
echo "[Report-only: intentionally NOT auto-deleted]" |
|
|
|
# Rust toolchains |
|
|
|
if [ -d "$HOME/.rustup/toolchains" ]; then |
|
|
|
echo \ |
|
" Rust toolchains: $(human_kb "$(dir_kb "$HOME/.rustup/toolchains")")" |
|
|
|
if command_exists rustup; then |
|
|
|
rustup toolchain list 2>/dev/null | |
|
sed 's/^/ /' || true |
|
|
|
fi |
|
|
|
fi |
|
|
|
# Xcode archives |
|
|
|
if [ -d "$HOME/Library/Developer/Xcode/Archives" ]; then |
|
|
|
echo \ |
|
" Xcode Archives: $(human_kb "$(dir_kb "$HOME/Library/Developer/Xcode/Archives")")" |
|
|
|
fi |
|
|
|
# Maven |
|
|
|
if [ -d "$HOME/.m2/repository" ]; then |
|
|
|
echo \ |
|
" Maven repository: $(human_kb "$(dir_kb "$HOME/.m2/repository")")" |
|
|
|
fi |
|
|
|
# Docker volumes |
|
|
|
if command_exists docker; then |
|
|
|
echo \ |
|
" Docker volumes/images detail: run 'docker system df -v'" |
|
|
|
fi |
|
|
|
# Managed runtime / application trees. |
|
# Report size, but do not blindly delete them. |
|
|
|
for managed in \ |
|
"$HOME/.hermes" \ |
|
"$HOME/.pi" \ |
|
"$HOME/.pi-lens" \ |
|
"$HOME/.nvm" \ |
|
"$HOME/.vscode" \ |
|
"$HOME/Library/Application Support/Local/addons" \ |
|
"$HOME/Library/Application Support/Code/agent-host" \ |
|
"$HOME/Library/Application Support/discord" |
|
do |
|
|
|
if [ -d "$managed" ]; then |
|
|
|
printf \ |
|
' Managed/runtime tree: %10s %s\n' \ |
|
"$(human_kb "$(dir_kb "$managed")")" \ |
|
"$managed" |
|
|
|
fi |
|
|
|
done |
|
|
|
# ============================================================ |
|
# 7. GIT WORKTREE AUDIT |
|
# ============================================================ |
|
|
|
echo |
|
echo "[Git worktree candidates]" |
|
|
|
echo \ |
|
" Build artifacts inside worktrees may be cleaned." |
|
|
|
echo \ |
|
" Worktrees themselves are NOT automatically removed." |
|
|
|
find "$HOME" \ |
|
\( \ |
|
-path "$HOME/Library" -o \ |
|
-path "$HOME/.Trash" -o \ |
|
-path "$HOME/.cache" \ |
|
\) \ |
|
-prune -o \ |
|
-type f \ |
|
-name .git \ |
|
-print \ |
|
2>/dev/null | |
|
while IFS= read -r gitfile; do |
|
|
|
if grep -q '/worktrees/' "$gitfile" 2>/dev/null; then |
|
|
|
worktree="$(dirname "$gitfile")" |
|
|
|
printf \ |
|
' %10s %s\n' \ |
|
"$(human_kb "$(dir_kb "$worktree")")" \ |
|
"$worktree" |
|
|
|
fi |
|
|
|
done |
|
|
|
# ============================================================ |
|
# 8. DISK MAP |
|
# ============================================================ |
|
|
|
echo |
|
echo "[Largest top-level home directories]" |
|
|
|
du -hd 1 "$HOME" 2>/dev/null | |
|
sort -h | |
|
tail -20 || true |
|
|
|
if [ -d "$HOME/Library" ]; then |
|
|
|
echo |
|
echo "[Largest ~/Library directories]" |
|
|
|
du -hd 1 "$HOME/Library" 2>/dev/null | |
|
sort -h | |
|
tail -20 || true |
|
|
|
fi |
|
|
|
# ============================================================ |
|
# 9. INDIVIDUAL HUGE FILES |
|
# ============================================================ |
|
|
|
echo |
|
echo "[Individual files larger than 1 GB — report only]" |
|
|
|
find "$HOME" \ |
|
-type f \ |
|
-size +1G \ |
|
-print \ |
|
2>/dev/null | |
|
while IFS= read -r bigfile; do |
|
|
|
size="$( |
|
du -h "$bigfile" 2>/dev/null | |
|
awk '{print $1}' |
|
)" |
|
|
|
printf \ |
|
' %10s %s\n' \ |
|
"${size:-?}" \ |
|
"$bigfile" |
|
|
|
done |
|
|
|
# ============================================================ |
|
# 10. SUMMARY |
|
# ============================================================ |
|
|
|
after_free_kb="$( |
|
df -Pk "$HOME" | |
|
awk 'NR==2 {print $4}' |
|
)" |
|
|
|
delta_kb=$(( after_free_kb - before_free_kb )) |
|
|
|
echo |
|
echo "============================================================" |
|
echo " GREAT DELETE SUMMARY" |
|
echo "------------------------------------------------------------" |
|
|
|
echo \ |
|
" Rust target artifacts found: $(human_kb "$RUST_KB")" |
|
|
|
echo \ |
|
" Node modules found: $(human_kb "$NODE_KB")" |
|
|
|
echo \ |
|
" Framework/build caches found: $(human_kb "$FRAMEWORK_KB")" |
|
|
|
echo \ |
|
" Known tool caches found: $(human_kb "$TOOLCACHE_KB")" |
|
|
|
if [ "$CLEAR_USER_CACHES" -eq 1 ]; then |
|
|
|
echo \ |
|
" User Library caches observed: $(human_kb "$APP_CACHE_KB")" |
|
|
|
fi |
|
|
|
echo "------------------------------------------------------------" |
|
|
|
echo \ |
|
" Project/tool artifacts observed: $(human_kb "$TOTAL_FOUND_KB")" |
|
|
|
echo \ |
|
" Protected/recent/active skipped: $(human_kb "$TOTAL_SKIPPED_KB")" |
|
|
|
if [ "$APPLY" -eq 1 ]; then |
|
|
|
echo \ |
|
" Tracked deletions completed: $(human_kb "$TOTAL_DELETED_KB")" |
|
|
|
if [ "$delta_kb" -gt 0 ]; then |
|
|
|
echo \ |
|
" Filesystem space reclaimed: $(human_kb "$delta_kb")" |
|
|
|
else |
|
|
|
echo \ |
|
" Filesystem free-space delta: $(human_kb "$delta_kb")" |
|
|
|
fi |
|
|
|
else |
|
|
|
echo \ |
|
" DRY RUN: nothing deleted." |
|
|
|
fi |
|
|
|
echo "============================================================" |