#!/usr/bin/env bash #When a query returns a non-zero status, the -e flag stops the script. It also detects errors in the currently executing script. set -E trap cleanup SIGINT SIGTERM ERR EXIT cleanup() { trap - SIGINT SIGTERM ERR EXIT } #italic="\033[3m" #underline="\033[4m" #ita_under="\033[3;4m" #bgd="\033[1;4;31m" #red="\033[1;31m" #bold="\033[1m" #box="\033[1;41m" #reset="\033[0m" opt_cache=true opt_log=false opt_adobe=false opt_chrome=false # opt_ios_app=false opt_ios_device=false opt_xcode=false opt_xcrun=true opt_brew=false opt_gem=false # ok opt_npm=false # ok opt_docker=true opt_go=false # ok opt_teams=false opt_dns=true opt_memory=true opt_periodic=true usage() { cat <&2 -e "${1-}" #fi } die() { local msg=$1 local code=${2-1} # default exit status 1 msg "$msg" exit "$code" } parse_params() { # default values of variables set from params update=false while :; do case "${1-}" in -h | --help) usage ;; #-v | --verbose) set -x ;; -d | --dry-run) dry_run=true ;; --no-color) NO_COLOR=1 ;; #-u | --update) update=true ;; # update flag -n) true ;; # This is a legacy option, now default behaviour -?*) die "Unknown option: $1" ;; *) break ;; esac shift done return 0 } parse_params "$@" setup_colors # [ -z EMPTYSTRING ] # [ -n NONEMPTYSTRING ] if [ -n "$dry_run" ]; then msg "${RED}Running dry run mode !\n${NOFORMAT}" else echo fi _bytesToHuman() { b=$1 echo $b } bytesToHuman() { b=${1:-0} d='' s=1 S=(Bytes {K,M,G,T,E,P,Y,Z}iB) while ((b > 1024)); do d="$(printf ".%02d" $((b % 1024 * 100 / 1024)))" b=$((b / 1024)) ((s++)) done human_size="$b$d ${S[$s]}" echo $human_size } count_dry() { #echo "count_dry" #local dry_results #local temp_dry_results for path in "${path_list[@]}"; do if [ -d "$path" ] || [ -f "$path" ]; then #echo "$path" temp_dry_results=$(sudo du -ck "$path" | tail -1 | awk '{ print $1 }') #echo "$path" "$temp_dry_results" dry_results="$((dry_results+temp_dry_results))" fi done echo $dry_results } remove_paths() { if [ -z "$dry_run" ]; then echo -e -n "Clean up $(bytesToHuman $count_section) ? (y/n) " read -r -s -n1 clean_dry_run if [[ $clean_dry_run = "y" ]]; then for path in "${path_list[@]}"; do msg "Remove $path" #rm -rfv "$path" &>/dev/null done totalFree="$((totalFree+count_section))" fi fi unset path_list } collect_paths() { path_list+=("$@") } # Ask for the administrator password upfront sudo -v HOST=$(whoami) # Keep-alive sudo until `mac-cleanup.sh` has finished while true; do sudo -n true sleep 60 kill -0 "$$" || exit done 2>/dev/null & # Enable extended regex shopt -s extglob # Space cleaned counter totalFree=0 ### Display disk free size ### msg "${BOLD}Free space on HDD...${NOFORMAT}\n" oldAvailable=$(df / | tail -1 | awk '{print $4}') msg "${BLUE}$(bytesToHuman $oldAvailable) available on /${NOFORMAT}" echo ### Empty Trashes ### msg "${BOLD}Emptying the Trash 🗑 on all mounted volumes and the main HDD...${NOFORMAT}" collect_paths /Volumes/*/.Trashes/* collect_paths ~/.Trash/* count_section=$(count_dry) msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space will be cleaned up${NOFORMAT}" remove_paths echo "count_section $count_section" echo "totalFree $totalFree" ### Empty System Cache Files ### if [[ $opt_cache = true ]]; then msg "\n${BOLD}Clearing System Cache Files...${NOFORMAT}" collect_paths /Library/Caches/* collect_paths /System/Library/Caches/* collect_paths ~/Library/Caches/* collect_paths /private/var/folders/bh/*/*/*/* count_section=$(count_dry) echo -e "${RED}System cache: $count_section${NOFORMAT}" #totalFree="$((totalFree+count_section))" if [ -z "$dry_run" ]; then msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space will be cleaned up${NOFORMAT}\n" remove_paths else msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space will be cleaned up${NOFORMAT}\n" unset path_list totalFree="$((totalFree+count_section))" fi echo "totalFree $totalFree" fi ### Empty System Log Files ### if [[ $opt_log = true ]]; then msg "\n${BOLD}Clearing System Log Files...${NOFORMAT}" collect_paths /private/var/log/asl/*.asl collect_paths /Library/Logs/DiagnosticReports/* collect_paths /Library/Logs/CreativeCloud/* collect_paths /Library/Logs/Adobe/* collect_paths /Library/Logs/adobegc.log collect_paths ~/Library/Containers/com.apple.mail/Data/Library/Logs/Mail/* collect_paths ~/Library/Logs/CoreSimulator/* count_section=$(count_dry) echo -e "${RED}System log: $count_section${NOFORMAT}" #totalFree="$((totalFree+count_section))" if [ -z "$dry_run" ]; then msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space will be cleaned up${NOFORMAT}\n" remove_paths else msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space will be cleaned up${NOFORMAT}\n" unset path_list totalFree="$((totalFree+count_section))" fi echo "totalFree $totalFree" fi ### Adobe Cache ### if [[ $opt_adobe = true ]]; then if [ -d ~/Library/Application\ Support/Adobe/ ]; then msg "\n${BOLD}Clearing Adobe Cache Files...${NOFORMAT}" collect_paths ~/Library/Application\ Support/Adobe/Common/Media\ Cache\ Files/* count_section=$(count_dry) echo -e "${RED}Adobe: $count_section${NOFORMAT}" if [ -z "$dry_run" ]; then msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space will be cleaned up${NOFORMAT}" remove_paths else msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space will be cleaned up${NOFORMAT}\n" unset path_list totalFree="$((totalFree+count_section))" fi fi echo "totalFree $totalFree" fi ### Google Chrome Cache ### if [[ $opt_chrome = true ]]; then if [ -d ~/Library/Application\ Support/Google/Chrome/ ]; then msg "\n${BOLD}Clearing Google Chrome Cache Files...${NOFORMAT}" collect_paths ~/Library/Application\ Support/Google/Chrome/Default/Application\ Cache/* count_section=$(count_dry) echo -e "${RED}Chrome: $count_section${NOFORMAT}" if [ -z "$dry_run" ]; then msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space will be cleaned up${NOFORMAT}" remove_paths else msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space will be cleaned up${NOFORMAT}\n" unset path_list totalFree="$((totalFree+count_section))" fi fi fi ### iOS Applications ### if [[ $opt_ios_app = true ]]; then if [ -d ~/Music/iTunes/ ]; then msg "\n${BOLD}Cleaning up iOS Applications...${NOFORMAT}" collect_paths ~/Music/iTunes/iTunes\ Media/Mobile\ Applications/* count_section=$(count_dry) echo -e "${RED}iOS app: $count_section${NOFORMAT}" if [ -z "$dry_run" ]; then msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space will be cleaned up${NOFORMAT}" remove_paths else msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space will be cleaned up${NOFORMAT}\n" unset path_list totalFree="$((totalFree+count_section))" fi fi echo "totalFree $totalFree" fi ### iOS Device Backups ### if [[ $opt_ios_device = true ]]; then msg "\n${BOLD}Removing iOS Device Backups...${NOFORMAT}" collect_paths ~/Library/Application\ Support/MobileSync/Backup/* count_section=$(count_dry) echo -e "${RED}iOS device: $count_section${NOFORMAT}" if [ -z "$dry_run" ]; then msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space will be cleaned up${NOFORMAT}" remove_paths else msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space will be cleaned up${NOFORMAT}\n" unset path_list totalFree="$((totalFree+count_section))" fi echo "totalFree $totalFree" fi ### Xcode ### if [[ $opt_xcode = true ]]; then if [ -d ~/Library/Developer/Xcode/ ]; then msg "\n${BOLD}Cleaning up XCode Derived Data and Archives...${NOFORMAT}" collect_paths ~/Library/Developer/Xcode/DerivedData/* collect_paths ~/Library/Developer/Xcode/Archives/* collect_paths ~/Library/Developer/Xcode/iOS Device Logs/* count_section=$(count_dry) echo -e "${RED}xcode: $count_section${NOFORMAT}" if [ -z "$dry_run" ]; then msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space will be cleaned up${NOFORMAT}" remove_paths else msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space will be cleaned up${NOFORMAT}\n" unset path_list totalFree="$((totalFree+count_section))" fi fi echo "totalFree $totalFree" fi # Xcrun if [[ $opt_xcrun = true ]]; then if type "xcrun" &>/dev/null; then msg "\n${BOLD}Cleaning up iOS Simulators...${NOFORMAT}\n" if [ -z "$dry_run" ]; then # List available devices, device types, runtimes, or device pairs. # xcrun simctl list # Shutdown a device. ( | all) # Specifying all will shut down all running devices # xcrun simctl shutdown all &>/dev/null # Erase a device's contents and settings. ([... ] | all) # Specifying all will erase all existing devices. # xcrun simctl erase all &>/dev/null echo -e "Running ${ITALIC}xcrun simctl delete unavailable${NOFORMAT}" # Delete specified devices, unavailable devices, or all devices. ([... ] | unavailable | all) # Specifying unavailable will delete devices that are not supported by the current Xcode SDK. # xcrun simctl delete unavailable else collect_paths ~/Library/Developer/CoreSimulator/Devices/*/data/!(Library|var|tmp|Media) collect_paths /Users/wah/Library/Developer/CoreSimulator/Devices/*/data/Library/!(PreferencesCaches|Caches|AddressBook) collect_paths ~/Library/Developer/CoreSimulator/Devices/*/data/Library/Caches/* collect_paths ~/Library/Developer/CoreSimulator/Devices/*/data/Library/AddressBook/AddressBook* count_section=$(count_dry) [ -z $count_section ] && count_section=0 echo -e "${RED}xcrcun: $count_section${NOFORMAT}" unset path_list totalFree="$((totalFree+count_section))" fi fi fi ### Brew ### if [[ $opt_brew = true ]]; then if type "brew" &>/dev/null; then msg "\n${BOLD}Cleaning up brew cache...${NOFORMAT}\n" collect_paths "$(brew --cache)" if [ -z "$dry_run" ]; then # Remove stale lock files and outdated downloads for all formulae and casks, and remove old versions of installed formulae. # If arguments are specified, only do this for the given formulae and casks. Removes all downloads more than 120 days old. # This can be adjusted with HOMEBREW_CLEANUP_MAX_AGE_DAYS. # -s: Scrub the cache, including downloads for even the latest versions. Note that downloads for any installed formulae or # casks will still not be deleted. If you want to delete those too: rm -rf "$(brew --cache)" echo -e "Running ${ITALIC}brew cleanup -s${NOFORMAT}" # brew cleanup -s &>/dev/null free=$(brew cleanup -s --dry-run | grep -o -E " [0-9]{1,3}(B|KB|MB|GB) ") msg "${BLUE}\nApprox $free of space has been cleaned up${NOFORMAT}\n" echo -e "Running ${ITALIC}brew autoremove${NOFORMAT}" # Uninstall formulae that were only installed as a dependency of another formula and are now no longer needed. brew autoremove --dry-run # Migrate tapped formulae from symlink-based to directory-based structure. # brew tap --repair &>/dev/null else echo -e "Running ${ITALIC}brew cleanup -s --dry-run${NOFORMAT}" free=$(brew cleanup -s --dry-run | grep -o -E " [0-9]{1,3}(B|KB|MB|GB) ") msg "${BLUE}\nApprox $free of space will be cleaned up${NOFORMAT}\n" echo -e "Running ${ITALIC}brew autoremove --dry-run${NOFORMAT}" brew autoremove --dry-run # totalFree="$((totalFree+count_section))" # free fi unset path_list fi fi ### gem ### # The cleanup command removes old versions of gems from GEM_HOME that are not required to meet a dependency. # If a gem is installed elsewhere in GEM_PATH the cleanup command won't delete it. # If no gems are named all gems in GEM_HOME are cleaned. if [[ $opt_gem = true ]]; then if type "gem" &>/dev/null; then # TODO add count_dry msg "\n${BOLD}Cleaning up any old versions of gems...${NOFORMAT}" if [ -z "$dry_run" ]; then echo -e "Running ${ITALIC}gem cleanup${NOFORMAT}" #gem cleanup &>/dev/null else echo -e "Running ${ITALIC}gem cleanup -V --dry-run${NOFORMAT}" gem cleanup -V --dry-run fi fi fi ### npm ### # prune: This command removes "extraneous" packages. If a package name is provided, then only packages matching one of the supplied names are removed. # Extraneous packages are those present in the node_modules folder that are not listed as any package's dependency list. # clean: Delete all data out of the cache folder. Note that this is typically unnecessary, as npm's cache is self-healing and resistant to data corruption issues. if [[ $opt_npm = true ]]; then if type "npm" &>/dev/null; then msg "\n${BOLD}Cleaning up npm cache...${NOFORMAT}" collect_paths ~/.npm/_cacache/* count_section=$(count_dry) echo -e "${RED}npm: $count_section${NOFORMAT}" if [ -z "$dry_run" ]; then echo -e "Running ${ITALIC}npm cache clean --force${NOFORMAT}" #npm cache clean --force &>/dev/null msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space has been cleaned up${NOFORMAT}." echo -e "Running ${ITALIC}npm prune${NOFORMAT}" npm_prune=$(npm prune) [[ ! "$npm_prune" =~ "up to date in" ]] && echo "$npm_prune" else # npm cache clean --dry-run doesn't exist msg "${BLUE}Approx $(bytesToHuman $count_section) of space will be cleaned up by running ${ITALIC}npm cache clean --force${NOFORMAT}" echo -e "Running ${ITALIC}npm prune --dry-run${NOFORMAT}" npm_prune=$(npm prune --dry-run) [[ ! "$npm_prune" =~ "up to date in" ]] && echo "$npm_prune" fi unset path_list totalFree="$((totalFree+count_section))" echo -e "${RED}TOTAL: $totalFree${NOFORMAT}" fi fi ### Docker ### #docker system prune: # remove: # - all stopped containers # - all networks not used by at least one container # - all dangling images # - all dangling build cache # # -a Remove # - all stopped containers # - all networks not used by at least one container # - all images without at least one container associated to them # - all build cache # -f Do not prompt for confirmation #docker volume prune: # remove anonymous local volumes not used by at least one container. # -a remove all local volumes not used by at least one container. # -f Do not prompt for confirmation # docker container prune # remove all stopped containers. # -f Do not prompt for confirmation # ~/Library/Containers/com.docker.docker/Data/vms/0/data/Docker.raw => 60Go # https://docs.docker.com/desktop/faqs/macfaqs/ if [[ $opt_docker = true ]]; then if type "docker" &>/dev/null; then msg "\n${BOLD}Cleaning Docker...${NOFORMAT}" vm_size=$(($(/usr/bin/stat -f%z ~/Library/Containers/com.docker.docker/Data/vms/0/data/Docker.raw) / 1000)) # 1000->38.14 GiB; 1024->37.25 GiB # 40 000 028 672 octets (2,53 Go sur disque) msg "\nOn macOS, Docker Desktop stores Linux containers and images in a single, large disk image file in the Mac filesystem (located à ~/Library/Containers/com.docker.docker/Data/vms/0/data/Docker.raw)(size=$(bytesToHuman $vm_size))." msg "\nhttps://docs.docker.com/desktop/faqs/macfaqs/" msg "\n${RED}Delete ou prune some images and container will free up space on this large disk image, not on the Mac filesystem.${NOFORMAT}" if [ -z "$dry_run" ]; then if ! docker ps >/dev/null 2>&1; then close_docker=true echo "Docker closed" open --background -a Docker fi msg '\nCleaning up Docker' echo -e "Running ${ITALIC}docker system prune -af${NOFORMAT}" docker_prune=$(docker system prune -af) # &>/dev/null docker_free=$(echo "$docker_prune" | grep 'Total reclaimed space' | awk -F"=" '{print $2}' | xargs) msg "${BLUE}\nApprox $docker_free of space has been cleaned up${NOFORMAT}." if [ "$close_docker" = true ]; then killall Docker fi fi fi fi # Pyenv # yarn # pnpm # pod ### Cargo ### # https://blog.rust-lang.org/2023/12/11/cargo-cache-cleaning.html # https://github.com/matthiaskrgr/cargo-cache # cargo cache --autoclean # cargo cache --dry-run # Size changed 842.71 MB => 660.87 MB (-181.84 MB, -21.57%) # Remove artifacts from the target directory that Cargo has generated in the past. # With no options, cargo clean will delete the entire target directory. # When no packages are selected, all packages and all dependencies in the workspace are cleaned. # cargo clean --dry-run # cargo clean gc # Remove one or more dependencies from a Cargo.toml manifest. # cargo remove --dry-run ### go ### # ~/Library/Caches/go-build # 1 This directory holds cached build artifacts from the Go build system. # 2 Run "go clean -cache" if the directory is getting too large. The -cache flag causes clean to remove the entire go build cache. # 3 Run "go clean -fuzzcache" to delete the fuzz cache. The -fuzzcache flag causes clean to remove files stored in the Go build # cache for fuzz testing. The fuzzing engine caches files that expand code coverage, so removing them may make fuzzing less effective until # new inputs are found that provide the same coverage. These files are distinct from those stored in testdata directory; clean does not remove # those files. # 4 The -modcache flag causes clean to remove the entire module download cache, including unpacked source code of versioned dependencies. # GOCACHE='/Users/bruno/Library/Caches/go-build' # GOMODCACHE='/Users/bruno/go/pkg/mod' # /Users/bruno/go/pkg/mod/cache if [[ $opt_go = true ]]; then if type "go" &>/dev/null; then msg "\n${BOLD}Clearing Go module cache...${NOFORMAT}" go_env=$(go env) go_cache=$(echo "$go_env" | grep GOCACHE | awk -F"=" '{print $2}' | tr -d "'") collect_paths "$go_cache" if [ -n "$GOPATH" ]; then collect_paths "$GOPATH/pkg/mod" else go_modcache=$(echo "$go_env" | grep GOMODCACHE | awk -F"=" '{print $2}' | tr -d "'") collect_paths "$go_modcache" fi count_section=$(count_dry) echo -e "${RED}go: $count_section{NOFORMAT}" if [ -z "$dry_run" ]; then echo -e "Remove the entire module download cache. Running ${ITALIC}go clean -modcache${NOFORMAT}" go clean -modcache &>/dev/null echo -e "Remove the entire go build cache. Running ${ITALIC}go clean -cache${NOFORMAT}" go clean -cache &>/dev/null msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space has been cleaned up${NOFORMAT}" else msg "\n${BLUE}Approx $(bytesToHuman $count_section) of space will be cleaned up by running ${ITALIC}go clean -modcache${NOFORMAT}${BLUE} and ${ITALIC}go clean -cache${NOFORMAT}" fi unset path_list totalFree="$((totalFree+count_section))" echo -e "${RED}TOTAL: $totalFree{NOFORMAT}" fi fi ### Deletes all Microsoft Teams Caches and resets it to default - can fix also some performance issues ### # -Astro if [[ $opt_teams = true ]]; then if [ -d ~/Library/Application\ Support/Microsoft/Teams ]; then msg "\n${BOLD}Deleting Microsoft Teams logs and caches...${NOFORMAT}" #collect_paths ~/Library/Application\ Support/Microsoft/Teams/IndexedDB #collect_paths ~/Library/Application\ Support/Microsoft/Teams/Cache #collect_paths ~/Library/Application\ Support/Microsoft/Teams/Application\ Cache #collect_paths ~/Library/Application\ Support/Microsoft/Teams/Code\ Cache #collect_paths ~/Library/Application\ Support/Microsoft/Teams/blob_storage #collect_paths ~/Library/Application\ Support/Microsoft/Teams/databases #collect_paths ~/Library/Application\ Support/Microsoft/Teams/gpucache #collect_paths ~/Library/Application\ Support/Microsoft/Teams/Local\ Storage #collect_paths ~/Library/Application\ Support/Microsoft/Teams/tmp #collect_paths ~/Library/Application\ Support/Microsoft/Teams/*logs*.txt #collect_paths ~/Library/Application\ Support/Microsoft/Teams/watchdog #collect_paths ~/Library/Application\ Support/Microsoft/Teams/*watchdog*.json collect_paths ~/Library/Group\ Containers/UBF8T346G9.com.microsoft.teams/* # 776 collect_paths ~/Library/Containers/com.microsoft.teams2/* # 26872 # ~/Library/Containers/com.microsoft.teams2.launcher/ # ~/Library/Containers/com.microsoft.teams2.notificationcenter/ collect_paths ~/Library/Application\ Support/Microsoft/Teams/* #977048 [ -d ~/Library/Logs/Microsoft\ Teams/ ] && collect_paths ~/Library/Logs/Microsoft\ Teams/* # / [ -d ~/Library/Logs/Microsoft\ Teams\ Helper/ ] && collect_paths ~/Library/Logs/Microsoft\ Teams\ Helper/* # / [ -d ~/Library/Logs/Microsoft\ Teams\ Helper\ \(Renderer\)/ ] && collect_paths ~/Library/Logs/Microsoft\ Teams\ Helper\ \(Renderer\)/* count_section=$(count_dry) echo -e "${RED}teams: $count_section${NOFORMAT}" if [ -z "$dry_run" ]; then # QUIT Teams /usr/bin/pkill -9 'Teams' &>/dev/null msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space will be cleaned up${NOFORMAT}" remove_paths else msg "${BLUE}\nApprox $(bytesToHuman $count_section) of space will be cleaned up${NOFORMAT}" unset path_list totalFree="$((totalFree+count_section))" fi echo -e "${RED}TOTAL: $totalFree${NOFORMAT}" fi fi if [[ $opt_dns = true ]]; then if [ -z "$dry_run" ]; then msg "\n${BOLD}Cleaning up DNS cache...${NOFORMAT}" echo -e "Running ${ITALIC}sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder${NOFORMAT}" #sudo dscacheutil -flushcache &>/dev/null #sudo killall -HUP mDNSResponder &>/dev/null fi fi if [[ $opt_memory = true ]]; then if [ -z "$dry_run" ]; then msg "\n${BOLD}Purging inactive memory...${NOFORMAT}" echo -e "Running ${ITALIC}sudo purge${NOFORMAT}" # force disk cache to be purged (flushed and emptied) #sudo purge &>/dev/null fi fi if [[ $opt_periodic = true ]]; then if [ -z "$dry_run" ]; then msg "\n${BOLD}Running the maintenance scripts...${NOFORMAT}" echo -e "Running ${ITALIC}sudo periodic daily weekly monthly${NOFORMAT}" #sudo periodic daily weekly monthly fi fi # End msg "${GREEN}\nSuccess!${NOFORMAT}" if [ -z "$dry_run" ]; then msg "${BLUE}\nApprox $(bytesToHuman $totalFree) of space has been cleaned up${NOFORMAT}" else msg "${BLUE}\nApprox $(bytesToHuman $totalFree) of space will be cleaned up${NOFORMAT}" fi newAvailable=$(df / | tail -1 | awk '{print $4}') msg "${BLUE}$(bytesToHuman $newAvailable) available on /${NOFORMAT}"