#!/bin/bash # cleanup.sh # Script dọn dẹp các ứng dụng nặng và các bản cũ trên macOS # Sử dụng: bash cleanup.sh # ----------------------------- # Function: cleanup chung cho thư mục chứa version # $1 = label (vd: "Edge", "Edge Updater") # $2 = đường dẫn thư mục # ----------------------------- cleanup_versions() { local label="$1" local target_dir="$2" echo "--- [$label] ---" echo " $target_dir" if [ ! -d "$target_dir" ]; then echo " ⚠ Thư mục không tồn tại." return fi pushd "$target_dir" > /dev/null || return # Lấy các thư mục version dạng x.y.z.w và sort theo version số. local raw_dirs=() local d shopt -s nullglob for d in [0-9]*.[0-9]*.[0-9]*.[0-9]*; do [[ -d "$d" ]] && raw_dirs+=("$d") done shopt -u nullglob if [ ${#raw_dirs[@]} -eq 0 ]; then echo " ⚠ Thư mục rỗng, không có phiên bản nào." popd > /dev/null return fi local dirs=() while IFS= read -r d; do [[ -n "$d" ]] && dirs+=("$d") done < <( printf '%s\n' "${raw_dirs[@]}" | awk -F'.' '{ printf "%05d.%05d.%05d.%05d\t%s\n", $1, $2, $3, $4, $0 }' | sort | cut -f2- ) local last_index=$(( ${#dirs[@]} - 1 )) local keep="${dirs[$last_index]}" # Helper: lấy size thư mục dir_size() { du -sh "$1" 2>/dev/null | cut -f1 | xargs; } if [ ${#dirs[@]} -le 1 ]; then echo " ✓ Phiên bản hiện tại: $keep ($(dir_size "$keep")) — không có bản cũ." popd > /dev/null return fi echo " ✓ Giữ lại: $keep ($(dir_size "$keep"))" echo " ✗ Sẽ xóa:" local i for ((i = 0; i < ${#dirs[@]} - 1; i++)); do echo " - ${dirs[$i]} ($(dir_size "${dirs[$i]}"))" done local confirm read -r -p " Xác nhận xóa? (Y/n): " confirm if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then echo " Đã hủy." popd > /dev/null return fi for ((i = 0; i < ${#dirs[@]} - 1; i++)); do echo " Đã xóa: ${dirs[$i]}" rm -rf -- "${dirs[$i]}" done popd > /dev/null } # ----------------------------- # Function: Cleanup Go mod cache # ----------------------------- cleanup_go() { echo "--- [Go Mod Cache] ---" if ! command -v go &> /dev/null; then echo " ⚠ go command not found." return fi local GOMODCACHE GOMODCACHE=$(go env GOMODCACHE) if [ -d "$GOMODCACHE" ]; then echo " $GOMODCACHE" local size size=$(du -sh "$GOMODCACHE" 2>/dev/null | cut -f1 | xargs) echo " ✗ Sẽ xóa bộ nhớ cache: $size" local confirm read -r -p " Xác nhận xóa? (Y/n): " confirm if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then echo " Đã hủy." return fi go clean -modcache echo " Đã xong." else echo " ✓ Bộ nhớ cache rỗng hoặc không tồn tại." fi } # ----------------------------- # Function: Cleanup NPM cache # ----------------------------- cleanup_npm() { echo "--- [NPM Cache] ---" if ! command -v npm &> /dev/null; then echo " ⚠ npm command not found." return fi local NPM_CACHE NPM_CACHE=$(npm config get cache) if [ -d "$NPM_CACHE" ]; then echo " $NPM_CACHE" local size # Lưu ý: npm cache size thực tế thường lớn hơn kết quả du vì cấu trúc thư mục phức tạp size=$(du -sh "$NPM_CACHE" 2>/dev/null | cut -f1 | xargs) echo " ✗ Sẽ xóa bộ nhớ cache: $size" local confirm read -r -p " Xác nhận xóa? (Y/n): " confirm if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then echo " Đã hủy." return fi npm cache clean --force echo " Đã xong." else echo " ✓ Bộ nhớ cache rỗng hoặc không tồn tại." fi } # ----------------------------- # Function: Cleanup old VS Code extensions # Giữ lại phiên bản mới nhất của mỗi extension # ----------------------------- cleanup_vscode_extensions() { echo "--- [VS Code Extensions] ---" local EXT_DIR="$HOME/.vscode/extensions" echo " $EXT_DIR" if [ ! -d "$EXT_DIR" ]; then echo " ⚠ Thư mục không tồn tại." return fi # Build "ext_iddirname" pairs local entries=() local basename ext_id shopt -s nullglob for dir in "$EXT_DIR"/*/; do basename=$(command basename "$dir") [[ "$basename" == .* ]] && continue # Extract extension ID: everything before - ext_id=$(echo "$basename" | sed 's/-[0-9][0-9.]*.*$//') [ -z "$ext_id" ] && continue entries+=("${ext_id}"$'\t'"${basename}") done shopt -u nullglob if [ ${#entries[@]} -eq 0 ]; then echo " ⚠ Không có extension nào." return fi # Sort by ext_id (field 1), then by dirname (field 2) for version ordering local sorted sorted=$(printf '%s\n' "${entries[@]}" | sort -t$'\t' -k1,1 -k2,2) # Group by ext_id, find old versions local to_delete=() local to_keep=() local group=() local prev_id="" while IFS=$'\t' read -r ext_id dirname; do if [ -n "$prev_id" ] && [ "$ext_id" != "$prev_id" ]; then # Process previous group if [ ${#group[@]} -gt 1 ]; then local last=$(( ${#group[@]} - 1 )) to_keep+=("${group[$last]}") local g for ((g = 0; g < last; g++)); do to_delete+=("${group[$g]}") done fi group=() fi group+=("$dirname") prev_id="$ext_id" done <<< "$sorted" # Process last group if [ ${#group[@]} -gt 1 ]; then local last=$(( ${#group[@]} - 1 )) to_keep+=("${group[$last]}") local g for ((g = 0; g < last; g++)); do to_delete+=("${group[$g]}") done fi if [ ${#to_delete[@]} -eq 0 ]; then echo " ✓ Không có extension cũ để xóa." return fi echo " ✓ Giữ lại (mới nhất):" local k for k in "${to_keep[@]}"; do local size size=$(du -sh "$EXT_DIR/$k" 2>/dev/null | cut -f1 | xargs) echo " + $k ($size)" done echo " ✗ Sẽ xóa (cũ):" local d for d in "${to_delete[@]}"; do local size size=$(du -sh "$EXT_DIR/$d" 2>/dev/null | cut -f1 | xargs) echo " - $d ($size)" done local confirm read -r -p " Xác nhận xóa? (Y/n): " confirm if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then echo " Đã hủy." return fi for d in "${to_delete[@]}"; do echo " Đã xóa: $d" rm -rf -- "$EXT_DIR/$d" done } # ----------------------------- # Function: Cleanup old Antigravity browser recordings # ----------------------------- cleanup_agy_recordings() { local days="${1:-7}" local rec_dir="$HOME/.gemini/antigravity/browser_recordings" echo "--- [Antigravity Recordings] ---" echo " $rec_dir" if [ ! -d "$rec_dir" ]; then echo " ⚠ Thư mục không tồn tại." return fi local to_delete=() local total_size=0 local dir shopt -s nullglob for dir in "$rec_dir"/*/; do [ -d "$dir" ] || continue local dirname dirname=$(basename "$dir") local size mod_date size=$(du -sh "$dir" 2>/dev/null | cut -f1 | xargs) mod_date=$(stat -f "%Sm" -t "%Y-%m-%d" "$dir") if [ "$(find "$dir" -maxdepth 0 -mtime +"$days" 2>/dev/null)" ]; then echo " - $dirname ($size, $mod_date)" to_delete+=("$dir") total_size=$((total_size + $(du -sk "$dir" | cut -f1))) else echo " + $dirname ($size, $mod_date) [KEEP]" fi done shopt -u nullglob if [ ${#to_delete[@]} -eq 0 ]; then echo " ✓ Không có recording cũ (>${days} ngày) để xóa." return fi local total_human total_human=$(echo "scale=1; $total_size / 1024" | bc) echo " ✗ Sẽ xóa ${#to_delete[@]} recording(s), tổng: ${total_human} MiB" local confirm read -r -p " Xác nhận xóa? (Y/n): " confirm if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then echo " Đã hủy." return fi for dir in "${to_delete[@]}"; do echo " Đã xóa: $(basename "$dir")" rm -rf -- "$dir" done } # ----------------------------- # Function: Cleanup Homebrew # ----------------------------- cleanup_homebrew() { echo "--- [Homebrew] ---" if ! command -v brew &> /dev/null; then echo " ⚠ brew command not found." return fi echo " Đang cập nhật Homebrew..." brew update --quiet local outdated outdated=$(brew outdated 2>/dev/null) if [ -n "$outdated" ]; then echo " ✗ Các package cần upgrade:" echo "$outdated" | sed 's/^/ - /' local confirm read -r -p " Upgrade tất cả? (Y/n): " confirm if [[ -z "$confirm" || "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then brew upgrade --quiet echo " Đã upgrade xong." else echo " Bỏ qua upgrade." fi else echo " ✓ Tất cả package đã cập nhật." fi # Cleanup cache local cache_size cache_size=$(brew --cache 2>/dev/null) if [ -d "$cache_size" ]; then local size size=$(du -sh "$cache_size" 2>/dev/null | cut -f1 | xargs) echo " ✗ Cache: $size ($cache_size)" fi echo " Đang dọn dẹp cache và autoremove..." brew cleanup --prune=all -s 2>/dev/null brew autoremove 2>/dev/null echo " Đã xong." } # ----------------------------- # Function: Cleanup VS Code C++ Tools cache # ----------------------------- cleanup_vscode_cpptools() { local cache_dir="$HOME/Library/Caches/vscode-cpptools" echo "--- [VS Code C++ Tools Cache] ---" echo " $cache_dir" if [ ! -d "$cache_dir" ]; then echo " ⚠ Thư mục không tồn tại." return fi local total_size total_size=$(du -sh "$cache_dir" 2>/dev/null | cut -f1 | xargs) echo " Tổng dung lượng: $total_size" # ipch (IntelliSense cache) - thường chiếm nhiều nhất if [ -d "$cache_dir/ipch" ]; then local ipch_size ipch_size=$(du -sh "$cache_dir/ipch" 2>/dev/null | cut -f1 | xargs) echo " - ipch (IntelliSense cache): $ipch_size" fi # Các thư mục hash cache khác local hash_count=0 local hash_size=0 local d shopt -s nullglob for d in "$cache_dir"/*/; do local dirname dirname=$(basename "$d") [[ "$dirname" == "ipch" ]] && continue hash_count=$((hash_count + 1)) hash_size=$((hash_size + $(du -sk "$d" | cut -f1))) done shopt -u nullglob if [ $hash_count -gt 0 ]; then local hash_human hash_human=$(echo "scale=1; $hash_size / 1024" | bc) echo " - $hash_count thư mục cache khác: ${hash_human} MiB" fi echo " ✗ Sẽ xóa toàn bộ cache" local confirm read -r -p " Xác nhận xóa? (Y/n): " confirm if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then echo " Đã hủy." return fi rm -rf -- "$cache_dir"/* echo " Đã xóa toàn bộ cache. VS Code sẽ tạo lại khi cần." } # ----------------------------- # Function: Cleanup IDE logs (JetBrains, Google, etc.) # Giữ lại phiên bản mới nhất của mỗi IDE # $1 = label, $2 = đường dẫn thư mục logs # ----------------------------- cleanup_ide_logs() { local label="$1" local logs_dir="$2" echo "--- [$label Logs] ---" echo " $logs_dir" if [ ! -d "$logs_dir" ]; then echo " ⚠ Thư mục không tồn tại." return fi local to_delete=() local all_dirs=() shopt -s nullglob for d in "$logs_dir"/*/; do local dirname dirname=$(basename "$d") [[ "$dirname" == "Toolbox" || "$dirname" == ".DS_Store" ]] && continue all_dirs+=("$dirname") done shopt -u nullglob if [ ${#all_dirs[@]} -eq 0 ]; then echo " ✓ Không có log nào." return fi local sorted sorted=$(printf '%s\n' "${all_dirs[@]}" | sort) local prev_ide="" local group=() while IFS= read -r dirname; do [ -z "$dirname" ] && continue local ide_name ide_name=$(echo "$dirname" | sed 's/[0-9].*$//') [ -z "$ide_name" ] && continue if [ -n "$prev_ide" ] && [ "$ide_name" != "$prev_ide" ]; then if [ ${#group[@]} -gt 0 ]; then local last_index=$(( ${#group[@]} - 1 )) local keep="${group[$last_index]}" local keep_size keep_size=$(du -sh "$logs_dir/$keep" 2>/dev/null | cut -f1 | xargs) echo " [$prev_ide]" echo " + $keep ($keep_size) [KEEP]" local i for ((i = 0; i < last_index; i++)); do local old="${group[$i]}" local old_size old_size=$(du -sh "$logs_dir/$old" 2>/dev/null | cut -f1 | xargs) echo " - $old ($old_size)" to_delete+=("$logs_dir/$old") done fi group=() fi group+=("$dirname") prev_ide="$ide_name" done <<< "$sorted" # Process the last group if [ ${#group[@]} -gt 0 ]; then local last_index=$(( ${#group[@]} - 1 )) local keep="${group[$last_index]}" local keep_size keep_size=$(du -sh "$logs_dir/$keep" 2>/dev/null | cut -f1 | xargs) echo " [$prev_ide]" echo " + $keep ($keep_size) [KEEP]" local i for ((i = 0; i < last_index; i++)); do local old="${group[$i]}" local old_size old_size=$(du -sh "$logs_dir/$old" 2>/dev/null | cut -f1 | xargs) echo " - $old ($old_size)" to_delete+=("$logs_dir/$old") done fi if [ ${#to_delete[@]} -eq 0 ]; then echo " ✓ Không có log cũ để xóa." return fi local confirm read -r -p " Xác nhận xóa ${#to_delete[@]} thư mục cũ? (Y/n): " confirm if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then echo " Đã hủy." return fi for d in "${to_delete[@]}"; do echo " Đã xóa: $(basename "$d")" rm -rf -- "$d" done } # ----------------------------- # Function: Cleanup pip cache # ----------------------------- cleanup_pip() { echo "--- [Pip Cache] ---" if ! command -v pip3 &> /dev/null; then echo " ⚠ pip3 command not found." return fi local cache_dir cache_dir=$(pip3 cache dir 2>/dev/null) if [ -d "$cache_dir" ]; then local size size=$(du -sh "$cache_dir" 2>/dev/null | cut -f1 | xargs) echo " $cache_dir" echo " ✗ Cache: $size" local confirm read -r -p " Xác nhận xóa? (Y/n): " confirm if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then echo " Đã hủy." return fi pip3 cache purge 2>/dev/null echo " Đã xong." else echo " ✓ Cache rỗng." fi } # ----------------------------- # Function: Cleanup deno cache # ----------------------------- cleanup_deno() { echo "--- [Deno Cache] ---" local deno_cache="$HOME/Library/Caches/deno" echo " $deno_cache" if [ ! -d "$deno_cache" ]; then echo " ⚠ Thư mục không tồn tại." return fi local size size=$(du -sh "$deno_cache" 2>/dev/null | cut -f1 | xargs) echo " ✗ Cache: $size" local confirm read -r -p " Xác nhận xóa? (Y/n): " confirm if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then echo " Đã hủy." return fi rm -rf -- "$deno_cache"/* echo " Đã xong." } # ----------------------------- # Function: Cleanup go build cache # ----------------------------- cleanup_go_build() { echo "--- [Go Build Cache] ---" if ! command -v go &> /dev/null; then echo " ⚠ go command not found." return fi local cache_dir cache_dir=$(go env GOCACHE) if [ -d "$cache_dir" ]; then local size size=$(du -sh "$cache_dir" 2>/dev/null | cut -f1 | xargs) echo " $cache_dir" echo " ✗ Cache: $size" local confirm read -r -p " Xác nhận xóa? (Y/n): " confirm if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then echo " Đã hủy." return fi go clean -cache echo " Đã xong." else echo " ✓ Cache rỗng." fi } # ----------------------------- # Function: Cleanup generic cache directory # $1 = label, $2 = đường dẫn thư mục # ----------------------------- cleanup_cache_dir() { local label="$1" local cache_dir="$2" echo "--- [$label Cache] ---" echo " $cache_dir" if [ ! -d "$cache_dir" ]; then echo " ⚠ Thư mục không tồn tại." return fi local size size=$(du -sh "$cache_dir" 2>/dev/null | cut -f1 | xargs) echo " ✗ Dung lượng: $size" local confirm read -r -p " Xác nhận xóa? (Y/n): " confirm if [[ -n "$confirm" && ! "$confirm" =~ ^([yY]|[yY][eE][sS])$ ]]; then echo " Đã hủy." return fi rm -rf -- "$cache_dir"/* echo " Đã xong." } # ============================================================================= # Interactive Menu System # ============================================================================= # Tên các nhóm cleanup MENU_ITEMS=( "All" "Browsers (Edge, Mozilla, Playwright)" "IDE (VS Code, AntiGravity)" "Dev Caches (Go, NPM, Pip, Deno)" "IDE Logs (JetBrains, Google)" "Other Caches (Raspberry Pi)" "Homebrew" ) # Trạng thái chọn (0=off, 1=on), mặc định All=on MENU_SELECTED=(1 1 1 1 1 1 1) MENU_CURSOR=0 MENU_COUNT=${#MENU_ITEMS[@]} # Vẽ menu lên terminal draw_menu() { # Di chuyển cursor lên đầu menu để vẽ lại if [ "$1" = "redraw" ]; then printf "\033[%dA" "$((MENU_COUNT + 2))" fi echo " Chọn mục cần dọn dẹp (↑↓: di chuyển | Space: chọn/bỏ chọn | Enter: chạy các mục có [✓]):" echo "" local i for ((i = 0; i < MENU_COUNT; i++)); do local prefix=" " [ $i -eq $MENU_CURSOR ] && prefix="▸ " local check="[ ]" [ "${MENU_SELECTED[$i]}" -eq 1 ] && check="[✓]" local label="${MENU_ITEMS[$i]}" # In dòng, xóa hết ký tự cũ trên dòng printf "\r\033[K%s%s %s\n" "$prefix" "$check" "$label" done } # Toggle All: chọn hoặc bỏ tất cả toggle_all() { local new_val="${MENU_SELECTED[0]}" # Nếu All đang bật → tắt hết, ngược lại → bật hết if [ "$new_val" -eq 1 ]; then new_val=0 else new_val=1 fi local i for ((i = 0; i < MENU_COUNT; i++)); do MENU_SELECTED[$i]=$new_val done } # Cập nhật lại trạng thái All dựa trên các item con sync_all() { local all_on=1 local i for ((i = 1; i < MENU_COUNT; i++)); do if [ "${MENU_SELECTED[$i]}" -eq 0 ]; then all_on=0 break fi done MENU_SELECTED[0]=$all_on } # Chạy interactive menu run_menu() { echo "" echo "===== 🧹 Cleanup macOS =====" echo "" draw_menu while true; do # Đọc 1 ký tự (không echo, không buffer) IFS= read -rsn1 key case "$key" in # ESC sequence (arrow keys) $'\x1b') read -rsn2 seq case "$seq" in '[A') # Up MENU_CURSOR=$(( (MENU_CURSOR - 1 + MENU_COUNT) % MENU_COUNT )) ;; '[B') # Down MENU_CURSOR=$(( (MENU_CURSOR + 1) % MENU_COUNT )) ;; esac ;; # Space = toggle ' ') if [ $MENU_CURSOR -eq 0 ]; then toggle_all else if [ "${MENU_SELECTED[$MENU_CURSOR]}" -eq 1 ]; then MENU_SELECTED[$MENU_CURSOR]=0 else MENU_SELECTED[$MENU_CURSOR]=1 fi sync_all fi ;; # Enter = confirm '') echo "" break ;; esac draw_menu "redraw" done } # ============================================================================= # Hàm chạy từng nhóm # ============================================================================= run_browsers() { cleanup_versions "Edge" "$HOME/Library/Application Support/Microsoft/EdgeUpdater/apps/msedge-stable" echo "" cleanup_versions "Edge Updater" "$HOME/Library/Application Support/Microsoft/EdgeUpdater/apps/msedge-updater" echo "" cleanup_cache_dir "Mozilla" "$HOME/Library/Caches/Mozilla" echo "" cleanup_cache_dir "Playwright" "$HOME/Library/Caches/ms-playwright" echo "" } run_ide() { cleanup_vscode_extensions echo "" cleanup_vscode_cpptools echo "" cleanup_cache_dir "VS Code ShipIt" "$HOME/Library/Caches/com.microsoft.VSCode.ShipIt" echo "" cleanup_agy_recordings echo "" } run_dev_caches() { cleanup_go echo "" cleanup_go_build echo "" cleanup_npm echo "" cleanup_pip echo "" cleanup_deno echo "" } run_ide_logs() { cleanup_ide_logs "JetBrains" "$HOME/Library/Logs/JetBrains" echo "" cleanup_ide_logs "Google" "$HOME/Library/Logs/Google" echo "" } run_other_caches() { cleanup_cache_dir "Raspberry Pi" "$HOME/Library/Caches/Raspberry Pi" echo "" } run_homebrew() { cleanup_homebrew echo "" } # ============================================================================= # Main # ============================================================================= # Map index → function GROUP_FUNCS=( "" # 0 = All (handled separately) "run_browsers" # 1 "run_ide" # 2 "run_dev_caches" # 3 "run_ide_logs" # 4 "run_other_caches" # 5 "run_homebrew" # 6 ) run_menu # Kiểm tra có mục nào được chọn không has_selection=0 for ((i = 1; i < MENU_COUNT; i++)); do if [ "${MENU_SELECTED[$i]}" -eq 1 ]; then has_selection=1 break fi done if [ "$has_selection" -eq 0 ]; then echo "Không có mục nào được chọn. Thoát." exit 0 fi # Chạy các nhóm đã chọn echo "===== Bắt đầu cleanup =====" echo "" for ((i = 1; i < MENU_COUNT; i++)); do if [ "${MENU_SELECTED[$i]}" -eq 1 ]; then ${GROUP_FUNCS[$i]} fi done echo "===== ✅ Cleanup hoàn tất ====="