# Deep Clean MacBook

**Requirements:** macOS + built-in Terminal only, nothing to install — full walkthrough in [SETUP.md](SETUP.md)

A practical, step-by-step playbook for reviving a MacBook that has slowed to a
crawl — usually because the boot disk is nearly full and macOS has started
swapping memory to disk. This is the same diagnostic sequence used to find
and fix that: check disk pressure first, find the actual space hogs, clean
them in a safe order, then purge swap with a reboot.

This is a guide plus one read-only helper script (`cleanup.sh`). It does not
delete anything on its own unless you explicitly tell it to.

## Why this matters

A MacBook with less than roughly 10-15% free disk space will start
compressing and swapping memory aggressively, even if you have plenty of RAM.
Symptoms: the beachball on ordinary tasks, fans spinning at idle, Spotlight
indexing forever, apps taking seconds to switch. Most people reach for "buy
more RAM" or "reinstall macOS" when the real problem is a full disk causing
memory pressure. Free the disk, reboot to clear swap, and the machine
usually comes back to life without touching hardware or the OS install.

## Quickstart

```bash
git clone <this-repo-url>
cd deep-clean-macbook

# 1. See what's actually wrong (read-only, no changes made)
sh cleanup.sh --diagnose

# 2. See what WOULD be deleted, without deleting anything
sh cleanup.sh

# 3. Once you've reviewed the list, actually delete it
sh cleanup.sh --execute

# 4. Reboot to purge swap
sudo reboot
```

## Step 1 — Diagnose: is this actually a disk problem?

Before deleting anything, confirm the theory.

**Check free disk space:**

```bash
df -h /
```

Look at the `Capacity` / `Avail` columns for `/`. Below roughly 10-15% free
on the boot volume is the danger zone on modern macOS (APFS local
snapshots and system data make this worse than it looks on paper).

**Check memory pressure:**

```bash
memory_pressure
```

A `System-wide memory free percentage` well below 20%, or a status of
`WARN` / `CRITICAL`, means macOS is already stressed. Memory pressure that
stays high on a machine with 16GB+ RAM is a strong signal the disk is too
full for macOS to manage swap space and local snapshots properly, not that
you need more RAM.

**Check swap usage:**

```bash
vm_stat | grep -E "Pageouts|Swapins|Swapouts"
sysctl vm.swapusage
```

High `Pageouts` and a large `vm.swapusage` figure confirm the machine has
been swapping to disk. Swap only clears on reboot — quitting apps does not
release it.

**Rule of thumb:** if `df -h /` shows less than ~15% free AND
`memory_pressure` or `vm.swapusage` looks stressed, the fix below will help.
If disk space is fine and the machine is still slow, the bottleneck is
elsewhere (CPU-bound process, failing disk, thermal throttling) and this
guide will not fix it — check Activity Monitor's CPU and Energy tabs
instead.

## Step 2 — Find the space hogs

Don't guess. Measure. These are the usual suspects, roughly in order of how
often they turn out to be the culprit:

```bash
# Overall picture of top-level Library usage
du -sh ~/Library/* 2>/dev/null | sort -rh | head -20

# App sandbox containers — can silently grow to tens of GB
du -sh ~/Library/Containers/* 2>/dev/null | sort -rh | head -20

# Caches — almost always safe to clear, macOS rebuilds them
du -sh ~/Library/Caches/* 2>/dev/null | sort -rh | head -20

# Xcode: DerivedData, device support, old simulators — commonly 20-100GB+
du -sh ~/Library/Developer/Xcode/DerivedData 2>/dev/null
du -sh ~/Library/Developer/Xcode/iOS\ DeviceSupport/* 2>/dev/null
xcrun simctl list devices 2>/dev/null

# CoreSimulator caches
du -sh ~/Library/Developer/CoreSimulator/Caches/* 2>/dev/null | sort -rh

# Docker / colima images and volumes — these do not shrink on their own
docker system df 2>/dev/null
colima list 2>/dev/null
du -sh ~/.colima/* 2>/dev/null | sort -rh

# node_modules sweep — find every one, sorted by size, without walking each by hand
find ~ -name node_modules -type d -prune -exec du -sh {} \; 2>/dev/null | sort -rh | head -30

# Trash and Downloads — the obvious ones people forget to check
du -sh ~/.Trash 2>/dev/null
du -sh ~/Downloads 2>/dev/null
du -sh ~/Downloads/* 2>/dev/null | sort -rh | head -20

# System-level overview (needs Full Disk Access for Terminal to be accurate)
du -sh /private/var/vm 2>/dev/null
du -sh /private/var/folders 2>/dev/null
```

If you want a visual map instead of scrolling text, `Finder → About This
Mac → Storage → Manage` gives Apple's own breakdown and is a fine
cross-check.

## Step 3 — Safe deletion order

Delete in this order — safest and highest-yield first:

1. **Trash.** `rm -rf ~/.Trash/*` or just empty it from Finder. Free, no
   risk, no side effects.
2. **App and browser caches** (`~/Library/Caches/*`). Apps rebuild these on
   next launch. Never breaks anything, worst case is a slightly slower next
   launch.
3. **Xcode DerivedData** (`~/Library/Developer/Xcode/DerivedData`). Rebuilt
   automatically the next time you build a project in Xcode. Frequently the
   single biggest win on a developer machine.
4. **Old iOS DeviceSupport folders and unused simulators.** Keep the device
   support version matching your current Xcode; delete the rest.
   `xcrun simctl delete unavailable` clears simulators macOS itself has
   already marked as unavailable.
5. **Docker / colima images you don't currently need.**
   `docker system prune -a` (and `docker volume prune` if you know you don't
   need old volumes) reclaims space colima/Docker never gives back on its
   own. Stop colima first (`colima stop`) if you want to also shrink its
   underlying disk image.
6. **Stale `node_modules` directories** for projects you're not actively
   working on. They regenerate with `npm install` / `pnpm install` any time
   you come back to a project — never delete one for a project you have open
   right now.
7. **Downloads folder.** The one place most people never clean. Old
   installers, duplicate zips, exported videos. This is manual triage, not
   something a script should touch automatically.
8. **App Containers** (`~/Library/Containers/*`) — only for apps you've
   actually uninstalled. Deleting a container for an app you still use wipes
   that app's local data/settings.

Do **not** delete anything under `/System`, `/Library` (system-level, not
`~/Library`), or files you don't recognize under `/private/var`. Those are
managed by macOS and Apple's own housekeeping (`clean_mach`, purgeable
space accounting) — touching them by hand is how people break their
installs.

## Step 4 — Purge swap

Clearing disk space does not immediately release swap that's already in
use — swap only clears on reboot:

```bash
sudo reboot
```

After reboot, re-run the checks from Step 1 to confirm `memory_pressure`
and `vm.swapusage` have dropped.

## Step 5 — Prevention

- **Keep at least 15% of your boot disk free**, always. That's roughly
  75GB free on a 500GB disk, or 150GB on a 1TB disk. Below that, macOS
  starts fighting itself for local snapshot and swap space.
- **Re-run `cleanup.sh --diagnose` monthly** or whenever things start to
  feel sluggish, before it becomes a full-blown slowdown.
- **Prune Docker/colima images periodically** — `docker system df` shows
  reclaimable space; these never shrink automatically.
- **Clear old Xcode DerivedData and simulators after major Xcode
  upgrades** — new versions leave the old ones behind by default.
- **Empty Trash and sweep Downloads on a schedule** rather than waiting
  for a crisis — a recurring calendar reminder works fine.

## The cleanup.sh script

`cleanup.sh` is intentionally conservative:

- **Default (no flags), or `--dry-run`:** prints what it would delete and
  the size it would reclaim. Deletes nothing.
- **`--diagnose`:** runs the Step 1 checks (disk, memory pressure, swap)
  and prints them. Deletes nothing.
- **`--execute`:** actually deletes. Requires this flag explicitly — there
  is no way to trigger real deletion by accident.
- **Scope is hard-limited** to known-safe, regenerable paths: user-level
  caches (`~/Library/Caches`), Xcode DerivedData, unavailable simulators,
  and `~/.Trash`. It will never touch `/System`, `/Library`, arbitrary
  `node_modules`, Docker volumes, or your Downloads folder — those need
  human judgment and are listed as commands above for you to run yourself.
- Every path it touches is checked against an allowlist of prefixes before
  any `rm` runs. If a path doesn't match, it's skipped and reported, not
  deleted.

Read the script before running it with `--execute`. It is under 150 lines
and worth reading once so you know exactly what it will do on your machine.

## Make it your own

- Add or remove paths from the `SAFE_PATHS` array in `cleanup.sh` to match
  your own workflow (for example, add a project-specific build cache
  directory you regenerate often).
- Turn Step 1's diagnostic checks into a LaunchAgent that runs weekly and
  sends you a notification when free disk space drops below your
  threshold, instead of waiting until the machine is already crawling.
- If you manage multiple Macs, wrap `cleanup.sh --diagnose` in a script you
  can run over SSH against each one to spot problems before they page you.

## Requirements

- macOS (uses `df`, `vm_stat`, `memory_pressure`, `sysctl` — all built in,
  nothing to install).
- `sh` / `bash` (built in).
- Optional: Docker/colima CLI if you want the Docker-specific commands to
  return output; Xcode command-line tools for the `xcrun simctl` commands.

## License

Use it, fork it, change it. No warranty — read `cleanup.sh` before running
it with `--execute`, same as you should with any script that deletes files.
