Last active
April 10, 2026 05:01
-
-
Save swateek/9a9c89fae5900ce1c31e596a8fe4a8cb to your computer and use it in GitHub Desktop.
Vagrant Cleanup Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| CYAN='\033[0;36m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| RED='\033[0;31m' | |
| BOLD='\033[1m' | |
| NC='\033[0m' | |
| # Use $HOME by default, or override with CLEANUP_HOME env var | |
| # e.g. CLEANUP_HOME=/home/otheruser ~/cleanup.sh | |
| CLEANUP_HOME="${CLEANUP_HOME:-$HOME}" | |
| TOTAL_FREED=0 | |
| AUTO_CONFIRM=false | |
| print_header() { | |
| echo "" | |
| echo -e "${BOLD}${CYAN}========================================${NC}" | |
| echo -e "${BOLD}${CYAN} $1${NC}" | |
| echo -e "${BOLD}${CYAN}========================================${NC}" | |
| } | |
| print_size() { | |
| du -sh "$1" 2>/dev/null | cut -f1 | |
| } | |
| bytes_to_human() { | |
| numfmt --to=iec "$1" 2>/dev/null || echo "${1}B" | |
| } | |
| get_dir_bytes() { | |
| du -sb "$1" 2>/dev/null | cut -f1 | |
| } | |
| confirm() { | |
| if $AUTO_CONFIRM; then | |
| return 0 | |
| fi | |
| read -rp "β οΈ $1 (yes/no): " CONFIRM | |
| [[ "$CONFIRM" == "yes" ]] | |
| } | |
| freed_message() { | |
| echo -e "${GREEN}β Freed: $1${NC}" | |
| } | |
| skip_message() { | |
| echo -e "${YELLOW}βοΈ Skipped.${NC}" | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # 1. Terraform .terraform directories | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| cleanup_terraform() { | |
| print_header "Terraform Provider Cache (.terraform)" | |
| mapfile -t DIRS < <(find "$CLEANUP_HOME" -type d -name ".terraform" 2>/dev/null) | |
| if [ ${#DIRS[@]} -eq 0 ]; then | |
| echo " No .terraform directories found." | |
| return | |
| fi | |
| echo "" | |
| for i in "${!DIRS[@]}"; do | |
| SIZE=$(print_size "${DIRS[$i]}") | |
| echo " [$i] $SIZE ${DIRS[$i]}" | |
| done | |
| if $AUTO_CONFIRM; then | |
| CHOICE="a" | |
| else | |
| echo "" | |
| echo " Options: (a)ll (s)elect indexes (n)one" | |
| read -rp " Choice: " CHOICE | |
| fi | |
| case "$CHOICE" in | |
| a) | |
| BEFORE=0 | |
| for DIR in "${DIRS[@]}"; do | |
| BEFORE=$((BEFORE + $(get_dir_bytes "$DIR"))) | |
| rm -rf "$DIR" | |
| echo " ποΈ Deleted: $DIR" | |
| done | |
| TOTAL_FREED=$((TOTAL_FREED + BEFORE)) | |
| freed_message "$(bytes_to_human $BEFORE)" | |
| ;; | |
| s) | |
| read -rp " Enter indexes (e.g. 0,2,4): " INDEXES | |
| IFS=',' read -ra SELECTED <<< "$INDEXES" | |
| BEFORE=0 | |
| for i in "${SELECTED[@]}"; do | |
| i=$(echo "$i" | tr -d ' ') | |
| if [[ -n "${DIRS[$i]}" ]]; then | |
| BEFORE=$((BEFORE + $(get_dir_bytes "${DIRS[$i]}"))) | |
| rm -rf "${DIRS[$i]}" | |
| echo " ποΈ Deleted: ${DIRS[$i]}" | |
| else | |
| echo " β οΈ Invalid index: $i β skipping" | |
| fi | |
| done | |
| TOTAL_FREED=$((TOTAL_FREED + BEFORE)) | |
| freed_message "$(bytes_to_human $BEFORE)" | |
| ;; | |
| *) | |
| skip_message | |
| ;; | |
| esac | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # 2. Playwright browser cache | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| cleanup_playwright() { | |
| print_header "Playwright Browser Cache" | |
| PLAYWRIGHT_DIRS=( | |
| "$CLEANUP_HOME/.cache/ms-playwright" | |
| "$CLEANUP_HOME/.cache/ms-playwright-go" | |
| ) | |
| FOUND=0 | |
| for DIR in "${PLAYWRIGHT_DIRS[@]}"; do | |
| if [ -d "$DIR" ]; then | |
| SIZE=$(print_size "$DIR") | |
| echo " π $SIZE $DIR" | |
| FOUND=1 | |
| fi | |
| done | |
| if [ $FOUND -eq 0 ]; then | |
| echo " No Playwright cache found." | |
| return | |
| fi | |
| echo "" | |
| if confirm "Delete Playwright browser cache?"; then | |
| BEFORE=0 | |
| for DIR in "${PLAYWRIGHT_DIRS[@]}"; do | |
| if [ -d "$DIR" ]; then | |
| BEFORE=$((BEFORE + $(get_dir_bytes "$DIR"))) | |
| rm -rf "$DIR" | |
| echo " ποΈ Deleted: $DIR" | |
| fi | |
| done | |
| TOTAL_FREED=$((TOTAL_FREED + BEFORE)) | |
| freed_message "$(bytes_to_human $BEFORE)" | |
| else | |
| skip_message | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # 3. APT package cache | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| cleanup_apt() { | |
| print_header "APT Package Cache" | |
| SIZE=$(print_size "/var/cache/apt") | |
| echo " π $SIZE /var/cache/apt" | |
| echo "" | |
| if confirm "Run apt clean, autoclean, and autoremove?"; then | |
| BEFORE=$(get_dir_bytes "/var/cache/apt") | |
| sudo apt-get clean -y | |
| sudo apt-get autoclean -y | |
| sudo apt-get autoremove -y --purge | |
| AFTER=$(get_dir_bytes "/var/cache/apt") | |
| DIFF=$((BEFORE - AFTER)) | |
| TOTAL_FREED=$((TOTAL_FREED + DIFF)) | |
| freed_message "$(bytes_to_human $DIFF)" | |
| else | |
| skip_message | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # 4. Journal logs | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| cleanup_journal() { | |
| print_header "Systemd Journal Logs" | |
| SIZE=$(journalctl --disk-usage 2>/dev/null | grep -oP '[\d.]+\s*\w+(?= on disk)') | |
| echo " π Journal logs currently using: ${SIZE:-unknown}" | |
| echo "" | |
| if confirm "Truncate journal logs to last 7 days / 50MB max?"; then | |
| sudo journalctl --vacuum-time=7d | |
| sudo journalctl --vacuum-size=50M | |
| freed_message "Journal logs truncated" | |
| else | |
| skip_message | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # 5. Temp files | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| cleanup_tmp() { | |
| print_header "Temp Files (/tmp, /var/tmp)" | |
| TMP_SIZE=$(print_size "/tmp") | |
| VAR_TMP_SIZE=$(print_size "/var/tmp") | |
| echo " π $TMP_SIZE /tmp" | |
| echo " π $VAR_TMP_SIZE /var/tmp" | |
| echo "" | |
| if confirm "Clear /tmp and /var/tmp?"; then | |
| BEFORE=$(($(get_dir_bytes "/tmp") + $(get_dir_bytes "/var/tmp"))) | |
| sudo rm -rf /tmp/* /var/tmp/* | |
| TOTAL_FREED=$((TOTAL_FREED + BEFORE)) | |
| freed_message "$(bytes_to_human $BEFORE)" | |
| else | |
| skip_message | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # 6. Old log files | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| cleanup_logs() { | |
| print_header "Old Log Files (/var/log)" | |
| SIZE=$(print_size "/var/log") | |
| echo " π $SIZE /var/log (compressed/rotated logs)" | |
| echo "" | |
| if confirm "Remove compressed and rotated log files (*.gz, *.old)?"; then | |
| BEFORE=$(find /var/log -type f \( -name "*.gz" -o -name "*.old" \) -exec du -sb {} + 2>/dev/null | awk '{sum+=$1} END{print sum+0}') | |
| sudo find /var/log -type f \( -name "*.gz" -o -name "*.old" \) -delete | |
| TOTAL_FREED=$((TOTAL_FREED + BEFORE)) | |
| freed_message "$(bytes_to_human $BEFORE)" | |
| else | |
| skip_message | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # 7. NPM cache | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| cleanup_npm() { | |
| print_header "NPM Cache" | |
| NPM_CACHE="$CLEANUP_HOME/.npm" | |
| if [ ! -d "$NPM_CACHE" ]; then | |
| echo " No NPM cache found." | |
| return | |
| fi | |
| SIZE=$(print_size "$NPM_CACHE") | |
| echo " π $SIZE $NPM_CACHE" | |
| echo "" | |
| if confirm "Clear NPM cache?"; then | |
| BEFORE=$(get_dir_bytes "$NPM_CACHE") | |
| npm cache clean --force 2>/dev/null | |
| AFTER=$(get_dir_bytes "$NPM_CACHE") | |
| DIFF=$((BEFORE - AFTER)) | |
| TOTAL_FREED=$((TOTAL_FREED + DIFF)) | |
| freed_message "$(bytes_to_human $DIFF)" | |
| else | |
| skip_message | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # 8. NVM old Node versions | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| cleanup_nvm() { | |
| print_header "NVM β Old Node Versions" | |
| NVM_DIR="$CLEANUP_HOME/.nvm/versions/node" | |
| if [ ! -d "$NVM_DIR" ]; then | |
| echo " NVM not found." | |
| return | |
| fi | |
| CURRENT=$(node --version 2>/dev/null) | |
| echo " Current active Node version: ${CURRENT:-unknown}" | |
| echo "" | |
| mapfile -t VERSIONS < <(ls "$NVM_DIR") | |
| if [ ${#VERSIONS[@]} -le 1 ]; then | |
| echo " Only one Node version installed, nothing to prune." | |
| return | |
| fi | |
| echo " Installed versions:" | |
| for i in "${!VERSIONS[@]}"; do | |
| SIZE=$(print_size "$NVM_DIR/${VERSIONS[$i]}") | |
| ACTIVE="" | |
| [[ "v${VERSIONS[$i]#v}" == "$CURRENT" ]] && ACTIVE=" β current" | |
| echo " [$i] $SIZE ${VERSIONS[$i]}$ACTIVE" | |
| done | |
| echo "" | |
| if $AUTO_CONFIRM; then | |
| # In auto mode, delete all non-current versions | |
| BEFORE=0 | |
| for i in "${!VERSIONS[@]}"; do | |
| VER_PATH="$NVM_DIR/${VERSIONS[$i]}" | |
| if [[ "v${VERSIONS[$i]#v}" == "$CURRENT" ]]; then | |
| echo " βοΈ Keeping current: ${VERSIONS[$i]}" | |
| else | |
| BEFORE=$((BEFORE + $(get_dir_bytes "$VER_PATH"))) | |
| rm -rf "$VER_PATH" | |
| echo " ποΈ Deleted: ${VERSIONS[$i]}" | |
| fi | |
| done | |
| TOTAL_FREED=$((TOTAL_FREED + BEFORE)) | |
| freed_message "$(bytes_to_human $BEFORE)" | |
| else | |
| echo " Options: (s)elect indexes to delete (n)one" | |
| read -rp " Choice: " CHOICE | |
| case "$CHOICE" in | |
| s) | |
| read -rp " Enter indexes to delete (e.g. 0,2): " INDEXES | |
| IFS=',' read -ra SELECTED <<< "$INDEXES" | |
| BEFORE=0 | |
| for i in "${SELECTED[@]}"; do | |
| i=$(echo "$i" | tr -d ' ') | |
| if [[ -n "${VERSIONS[$i]}" ]]; then | |
| VER_PATH="$NVM_DIR/${VERSIONS[$i]}" | |
| if [[ "v${VERSIONS[$i]#v}" == "$CURRENT" ]]; then | |
| echo " β οΈ Skipping current version: ${VERSIONS[$i]}" | |
| else | |
| BEFORE=$((BEFORE + $(get_dir_bytes "$VER_PATH"))) | |
| rm -rf "$VER_PATH" | |
| echo " ποΈ Deleted: ${VERSIONS[$i]}" | |
| fi | |
| else | |
| echo " β οΈ Invalid index: $i β skipping" | |
| fi | |
| done | |
| TOTAL_FREED=$((TOTAL_FREED + BEFORE)) | |
| freed_message "$(bytes_to_human $BEFORE)" | |
| ;; | |
| *) | |
| skip_message | |
| ;; | |
| esac | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # 9. Zsh completion dumps | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| cleanup_zcompdump() { | |
| print_header "Zsh Completion Dumps (.zcompdump*)" | |
| mapfile -t FILES < <(find "$CLEANUP_HOME" -maxdepth 1 -name ".zcompdump*" 2>/dev/null) | |
| if [ ${#FILES[@]} -eq 0 ]; then | |
| echo " No .zcompdump files found." | |
| return | |
| fi | |
| BEFORE=0 | |
| for f in "${FILES[@]}"; do | |
| SIZE=$(print_size "$f") | |
| BYTES=$(get_dir_bytes "$f") | |
| BEFORE=$((BEFORE + BYTES)) | |
| echo " π $SIZE $f" | |
| done | |
| echo "" | |
| if confirm "Delete all .zcompdump files? (they regenerate automatically)"; then | |
| for f in "${FILES[@]}"; do | |
| rm -f "$f" | |
| echo " ποΈ Deleted: $f" | |
| done | |
| TOTAL_FREED=$((TOTAL_FREED + BEFORE)) | |
| freed_message "$(bytes_to_human $BEFORE)" | |
| else | |
| skip_message | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # 10. Cursor server old versions | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| cleanup_cursor_server() { | |
| print_header "Cursor Server β Old Versions" | |
| CURSOR_SERVER_DIR="$CLEANUP_HOME/.cursor-server" | |
| if [ ! -d "$CURSOR_SERVER_DIR" ]; then | |
| echo " No .cursor-server directory found." | |
| return | |
| fi | |
| mapfile -t VERSIONS < <(ls "$CURSOR_SERVER_DIR" 2>/dev/null) | |
| if [ ${#VERSIONS[@]} -le 1 ]; then | |
| echo " Only one Cursor server version installed, nothing to prune." | |
| return | |
| fi | |
| echo " Installed versions (newest last):" | |
| for i in "${!VERSIONS[@]}"; do | |
| SIZE=$(print_size "$CURSOR_SERVER_DIR/${VERSIONS[$i]}") | |
| echo " [$i] $SIZE ${VERSIONS[$i]}" | |
| done | |
| LATEST_IDX=$(( ${#VERSIONS[@]} - 1 )) | |
| echo "" | |
| if $AUTO_CONFIRM; then | |
| # Keep only the latest version | |
| BEFORE=0 | |
| for i in "${!VERSIONS[@]}"; do | |
| if [ "$i" -eq "$LATEST_IDX" ]; then | |
| echo " βοΈ Keeping latest: ${VERSIONS[$i]}" | |
| else | |
| VER_PATH="$CURSOR_SERVER_DIR/${VERSIONS[$i]}" | |
| BEFORE=$((BEFORE + $(get_dir_bytes "$VER_PATH"))) | |
| rm -rf "$VER_PATH" | |
| echo " ποΈ Deleted: ${VERSIONS[$i]}" | |
| fi | |
| done | |
| TOTAL_FREED=$((TOTAL_FREED + BEFORE)) | |
| freed_message "$(bytes_to_human $BEFORE)" | |
| else | |
| echo " Tip: Keep the highest-numbered version, delete the rest." | |
| echo " Options: (s)elect indexes to delete (n)one" | |
| read -rp " Choice: " CHOICE | |
| case "$CHOICE" in | |
| s) | |
| read -rp " Enter indexes to delete (e.g. 0,1): " INDEXES | |
| IFS=',' read -ra SELECTED <<< "$INDEXES" | |
| BEFORE=0 | |
| for i in "${SELECTED[@]}"; do | |
| i=$(echo "$i" | tr -d ' ') | |
| if [[ -n "${VERSIONS[$i]}" ]]; then | |
| VER_PATH="$CURSOR_SERVER_DIR/${VERSIONS[$i]}" | |
| BEFORE=$((BEFORE + $(get_dir_bytes "$VER_PATH"))) | |
| rm -rf "$VER_PATH" | |
| echo " ποΈ Deleted: ${VERSIONS[$i]}" | |
| else | |
| echo " β οΈ Invalid index: $i β skipping" | |
| fi | |
| done | |
| TOTAL_FREED=$((TOTAL_FREED + BEFORE)) | |
| freed_message "$(bytes_to_human $BEFORE)" | |
| ;; | |
| *) | |
| skip_message | |
| ;; | |
| esac | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # 11. VS Code Remote Server β Old Versions | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # ~/.vscode-server/cli/servers/ holds one subdirectory per VS Code | |
| # server version (named by commit hash). Deleting the ACTIVE version | |
| # breaks remote SSH connections until VS Code re-downloads it. | |
| # We always keep the most recently modified version (= the one VS Code | |
| # last connected with) and offer to delete the rest. | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| cleanup_vscode_server() { | |
| print_header "VS Code Remote Server β Old Versions (~/.vscode-server)" | |
| # VS Code stores servers under either of these paths depending on version | |
| VSCODE_SERVER_ROOTS=( | |
| "$CLEANUP_HOME/.vscode-server/cli/servers" | |
| "$CLEANUP_HOME/.vscode-server/bin" | |
| ) | |
| FOUND=0 | |
| for ROOT in "${VSCODE_SERVER_ROOTS[@]}"; do | |
| [ -d "$ROOT" ] && FOUND=1 && break | |
| done | |
| if [ $FOUND -eq 0 ]; then | |
| echo " No VS Code remote server directory found." | |
| return | |
| fi | |
| declare -a ALL_VERSIONS | |
| declare -a ALL_VERSION_ROOTS | |
| for ROOT in "${VSCODE_SERVER_ROOTS[@]}"; do | |
| [ ! -d "$ROOT" ] && continue | |
| while IFS= read -r -d '' DIR; do | |
| ALL_VERSIONS+=("$DIR") | |
| ALL_VERSION_ROOTS+=("$ROOT") | |
| done < <(find "$ROOT" -maxdepth 1 -mindepth 1 -type d -print0 2>/dev/null) | |
| done | |
| TOTAL=${#ALL_VERSIONS[@]} | |
| if [ "$TOTAL" -eq 0 ]; then | |
| echo " No server versions found." | |
| return | |
| fi | |
| if [ "$TOTAL" -le 1 ]; then | |
| echo " Only one VS Code server version installed β nothing to prune." | |
| SIZE=$(print_size "${ALL_VERSIONS[0]}") | |
| echo " β $SIZE $(basename "${ALL_VERSIONS[0]}") β keeping" | |
| return | |
| fi | |
| # Sort by modification time ascending (oldest first), keep the newest | |
| mapfile -t SORTED < <( | |
| for DIR in "${ALL_VERSIONS[@]}"; do | |
| echo "$(stat -c '%Y' "$DIR" 2>/dev/null) $DIR" | |
| done | sort -n | awk '{print $2}' | |
| ) | |
| LATEST="${SORTED[-1]}" | |
| LATEST_NAME=$(basename "$LATEST") | |
| echo " Installed versions (oldest β newest):" | |
| DELETABLE_BYTES=0 | |
| declare -a TO_DELETE | |
| for i in "${!SORTED[@]}"; do | |
| DIR="${SORTED[$i]}" | |
| NAME=$(basename "$DIR") | |
| SIZE=$(print_size "$DIR") | |
| if [[ "$DIR" == "$LATEST" ]]; then | |
| echo " [$i] $SIZE $NAME β ACTIVE (will keep)" | |
| else | |
| BYTES=$(get_dir_bytes "$DIR") | |
| DELETABLE_BYTES=$((DELETABLE_BYTES + BYTES)) | |
| TO_DELETE+=("$DIR") | |
| echo " [$i] $SIZE $NAME" | |
| fi | |
| done | |
| DELETE_COUNT=${#TO_DELETE[@]} | |
| echo "" | |
| echo " Candidates to delete: $DELETE_COUNT old version(s) (~$(bytes_to_human $DELETABLE_BYTES))" | |
| echo " β οΈ The ACTIVE version ($LATEST_NAME) is always preserved." | |
| echo "" | |
| if [ $DELETE_COUNT -eq 0 ]; then | |
| echo " Nothing to prune." | |
| return | |
| fi | |
| if $AUTO_CONFIRM || confirm "Delete old VS Code server versions (active version is safe)?"; then | |
| FREED=0 | |
| for DIR in "${TO_DELETE[@]}"; do | |
| FREED=$((FREED + $(get_dir_bytes "$DIR"))) | |
| rm -rf "$DIR" | |
| echo " ποΈ Deleted: $(basename "$DIR")" | |
| done | |
| TOTAL_FREED=$((TOTAL_FREED + FREED)) | |
| freed_message "$(bytes_to_human $FREED)" | |
| else | |
| skip_message | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # 12. Virtualenv cache | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| cleanup_virtualenv() { | |
| print_header "Virtualenv Cache" | |
| VENV_CACHE="$CLEANUP_HOME/.local/share/virtualenv" | |
| if [ ! -d "$VENV_CACHE" ]; then | |
| echo " No virtualenv cache found." | |
| return | |
| fi | |
| SIZE=$(print_size "$VENV_CACHE") | |
| echo " π $SIZE $VENV_CACHE" | |
| echo "" | |
| if confirm "Clear virtualenv cache?"; then | |
| BEFORE=$(get_dir_bytes "$VENV_CACHE") | |
| rm -rf "$VENV_CACHE" | |
| TOTAL_FREED=$((TOTAL_FREED + BEFORE)) | |
| freed_message "$(bytes_to_human $BEFORE)" | |
| else | |
| skip_message | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # 12. General .cache sweep | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| cleanup_cache() { | |
| print_header "General Cache (~/.cache)" | |
| CACHE_DIR="$CLEANUP_HOME/.cache" | |
| if [ ! -d "$CACHE_DIR" ]; then | |
| echo " No .cache directory found." | |
| return | |
| fi | |
| echo " Cache subdirectories:" | |
| mapfile -t SUBDIRS < <(find "$CACHE_DIR" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sort) | |
| # Dirs inside ~/.cache that must never be auto-deleted: | |
| # ms-playwright / ms-playwright-go β handled by their own section | |
| # vscode-server / vscode-server-insiders β breaks VS Code Remote SSH if wiped | |
| is_cache_protected() { | |
| local NAME="$1" | |
| local PROTECTED=("ms-playwright" "ms-playwright-go" "vscode-server" "vscode-server-insiders") | |
| for P in "${PROTECTED[@]}"; do | |
| [[ "$NAME" == "$P" ]] && return 0 | |
| done | |
| return 1 | |
| } | |
| TOTAL_CACHE=0 | |
| for i in "${!SUBDIRS[@]}"; do | |
| NAME=$(basename "${SUBDIRS[$i]}") | |
| is_cache_protected "$NAME" && continue | |
| SIZE=$(print_size "${SUBDIRS[$i]}") | |
| BYTES=$(get_dir_bytes "${SUBDIRS[$i]}") | |
| TOTAL_CACHE=$((TOTAL_CACHE + BYTES)) | |
| echo " [$i] $SIZE $NAME" | |
| done | |
| echo "" | |
| echo " Total: $(bytes_to_human $TOTAL_CACHE)" | |
| echo " (vscode-server, ms-playwright excluded β managed by their own sections)" | |
| echo "" | |
| if $AUTO_CONFIRM; then | |
| BEFORE=0 | |
| for DIR in "${SUBDIRS[@]}"; do | |
| NAME=$(basename "$DIR") | |
| is_cache_protected "$NAME" && continue | |
| BEFORE=$((BEFORE + $(get_dir_bytes "$DIR"))) | |
| rm -rf "$DIR" | |
| echo " ποΈ Deleted: $NAME" | |
| done | |
| TOTAL_FREED=$((TOTAL_FREED + BEFORE)) | |
| freed_message "$(bytes_to_human $BEFORE)" | |
| else | |
| echo " Options: (a)ll (s)elect indexes (n)one" | |
| read -rp " Choice: " CHOICE | |
| case "$CHOICE" in | |
| a) | |
| if confirm "Delete ALL .cache subdirectories (except protected dirs)?"; then | |
| BEFORE=0 | |
| for DIR in "${SUBDIRS[@]}"; do | |
| NAME=$(basename "$DIR") | |
| is_cache_protected "$NAME" && continue | |
| BEFORE=$((BEFORE + $(get_dir_bytes "$DIR"))) | |
| rm -rf "$DIR" | |
| echo " ποΈ Deleted: $NAME" | |
| done | |
| TOTAL_FREED=$((TOTAL_FREED + BEFORE)) | |
| freed_message "$(bytes_to_human $BEFORE)" | |
| else | |
| skip_message | |
| fi | |
| ;; | |
| s) | |
| read -rp " Enter indexes (e.g. 0,2,4): " INDEXES | |
| IFS=',' read -ra SELECTED <<< "$INDEXES" | |
| BEFORE=0 | |
| for i in "${SELECTED[@]}"; do | |
| i=$(echo "$i" | tr -d ' ') | |
| if [[ -n "${SUBDIRS[$i]}" ]]; then | |
| NAME=$(basename "${SUBDIRS[$i]}") | |
| if is_cache_protected "$NAME"; then | |
| echo " β οΈ Skipping protected dir: $NAME" | |
| continue | |
| fi | |
| BEFORE=$((BEFORE + $(get_dir_bytes "${SUBDIRS[$i]}"))) | |
| rm -rf "${SUBDIRS[$i]}" | |
| echo " ποΈ Deleted: $NAME" | |
| else | |
| echo " β οΈ Invalid index: $i β skipping" | |
| fi | |
| done | |
| TOTAL_FREED=$((TOTAL_FREED + BEFORE)) | |
| freed_message "$(bytes_to_human $BEFORE)" | |
| ;; | |
| *) | |
| skip_message | |
| ;; | |
| esac | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # 13. Mozilla Firefox cache | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| cleanup_mozilla() { | |
| print_header "Mozilla Firefox Cache" | |
| FIREFOX_DIR="$CLEANUP_HOME/.mozilla/firefox" | |
| if [ ! -d "$FIREFOX_DIR" ]; then | |
| echo " No Firefox profile found." | |
| return | |
| fi | |
| mapfile -t CACHE_DIRS < <(find "$FIREFOX_DIR" -maxdepth 2 -type d \( -name "cache2" -o -name "startupCache" -o -name "jumpListCache" \) 2>/dev/null) | |
| if [ ${#CACHE_DIRS[@]} -eq 0 ]; then | |
| echo " No Firefox cache directories found." | |
| return | |
| fi | |
| BEFORE=0 | |
| for DIR in "${CACHE_DIRS[@]}"; do | |
| SIZE=$(print_size "$DIR") | |
| BYTES=$(get_dir_bytes "$DIR") | |
| BEFORE=$((BEFORE + BYTES)) | |
| echo " π $SIZE $DIR" | |
| done | |
| echo "" | |
| if confirm "Clear Firefox cache directories? (profile/bookmarks/passwords are untouched)"; then | |
| for DIR in "${CACHE_DIRS[@]}"; do | |
| rm -rf "$DIR" | |
| echo " ποΈ Deleted: $DIR" | |
| done | |
| TOTAL_FREED=$((TOTAL_FREED + BEFORE)) | |
| freed_message "$(bytes_to_human $BEFORE)" | |
| else | |
| skip_message | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # 14. Claude Code β Old Session Transcripts | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # ~/.claude/projects/<project-slug>/<session>.jsonl | |
| # Each .jsonl is a full session transcript. Sessions grow quickly | |
| # and can reach several GBs. We keep the N most recent per project | |
| # and delete the rest. settings.json, CLAUDE.md, commands/, and | |
| # .credentials.json are never touched. | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| cleanup_claude_sessions() { | |
| print_header "Claude Code β Session Transcripts (~/.claude/projects)" | |
| CLAUDE_PROJECTS_DIR="$CLEANUP_HOME/.claude/projects" | |
| if [ ! -d "$CLAUDE_PROJECTS_DIR" ]; then | |
| echo " No Claude Code projects directory found." | |
| return | |
| fi | |
| # Default: keep the 3 most recent sessions per project | |
| KEEP_COUNT=3 | |
| if ! $AUTO_CONFIRM; then | |
| echo " How many recent sessions to keep per project? (default: 3)" | |
| read -rp " Keep count [3]: " USER_KEEP | |
| [[ "$USER_KEEP" =~ ^[0-9]+$ ]] && KEEP_COUNT="$USER_KEEP" | |
| fi | |
| mapfile -t PROJECT_DIRS < <(find "$CLAUDE_PROJECTS_DIR" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sort) | |
| if [ ${#PROJECT_DIRS[@]} -eq 0 ]; then | |
| echo " No project session directories found." | |
| return | |
| fi | |
| TOTAL_SESSIONS=0 | |
| DELETABLE_BYTES=0 | |
| echo "" | |
| echo " Projects found (sessions older than the $KEEP_COUNT most recent will be pruned):" | |
| echo "" | |
| declare -A PROJECT_DELETE_MAP | |
| for PROJ_DIR in "${PROJECT_DIRS[@]}"; do | |
| PROJ_NAME=$(basename "$PROJ_DIR") | |
| mapfile -t ALL_SESSIONS < <(find "$PROJ_DIR" -maxdepth 1 -name "*.jsonl" -type f 2>/dev/null | sort) | |
| TOTAL=${#ALL_SESSIONS[@]} | |
| KEEP=$KEEP_COUNT | |
| [[ $KEEP -gt $TOTAL ]] && KEEP=$TOTAL | |
| DELETE_COUNT=$((TOTAL - KEEP)) | |
| if [ $DELETE_COUNT -le 0 ]; then | |
| echo " β $PROJ_NAME ($TOTAL sessions β all within keep limit)" | |
| PROJECT_DELETE_MAP["$PROJ_DIR"]="" | |
| continue | |
| fi | |
| # Sessions to delete = oldest ones (sorted ascending, so head) | |
| mapfile -t TO_DELETE < <(printf '%s\n' "${ALL_SESSIONS[@]}" | head -n "$DELETE_COUNT") | |
| PROJ_BYTES=0 | |
| for f in "${TO_DELETE[@]}"; do | |
| B=$(get_dir_bytes "$f") | |
| PROJ_BYTES=$((PROJ_BYTES + B)) | |
| done | |
| DELETABLE_BYTES=$((DELETABLE_BYTES + PROJ_BYTES)) | |
| TOTAL_SESSIONS=$((TOTAL_SESSIONS + DELETE_COUNT)) | |
| echo " ποΈ $PROJ_NAME" | |
| echo " Sessions: $TOTAL total | keeping: $KEEP | deleting: $DELETE_COUNT | $(bytes_to_human $PROJ_BYTES)" | |
| PROJECT_DELETE_MAP["$PROJ_DIR"]="${TO_DELETE[*]}" | |
| done | |
| echo "" | |
| echo " Total to delete: $TOTAL_SESSIONS sessions (~$(bytes_to_human $DELETABLE_BYTES))" | |
| echo "" | |
| if [ $TOTAL_SESSIONS -eq 0 ]; then | |
| echo " Nothing to prune β all projects are within the keep limit." | |
| return | |
| fi | |
| if $AUTO_CONFIRM || confirm "Delete old Claude Code session transcripts (settings/credentials/CLAUDE.md untouched)?"; then | |
| FREED=0 | |
| for PROJ_DIR in "${!PROJECT_DELETE_MAP[@]}"; do | |
| FILES_STR="${PROJECT_DELETE_MAP[$PROJ_DIR]}" | |
| [[ -z "$FILES_STR" ]] && continue | |
| read -ra FILES <<< "$FILES_STR" | |
| for f in "${FILES[@]}"; do | |
| FREED=$((FREED + $(get_dir_bytes "$f"))) | |
| rm -f "$f" | |
| echo " ποΈ Deleted: $(basename "$PROJ_DIR")/$(basename "$f")" | |
| done | |
| done | |
| TOTAL_FREED=$((TOTAL_FREED + FREED)) | |
| freed_message "$(bytes_to_human $FREED)" | |
| else | |
| skip_message | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # 15. Claude Code β Statsig Analytics Cache | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # ~/.claude/statsig/ is a local analytics/feature-flag cache | |
| # written by Claude Code. It is fully safe to delete β Claude Code | |
| # rebuilds it on next launch. It never contains credentials or | |
| # session history. | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| cleanup_claude_cache() { | |
| print_header "Claude Code β Analytics & Misc Cache (~/.claude/statsig)" | |
| CLAUDE_DIR="$CLEANUP_HOME/.claude" | |
| if [ ! -d "$CLAUDE_DIR" ]; then | |
| echo " No ~/.claude directory found (Claude Code not installed?)." | |
| return | |
| fi | |
| # Dirs that are safe to nuke and regenerate automatically | |
| SAFE_CACHE_DIRS=( | |
| "$CLAUDE_DIR/statsig" | |
| "$CLAUDE_DIR/shell-snapshots" | |
| ) | |
| FOUND=0 | |
| TOTAL_BYTES=0 | |
| for DIR in "${SAFE_CACHE_DIRS[@]}"; do | |
| if [ -d "$DIR" ]; then | |
| SIZE=$(print_size "$DIR") | |
| BYTES=$(get_dir_bytes "$DIR") | |
| TOTAL_BYTES=$((TOTAL_BYTES + BYTES)) | |
| echo " π $SIZE $DIR" | |
| FOUND=1 | |
| fi | |
| done | |
| if [ $FOUND -eq 0 ]; then | |
| echo " No Claude Code cache directories found." | |
| return | |
| fi | |
| echo "" | |
| echo " These are regenerated automatically on next Claude Code launch." | |
| echo " β οΈ settings.json / CLAUDE.md / .credentials.json are NOT touched." | |
| echo "" | |
| if $AUTO_CONFIRM || confirm "Delete Claude Code cache directories (statsig, shell-snapshots)?"; then | |
| FREED=0 | |
| for DIR in "${SAFE_CACHE_DIRS[@]}"; do | |
| if [ -d "$DIR" ]; then | |
| FREED=$((FREED + $(get_dir_bytes "$DIR"))) | |
| rm -rf "$DIR" | |
| echo " ποΈ Deleted: $DIR" | |
| fi | |
| done | |
| TOTAL_FREED=$((TOTAL_FREED + FREED)) | |
| freed_message "$(bytes_to_human $FREED)" | |
| else | |
| skip_message | |
| fi | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # MAIN | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| clear | |
| echo -e "${BOLD}${CYAN}" | |
| echo " ββββββββββ ββββββββ ββββββ ββββ ββββββ ββββββββββ " | |
| echo " βββββββββββ βββββββββββββββββββββ ββββββ βββββββββββ" | |
| echo " βββ βββ ββββββ ββββββββββββββ ββββββ βββββββββββ" | |
| echo " βββ βββ ββββββ βββββββββββββββββββββ ββββββββββ " | |
| echo " βββββββββββββββββββββββββββ ββββββ ββββββββββββββββββ " | |
| echo " ββββββββββββββββββββββββββ ββββββ βββββ βββββββ βββ " | |
| echo -e "${NC}" | |
| echo -e " ${BOLD}Vagrant Box Disk Cleanup Utility${NC}" | |
| echo -e " Run this script regularly to keep your box clean." | |
| echo -e " Home directory: ${CYAN}$CLEANUP_HOME${NC}" | |
| echo "" | |
| echo -e "${BOLD}Current Disk Usage:${NC}" | |
| df -h / | tail -1 | awk '{printf " Used: %s / %s (%s full)\n", $3, $2, $5}' | |
| echo "" | |
| # βββ Single upfront prompt βββββββββββββββ | |
| echo -e "${BOLD}${YELLOW}Run mode:${NC}" | |
| echo " (y) Auto β clean everything without prompts (NVM/Cursor keep latest only)" | |
| echo " (n) Interactive β confirm each section individually" | |
| echo "" | |
| read -rp " Auto-clean everything? (y/n): " RUN_MODE | |
| echo "" | |
| if [[ "$RUN_MODE" == "y" || "$RUN_MODE" == "yes" ]]; then | |
| AUTO_CONFIRM=true | |
| echo -e "${GREEN} Running in auto mode β cleaning all sections...${NC}" | |
| else | |
| AUTO_CONFIRM=false | |
| echo -e "${CYAN} Running in interactive mode β you'll be asked per section.${NC}" | |
| fi | |
| echo "" | |
| cleanup_terraform | |
| cleanup_playwright | |
| cleanup_npm | |
| cleanup_nvm | |
| cleanup_zcompdump | |
| cleanup_cursor_server | |
| cleanup_vscode_server | |
| cleanup_virtualenv | |
| cleanup_cache | |
| cleanup_mozilla | |
| cleanup_claude_sessions | |
| cleanup_claude_cache | |
| cleanup_apt | |
| cleanup_journal | |
| cleanup_tmp | |
| cleanup_logs | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| # SUMMARY | |
| # βββββββββββββββββββββββββββββββββββββββββ | |
| print_header "Cleanup Summary" | |
| echo -e " ${BOLD}${GREEN}Total space freed: $(bytes_to_human $TOTAL_FREED)${NC}" | |
| echo "" | |
| echo -e "${BOLD}Disk Usage After Cleanup:${NC}" | |
| df -h / | tail -1 | awk '{printf " Used: %s / %s (%s full)\n", $3, $2, $5}' | |
| echo "" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment