-
-
Save szymonrucinski/d7760198b5d0366d98994e3d0e4f7542 to your computer and use it in GitHub Desktop.
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
#!/usr/bin/env bash | |
# inspired by | |
# https://gist.github.com/JannikArndt/feb720c1f5d210b4820b880af23f2a07 | |
# which was inspired by | |
# https://github.com/fwartner/mac-cleanup/blob/master/cleanup.sh | |
# https://gist.github.com/jamesrampton/4503412 | |
# https://github.com/mengfeng/clean-my-mac/blob/master/clean_my_mac.sh | |
# https://github.com/szymonkaliski/Dotfiles/blob/master/Scripts/clean-my-mac | |
# http://brettterpstra.com/2015/10/27/vacuuming-mail-dot-app-on-el-capitan/ / https://github.com/pbihq/tools/blob/master/MailDBOptimiser.sh | |
# Function to format bytes to human-readable format | |
bytesToHuman() { | |
b=${1:-0}; d=''; s=0; 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)) | |
let s++ | |
done | |
echo "$b$d ${S[$s]}" | |
} | |
# Function to display disk usage in a nice formatted way | |
showDiskUsage() { | |
echo "==== Disk Space Information ====" | |
echo " Size Used Avail Use% Mount" | |
# Get the main disk usage (filtering out virtual filesystems) | |
df -h | grep -v "devfs\|map\|com.apple\|/dev/disk[1-9]" | grep "/dev/disk" | while read line; do | |
# Parse the line | |
local disk=$(echo "$line" | awk '{print $1}') | |
local size=$(echo "$line" | awk '{print $2}') | |
local used=$(echo "$line" | awk '{print $3}') | |
local avail=$(echo "$line" | awk '{print $4}') | |
local capacity=$(echo "$line" | awk '{print $5}') | |
local mount=$(echo "$line" | awk '{print $9}') | |
# Format for nicer display | |
if [[ "$mount" == "/" ]]; then | |
echo "Root Volume: $size $used $avail $capacity" | |
elif [[ "$mount" == "/System/Volumes/Data" ]]; then | |
echo "Data Volume: $size $used $avail $capacity" | |
else | |
echo "$mount: $size $used $avail $capacity" | |
fi | |
done | |
# Show total space for simulator volumes which can take a lot of space | |
local simSize=$(du -ch /Library/Developer/CoreSimulator/Volumes 2>/dev/null | grep total | cut -f1) | |
if [[ -n "$simSize" && "$simSize" != "0B" ]]; then | |
echo "iOS Simulators: $simSize total" | |
fi | |
# Show Docker usage if Docker is installed | |
if command -v docker &>/dev/null && docker info &>/dev/null; then | |
local dockerDiskUsage=$(docker system df --format "{{.Size}}" 2>/dev/null | head -1) | |
if [[ -n "$dockerDiskUsage" ]]; then | |
echo "Docker Usage: $dockerDiskUsage" | |
fi | |
fi | |
echo "==================================" | |
} | |
# Show initial disk usage | |
echo "BEFORE CLEANUP:" | |
showDiskUsage | |
# Check if Time Machine is running | |
if [ `tmutil status 2>/dev/null | grep -c "Running = 1"` -ne 0 ]; then | |
echo "Time Machine is currently running. Let it finish first!" | |
exit | |
fi | |
# Check for last Time Machine backup and exit if it's longer than 1 hour ago | |
lastBackupDateString=`tmutil latestbackup 2>/dev/null | grep -E -o "[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{6}"` | |
if [ "$lastBackupDateString" == "" ]; then | |
read -n 1 -p "$(tput setaf 3)Last Time Machine backup cannot be found. Proceed anyway?$(tput sgr0) (y/n) " RESP | |
echo "" | |
if [ "$RESP" != "y" ]; then | |
exit | |
fi | |
else | |
lastBackupDate=`date -j -f "%Y-%m-%d-%H%M%S" $lastBackupDateString "+%s"` | |
if [ $((`date +%s` - $lastBackupDate)) -gt 3600 ] | |
then | |
printf "Time Machine has not backed up since `date -j -f %s $lastBackupDate` (more than 60 minutes)!" | |
exit 1003 | |
else | |
echo "Last Time Machine backup was on `date -j -f %s $lastBackupDate`. " | |
fi | |
fi | |
# Ask for the administrator password upfront | |
if [ "$EUID" -ne 0 ]; then | |
echo "Please run as root" | |
exit | |
fi | |
# Record starting disk space | |
oldAvailable=$(df / | tail -1 | awk '{print $4}') | |
oldUsed=$(df / | tail -1 | awk '{print $3}') | |
totalSize=$(df / | tail -1 | awk '{print $2}') | |
# Get the data volume space (where most user data is stored) | |
dataVolumeAvailable=$(df /System/Volumes/Data 2>/dev/null | tail -1 | awk '{print $4}') | |
dataVolumeUsed=$(df /System/Volumes/Data 2>/dev/null | tail -1 | awk '{print $3}') | |
dataVolumeSize=$(df /System/Volumes/Data 2>/dev/null | tail -1 | awk '{print $2}') | |
echo "Starting cleanup operations..." | |
echo 'Empty the Trash on all mounted volumes and the main HDD...' | |
sudo rm -rfv /Volumes/*/.Trashes &>/dev/null | |
sudo rm -rfv ~/.Trash &>/dev/null | |
echo 'Clean temporary files...' | |
sudo -S rm -rfv /tmp/* | |
sudo -S rm -rfv /private/var/tmp/Processing/ | |
sudo -S rm -rfv /private/var/tmp/Xcode/ | |
sudo -S rm -rfv /private/var/tmp/tmp* | |
echo 'Clear Mail Downloads...' | |
sudo rm -rfv ~/Library/Containers/com.apple.mail/Data/Library/Mail\ Downloads/* &>/dev/null | |
echo 'Clear System Log Files...' | |
sudo -S rm -rfv /private/var/log/* &>/dev/null | |
sudo -S rm -rfv /Library/Logs/* &>/dev/null | |
sudo -S rm -rfv ~/Library/Logs/* &>/dev/null | |
sudo -S rm -rfv ~/Library/Application\ Support/Adobe/Common/Media\ Cache\ Files/* &>/dev/null | |
sudo rm -rfv /private/var/log/asl/*.asl &>/dev/null | |
rm -rfv ~/Library/Containers/com.apple.mail/Data/Library/Logs/Mail/* &>/dev/null | |
rm -rfv ~/Library/Logs/CoreSimulator/* &>/dev/null | |
echo 'Clean .DS_Store files in ~...' | |
sudo -S find ~ / -name ".DS_Store" -exec rm {} &>/dev/null | |
echo 'Vacuum Mail Envelope Index...' | |
sqlite3 ~/Library/Mail/V4/MailData/Envelope\ Index vacuum &>/dev/null | |
echo 'Clean System Caches...' | |
sudo -S rm -rf /Library/Caches/* | |
sudo -S rm -rf ~/Library/Caches/* | |
echo 'Clean Application Caches...' | |
for x in $(ls ~/Library/Containers/) | |
do | |
rm -rfv ~/Library/Containers/$x/Data/Library/Caches/* | |
done | |
echo 'Clear Adobe Cache Files...' | |
sudo rm -rfv ~/Library/Application\ Support/Adobe/Common/Media\ Cache\ Files/* &>/dev/null | |
echo 'Cleanup iOS Applications...' | |
rm -rfv ~/Music/iTunes/iTunes\ Media/Mobile\ Applications/* &>/dev/null | |
echo 'Remove iOS Device Backups...' | |
rm -rfv ~/Library/Application\ Support/MobileSync/Backup/* &>/dev/null | |
echo 'Performing comprehensive Xcode cleanup...' | |
# Standard Xcode cleanup | |
echo ' - Removing Derived Data...' | |
rm -rfv ~/Library/Developer/Xcode/DerivedData/* &>/dev/null | |
echo ' - Removing Archives...' | |
rm -rfv ~/Library/Developer/Xcode/Archives/* &>/dev/null | |
echo ' - Removing Module Caches...' | |
rm -rfv ~/Library/Developer/Xcode/iOS\ DeviceSupport/* &>/dev/null | |
rm -rfv ~/Library/Developer/Xcode/watchOS\ DeviceSupport/* &>/dev/null | |
rm -rfv ~/Library/Developer/Xcode/tvOS\ DeviceSupport/* &>/dev/null | |
echo ' - Removing Device Support Files...' | |
rm -rfv ~/Library/Developer/CoreSimulator/Caches/* &>/dev/null | |
echo ' - Removing Documentation Cache...' | |
rm -rfv ~/Library/Caches/com.apple.dt.Xcode/* &>/dev/null | |
echo ' - Removing Playgrounds Cache...' | |
rm -rfv ~/Library/Developer/XCPGDevices/* &>/dev/null | |
# Additional simulator cleanup | |
echo ' - Clearing Simulator Cache...' | |
rm -rfv ~/Library/Developer/CoreSimulator/Devices/*/data/Library/Caches/* &>/dev/null | |
rm -rfv ~/Library/Developer/CoreSimulator/Devices/*/data/Library/SplashBoard/Snapshots/* &>/dev/null | |
echo ' - Removing orphaned simulator device logs (this may take a moment)...' | |
find ~/Library/Logs/CoreSimulator -type f -name "*.log" -mtime +7 -delete &>/dev/null | |
echo 'Cleanup Homebrew Cache...' | |
brew cleanup --force -s &>/dev/null | |
# The command below is deprecated in newer versions of Homebrew | |
# Commenting it out to avoid errors | |
# brew cask cleanup &>/dev/null | |
rm -rfv /Library/Caches/Homebrew/* &>/dev/null | |
brew tap --repair &>/dev/null | |
echo 'Purge CoreSimulator...' | |
echo ' - Removing unavailable simulator runtimes...' | |
xcrun simctl delete unavailable &>/dev/null | |
echo ' - Would you like to delete all simulators? (y/n)' | |
read -n 1 -p "This will remove all simulator devices and you'll need to recreate them: " DELETE_SIMULATORS | |
echo "" | |
if [ "$DELETE_SIMULATORS" = "y" ]; then | |
echo ' - Removing all simulator devices...' | |
xcrun simctl erase all &>/dev/null | |
fi | |
echo 'Cleanup any old versions of gems...' | |
gem cleanup &>/dev/null | |
# Enhanced Docker cleanup section | |
echo '==== Starting comprehensive Docker cleanup ====' | |
echo 'Checking if Docker is installed and running...' | |
if command -v docker &>/dev/null; then | |
if ! docker info &>/dev/null; then | |
echo "Docker is installed but not running. Skipping Docker cleanup." | |
else | |
echo 'Removing dangling Docker containers...' | |
docker rm $(docker ps -q -f status=exited) &>/dev/null || echo "No exited containers to remove." | |
echo 'Removing all stopped containers...' | |
docker container prune -f &>/dev/null | |
echo 'Removing unused Docker volumes...' | |
docker volume prune -f &>/dev/null | |
echo 'Removing unused Docker networks...' | |
docker network prune -f &>/dev/null | |
echo 'Removing dangling Docker images...' | |
docker image prune -f &>/dev/null | |
echo 'Would you like to remove ALL unused Docker images? (y/n)' | |
read -n 1 -p "This will remove all images not used by containers (could be several GB): " REMOVE_IMAGES | |
echo "" | |
if [ "$REMOVE_IMAGES" = "y" ]; then | |
echo 'Removing ALL unused Docker images...' | |
docker image prune -a -f &>/dev/null | |
fi | |
echo 'Would you like to perform a full Docker system prune? (y/n)' | |
read -n 1 -p "This will remove all unused containers, networks, images and volumes: " FULL_PRUNE | |
echo "" | |
if [ "$FULL_PRUNE" = "y" ]; then | |
echo 'Performing full Docker system prune...' | |
docker system prune -a -f --volumes &>/dev/null | |
fi | |
echo 'Docker cleanup completed!' | |
fi | |
else | |
echo "Docker is not installed. Skipping Docker cleanup." | |
fi | |
echo '==== Docker cleanup finished ====' | |
echo 'Purge inactive memory...' | |
sudo purge | |
echo 'Rebuild Spotlight...' | |
sudo mdutil -E / | |
clear | |
echo "==========================" | |
echo "CLEANUP SUMMARY" | |
echo "==========================" | |
# Calculate freed space on root volume | |
newAvailable=$(df / | tail -1 | awk '{print $4}') | |
newUsed=$(df / | tail -1 | awk '{print $3}') | |
freedSpace=$((newAvailable-oldAvailable)) | |
freedSpaceBytes=$(( $freedSpace * 512)) # Convert blocks to bytes | |
freedSpaceHuman=$(bytesToHuman $freedSpaceBytes) | |
# Calculate freed space on data volume if it exists | |
if [[ -n "$dataVolumeAvailable" ]]; then | |
newDataVolumeAvailable=$(df /System/Volumes/Data | tail -1 | awk '{print $4}') | |
newDataVolumeUsed=$(df /System/Volumes/Data | tail -1 | awk '{print $3}') | |
dataFreedSpace=$((newDataVolumeAvailable-dataVolumeAvailable)) | |
dataFreedSpaceBytes=$(( $dataFreedSpace * 512)) # Convert blocks to bytes | |
dataFreedSpaceHuman=$(bytesToHuman $dataFreedSpaceBytes) | |
fi | |
# Display space freed | |
echo "Space freed on root volume: $freedSpaceHuman" | |
if [[ -n "$dataVolumeAvailable" ]]; then | |
echo "Space freed on data volume: $dataFreedSpaceHuman" | |
fi | |
# Calculate total space freed across both volumes | |
totalFreedSpaceBytes=$freedSpaceBytes | |
if [[ -n "$dataVolumeAvailable" ]]; then | |
# Only add data volume if it's separate from root (to avoid double counting) | |
if [[ "$dataVolumeSize" != "$totalSize" ]]; then | |
totalFreedSpaceBytes=$((freedSpaceBytes + dataFreedSpaceBytes)) | |
fi | |
fi | |
totalFreedSpaceHuman=$(bytesToHuman $totalFreedSpaceBytes) | |
echo "Total space freed: $totalFreedSpaceHuman" | |
echo "" | |
# Show final disk usage | |
echo "AFTER CLEANUP:" | |
showDiskUsage |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment