-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcleanup-disk
More file actions
executable file
·518 lines (463 loc) · 19.1 KB
/
cleanup-disk
File metadata and controls
executable file
·518 lines (463 loc) · 19.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
#!/bin/bash
# macOS Disk Cleanup Script
# Safely removes cache files, development artifacts, and temporary data
# Usage: cleanup-disk [OPTIONS]
# -d, --dry-run Show what would be deleted without deleting
# -a, --all Clean everything (default: interactive)
# -c, --common Clean common items (browser caches, package managers, logs)
# -h, --help Show this help message
# Removed set -e to handle errors gracefully
# set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Options
DRY_RUN=false
CLEAN_ALL=false
CLEAN_COMMON=false
# Statistics tracking
STATS_SUCCESS=0
STATS_SKIPPED=0
STATS_FAILED=0
STATS_EMPTY=0
FAILED_ITEMS=()
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-d|--dry-run)
DRY_RUN=true
shift
;;
-a|--all)
CLEAN_ALL=true
shift
;;
-c|--common)
CLEAN_COMMON=true
shift
;;
-h|--help)
grep "^#" "$0" | grep -v "^#!/" | sed 's/^# //'
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use -h or --help for usage information"
exit 1
;;
esac
done
# Disable Homebrew auto-update during cleanup
export HOMEBREW_NO_AUTO_UPDATE=1
# Helper function to check if a process is running
is_process_running() {
local process_name="$1"
pgrep -x "$process_name" > /dev/null 2>&1
}
# Helper function to check if a tool is installed
is_tool_installed() {
command -v "$1" &> /dev/null
}
# Helper function to show size with consistent formatting
show_size() {
local path="$1"
if [ -e "$path" ]; then
local size=$(du -sh "$path" 2>/dev/null | awk '{print $1}')
if [ -z "$size" ] || [ "$size" = "0B" ]; then
echo "(empty)"
else
echo "($size)"
fi
else
echo "(not found)"
fi
}
# Helper function to check if path is empty or effectively 0B
is_empty() {
local path="$1"
if [ ! -e "$path" ]; then
return 0 # Not found = empty
fi
# Get size in KB
local size_kb=$(du -sk "$path" 2>/dev/null | awk '{print $1}')
[ -z "$size_kb" ] || [ "$size_kb" -eq 0 ]
}
# Helper function to safely remove with enhanced error context
safe_remove() {
local path="$1"
local description="$2"
local context="${3:-}" # Optional context for why it might fail
if [ ! -e "$path" ]; then
echo -e "> ${YELLOW}⊘${NC} $description - Not found, skipping"
STATS_SKIPPED=$((STATS_SKIPPED + 1))
return
fi
# Skip if empty/0B
if is_empty "$path"; then
echo -e "> ${YELLOW}⊘${NC} $description (empty)"
STATS_EMPTY=$((STATS_EMPTY + 1))
return
fi
local size=$(show_size "$path")
if [ "$DRY_RUN" = true ]; then
echo -e "> ${BLUE}[DRY RUN]${NC} Would delete: $description $size"
STATS_SUCCESS=$((STATS_SUCCESS + 1))
else
echo -e "> ${GREEN}✓${NC} Removing: $description $size"
if rm -rf "$path" 2>/dev/null; then
STATS_SUCCESS=$((STATS_SUCCESS + 1))
else
if [ -n "$context" ]; then
echo -e "> ${RED}✗${NC} Error: Could not remove $description - $context"
FAILED_ITEMS+=("$description - $context")
else
echo -e "${RED}✗${NC} Error: Could not remove $description (permission denied or in use)"
FAILED_ITEMS+=("$description - permission denied or in use")
fi
STATS_FAILED=$((STATS_FAILED + 1))
fi
fi
}
# Helper function to ask for confirmation
confirm() {
local message="$1"
if [ "$CLEAN_ALL" = true ]; then
return 0
fi
if [ "$CLEAN_COMMON" = true ]; then
return 1 # Will be overridden for common items
fi
echo -ne "${YELLOW}?${NC} $message (y/N): "
read -n 1 -r response
echo "" # Move to new line after single character input
case "$response" in
[yY])
return 0
;;
*)
return 1
;;
esac
}
# Helper to auto-confirm for common items
confirm_common() {
local message="$1"
if [ "$CLEAN_COMMON" = true ]; then
return 0 # Auto-yes for common items
fi
confirm "$message"
}
echo -e "${BLUE}════════════════════════════════════════${NC}"
echo -e "${BLUE} macOS Disk Cleanup Tool${NC}"
echo -e "${BLUE}════════════════════════════════════════${NC}"
echo ""
if [ "$DRY_RUN" = true ]; then
echo -e "${YELLOW}Running in DRY RUN mode - nothing will be deleted${NC}"
echo ""
fi
if [ "$CLEAN_COMMON" = true ]; then
echo -e "${BLUE}Running with --common flag (cleaning typical items)${NC}"
echo ""
fi
# Show initial disk usage
echo -e "${BLUE}Current disk usage:${NC}"
df -h / | tail -1 | awk '{print "Used: " $3 " / Free: " $4 " (" $5 " full)"}'
echo ""
TOTAL_BEFORE=$(df / | tail -1 | awk '{print $3}')
# 1. Adobe Cache
if confirm "Clean Adobe Cache (Premiere/After Effects media cache)?"; then
safe_remove "$HOME/Library/Caches/Adobe" "Adobe Cache"
fi
# 2-4. Xcode-related items (group check)
if is_tool_installed "xcodebuild"; then
# 2. Xcode Derived Data
if confirm "Clean Xcode DerivedData (build artifacts)?"; then
safe_remove "$HOME/Library/Developer/Xcode/DerivedData" "Xcode DerivedData"
fi
# 3. iOS Device Support
if confirm "Clean iOS DeviceSupport (old iOS debugging symbols)?"; then
safe_remove "$HOME/Library/Developer/Xcode/iOS DeviceSupport" "iOS DeviceSupport"
fi
# 4. iOS Simulators
if confirm "Clean iOS Simulators (Xcode will recreate if needed)?"; then
if [ "$DRY_RUN" = false ]; then
echo -e "> ${GREEN}✓${NC} Removing unavailable iOS Simulators"
xcrun simctl delete unavailable 2>/dev/null || true
STATS_SUCCESS=$((STATS_SUCCESS + 1))
else
echo -e "> ${BLUE}[DRY RUN]${NC} Would remove unavailable iOS Simulators"
STATS_SUCCESS=$((STATS_SUCCESS + 1))
fi
fi
fi
# 5. Browser Caches (common item)
if confirm_common "Clean browser caches (Chrome, Firefox, Brave, Edge)?"; then
# De-duplicated: remove top-level Google caches (includes Chrome subfolder)
safe_remove "$HOME/Library/Caches/Google" "Google caches (incl. Chrome)"
safe_remove "$HOME/Library/Caches/Firefox" "Firefox Cache"
safe_remove "$HOME/Library/Caches/BraveSoftware" "Brave Cache"
safe_remove "$HOME/Library/Caches/Microsoft Edge" "Microsoft Edge Cache"
fi
# 6. Node package manager caches (common item)
if is_tool_installed "node" || is_tool_installed "npm" || is_tool_installed "pnpm" || is_tool_installed "yarn"; then
if confirm_common "Clean Node package manager caches (npm, pnpm, yarn)?"; then
if command -v pnpm &> /dev/null; then
if [ "$DRY_RUN" = false ]; then
echo -e "> ${GREEN}✓${NC} Running: pnpm store prune"
pnpm store prune 2>/dev/null | tail -1 || true
STATS_SUCCESS=$((STATS_SUCCESS + 1))
else
echo -e "> ${BLUE}[DRY RUN]${NC} Would run: pnpm store prune"
STATS_SUCCESS=$((STATS_SUCCESS + 1))
fi
fi
if command -v npm &> /dev/null; then
if [ "$DRY_RUN" = false ]; then
echo -e "> ${GREEN}✓${NC} Running: npm cache clean --force"
npm cache clean --force 2>/dev/null || true
STATS_SUCCESS=$((STATS_SUCCESS + 1))
else
echo -e "> ${BLUE}[DRY RUN]${NC} Would run: npm cache clean --force"
STATS_SUCCESS=$((STATS_SUCCESS + 1))
fi
fi
if command -v yarn &> /dev/null; then
if [ "$DRY_RUN" = false ]; then
echo -e "> ${GREEN}✓${NC} Running: yarn cache clean"
yarn cache clean --silent 2>&1 | tail -1 || true
STATS_SUCCESS=$((STATS_SUCCESS + 1))
else
echo -e "> ${BLUE}[DRY RUN]${NC} Would run: yarn cache clean"
STATS_SUCCESS=$((STATS_SUCCESS + 1))
fi
fi
fi
fi
# 7. Python pip cache
if is_tool_installed "pip" || is_tool_installed "pip3"; then
if confirm_common "Clean Python pip cache?"; then
if command -v pip &> /dev/null; then
if [ "$DRY_RUN" = false ]; then
echo -e "> ${GREEN}✓${NC} Running: pip cache purge"
pip cache purge 2>/dev/null || true
STATS_SUCCESS=$((STATS_SUCCESS + 1))
else
echo -e "> ${BLUE}[DRY RUN]${NC} Would run: pip cache purge"
STATS_SUCCESS=$((STATS_SUCCESS + 1))
fi
fi
safe_remove "$HOME/Library/Caches/pip" "pip cache directory"
fi
fi
# 8. Homebrew cache (common item)
if is_tool_installed "brew"; then
if confirm_common "Clean Homebrew cache and old versions?"; then
if [ "$DRY_RUN" = false ]; then
echo -e "> ${GREEN}✓${NC} Running: brew cleanup -s"
HOMEBREW_NO_AUTO_UPDATE=1 brew cleanup -s 2>&1 | grep -E "(Removing|freed)" || true
STATS_SUCCESS=$((STATS_SUCCESS + 1))
else
echo -e "> ${BLUE}[DRY RUN]${NC} Would run: brew cleanup -s"
STATS_SUCCESS=$((STATS_SUCCESS + 1))
fi
safe_remove "$HOME/Library/Caches/Homebrew" "Homebrew cache"
fi
fi
# 9. Application caches (common item)
if confirm_common "Clean application caches (Spotify, Playwright, etc.)?"; then
safe_remove "$HOME/Library/Caches/com.spotify.client" "Spotify Cache"
safe_remove "$HOME/Library/Caches/ms-playwright" "Playwright Browsers"
safe_remove "$HOME/Library/Caches/Thunderbird" "Thunderbird Cache"
safe_remove "$HOME/Library/Caches/composer" "Composer Cache"
safe_remove "$HOME/Library/Caches/typescript" "TypeScript Cache"
fi
# 10. System log files (common item)
if confirm_common "Clean old log files?"; then
# Remove old log files (older than 30 days) instead of the entire directory
if [ -d "$HOME/Library/Logs" ]; then
if [ "$DRY_RUN" = false ]; then
log_count=$(find "$HOME/Library/Logs" -type f -name "*.log" -mtime +30 2>/dev/null | wc -l | tr -d ' ')
if [ "$log_count" -gt 0 ]; then
echo -e "> ${GREEN}✓${NC} Removing old log files (${log_count} files older than 30 days)"
find "$HOME/Library/Logs" -type f -name "*.log" -mtime +30 -delete 2>/dev/null || true
STATS_SUCCESS=$((STATS_SUCCESS + 1))
else
echo -e "> ${YELLOW}⊘${NC} No old log files found (older than 30 days)"
STATS_EMPTY=$((STATS_EMPTY + 1))
fi
else
log_count=$(find "$HOME/Library/Logs" -type f -name "*.log" -mtime +30 2>/dev/null | wc -l | tr -d ' ')
echo -e "> ${BLUE}[DRY RUN]${NC} Would remove old log files (${log_count} files older than 30 days)"
STATS_SUCCESS=$((STATS_SUCCESS + 1))
fi
else
echo -e "> ${YELLOW}⊘${NC} Logs directory - Not found, skipping"
STATS_SKIPPED=$((STATS_SKIPPED + 1))
fi
fi
# 11. Additional Node.js caches
if is_tool_installed "node" || is_tool_installed "npm"; then
if confirm "Clean additional Node.js caches (npm cache directory)?"; then
safe_remove "$HOME/.npm" "npm cache directory"
safe_remove "$HOME/.node-gyp" "node-gyp cache"
# Yarn cache lives under Library/Caches on macOS
safe_remove "$HOME/Library/Caches/Yarn" "Yarn cache directory"
fi
fi
# 12. Java build tool caches
if is_tool_installed "java" || is_tool_installed "mvn" || is_tool_installed "gradle"; then
if confirm "Clean Java build tool caches (Maven, Gradle)?"; then
safe_remove "$HOME/.m2/repository" "Maven repository cache"
safe_remove "$HOME/.gradle/caches" "Gradle cache"
safe_remove "$HOME/.ivy2/cache" "Ivy cache"
fi
fi
# 13. Rust toolchain caches
if is_tool_installed "cargo" || is_tool_installed "rustc"; then
if confirm "Clean Rust toolchain caches (cargo, rustup)?"; then
if command -v cargo &> /dev/null; then
if [ "$DRY_RUN" = false ]; then
echo -e "> ${GREEN}✓${NC} Running: cargo cache --autoclean"
if ! cargo cache --autoclean 2>/dev/null; then
echo -e "> ${YELLOW} Note: For better Rust cache cleanup, install: cargo install cargo-cache${NC}"
fi
STATS_SUCCESS=$((STATS_SUCCESS + 1))
else
echo -e "> ${BLUE}[DRY RUN]${NC} Would run: cargo cache --autoclean"
STATS_SUCCESS=$((STATS_SUCCESS + 1))
fi
fi
safe_remove "$HOME/.cargo/registry/cache" "Cargo registry cache"
safe_remove "$HOME/.cargo/git/db" "Cargo git database"
fi
fi
# 14. Advanced browser caches
if confirm "Clean advanced browser caches (Service Workers, GPU Cache)?"; then
# Handle all Chrome profiles instead of only Default
if [ -d "$HOME/Library/Application Support/Google/Chrome" ]; then
for profile in "$HOME/Library/Application Support/Google/Chrome"/*; do
[ -d "$profile" ] || continue
safe_remove "$profile/Service Worker" "Chrome Service Workers ($(basename "$profile"))"
safe_remove "$profile/GPUCache" "Chrome GPU Cache ($(basename "$profile"))"
done
fi
# Check if Safari is running for better error messages
if is_process_running "Safari"; then
echo -e "> ${YELLOW}Note: Safari is currently running. Close Safari for complete cache cleanup.${NC}"
safe_remove "$HOME/Library/Safari/LocalStorage" "Safari Local Storage" "Close Safari first"
safe_remove "$HOME/Library/Safari/Databases" "Safari Databases" "Close Safari first"
safe_remove "$HOME/Library/Caches/com.apple.Safari" "Safari Cache" "Close Safari first"
safe_remove "$HOME/Library/WebKit/NetworkCache" "Safari WebKit NetworkCache" "Close Safari first"
safe_remove "$HOME/Library/WebKit/WebsiteData/LocalStorage" "Safari WebKit LocalStorage" "Close Safari first"
else
safe_remove "$HOME/Library/Safari/LocalStorage" "Safari Local Storage"
safe_remove "$HOME/Library/Safari/Databases" "Safari Databases"
safe_remove "$HOME/Library/Caches/com.apple.Safari" "Safari Cache"
safe_remove "$HOME/Library/WebKit/NetworkCache" "Safari WebKit NetworkCache"
safe_remove "$HOME/Library/WebKit/WebsiteData/LocalStorage" "Safari WebKit LocalStorage"
fi
fi
# 15. Saved Application States
if confirm "Clean saved application states (safe, apps will reopen in default positions)?"; then
safe_remove "$HOME/Library/Saved Application State" "Saved Application States" "Some apps may be running"
fi
# 16. iOS/iPad backups
if confirm "Clean old iPhone/iPad backups (WARNING: Only if you have iCloud or recent backups)?"; then
safe_remove "$HOME/Library/Application Support/MobileSync/Backup" "iOS Device Backups" "Backups may be protected by system"
fi
# 17. Trash (System, Google Drive, Dropbox, iCloud)
if confirm "Empty all Trash folders (System, Google Drive, Dropbox, iCloud)?"; then
# System Trash - Use Finder AppleScript to handle permissions gracefully
if [ "$DRY_RUN" = false ]; then
trash_size=$(show_size "$HOME/.Trash")
echo -e "> ${GREEN}✓${NC} Emptying: System Trash $trash_size"
if osascript -e 'tell application "Finder" to empty trash' >/dev/null 2>&1; then
STATS_SUCCESS=$((STATS_SUCCESS + 1))
else
echo -e "> ${RED}✗${NC} Error: Could not empty System Trash via Finder"
FAILED_ITEMS+=("System Trash - Finder AppleScript failed or not permitted")
STATS_FAILED=$((STATS_FAILED + 1))
fi
else
trash_size=$(show_size "$HOME/.Trash")
echo -e "> ${BLUE}[DRY RUN]${NC} Would ask Finder to empty: System Trash $trash_size"
STATS_SUCCESS=$((STATS_SUCCESS + 1))
fi
# Google Drive Trash (all accounts) with safer checks
if [ -d "$HOME/Library/CloudStorage" ]; then
for gdrive_dir in "$HOME/Library/CloudStorage"/GoogleDrive-*; do
[ -d "$gdrive_dir" ] || continue
if [ -d "$gdrive_dir/.Trash" ]; then
account_name=$(basename "$gdrive_dir" | sed 's/GoogleDrive-//' )
safe_remove "$gdrive_dir/.Trash" "Google Drive Trash ($account_name)"
else
echo -e "> ${YELLOW}⊘${NC} Google Drive Trash ($(basename "$gdrive_dir")) - Not found, skipping"
STATS_SKIPPED=$((STATS_SKIPPED + 1))
fi
done
fi
# Dropbox Trash
if [ -d "$HOME/Library/CloudStorage/Dropbox/.Trash" ]; then
safe_remove "$HOME/Library/CloudStorage/Dropbox/.Trash" "Dropbox Trash"
fi
# iCloud Trash
if [ -d "$HOME/Library/Mobile Documents/.Trash" ]; then
safe_remove "$HOME/Library/Mobile Documents/.Trash" "iCloud Trash"
fi
fi
echo ""
echo -e "${BLUE}════════════════════════════════════════${NC}"
if [ "$DRY_RUN" = false ]; then
TOTAL_AFTER=$(df / | tail -1 | awk '{print $3}')
FREED=$((TOTAL_BEFORE - TOTAL_AFTER))
# Format space freed with appropriate units
if [ $FREED -lt 1024 ]; then
FREED_DISPLAY="${FREED}KB"
elif [ $FREED -lt 1048576 ]; then
FREED_MB=$(echo "scale=1; $FREED / 1024" | bc)
FREED_DISPLAY="${FREED_MB}MB"
else
FREED_GB=$(echo "scale=2; $FREED / 1024 / 1024" | bc)
FREED_DISPLAY="${FREED_GB}GB"
fi
echo -e "${GREEN}Cleanup complete!${NC}"
echo ""
echo -e "${BLUE}Final disk usage:${NC}"
df -h / | tail -1 | awk '{print "Used: " $3 " / Free: " $4 " (" $5 " full)"}'
echo ""
echo -e "${GREEN}Space freed: ~${FREED_DISPLAY}${NC}"
echo ""
# Summary report
echo -e "${BLUE}Summary:${NC}"
echo -e "> ${GREEN}✓${NC} Successfully cleaned: $STATS_SUCCESS items"
if [ $STATS_EMPTY -gt 0 ]; then
echo -e "> ${YELLOW}⊘${NC} Skipped (empty): $STATS_EMPTY items"
fi
if [ $STATS_SKIPPED -gt 0 ]; then
echo -e "> ${YELLOW}⊘${NC} Skipped (not found): $STATS_SKIPPED items"
fi
if [ $STATS_FAILED -gt 0 ]; then
echo -e "> ${RED}✗${NC} Failed: $STATS_FAILED items"
echo ""
echo -e "${YELLOW}Failed items (need manual attention):${NC}"
for item in "${FAILED_ITEMS[@]}"; do
echo -e " - $item"
done
fi
else
echo -e "${YELLOW}Dry run complete. No files were deleted.${NC}"
echo -e "Run without --dry-run to actually clean up."
echo ""
echo -e "${BLUE}Summary:${NC}"
echo -e "> ${BLUE}Would clean: $STATS_SUCCESS items${NC}"
if [ $STATS_EMPTY -gt 0 ]; then
echo -e "> ${YELLOW}Would skip (empty): $STATS_EMPTY items${NC}"
fi
if [ $STATS_SKIPPED -gt 0 ]; then
echo -e "> ${YELLOW}Would skip (not found): $STATS_SKIPPED items${NC}"
fi
fi
echo -e "${BLUE}════════════════════════════════════════${NC}"