#!/usr/bin/env bash
set -u
PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${PATH:-}"
HOME_DIR="${HOME:-/Users/chouyasushi}"
MOBILE_DIR="${MOBILE_DIR:-$HOME_DIR/htdocs/david/from-fed-to-chain-mono/apps/mobile}"
ZAPENGINE_DIR="${ZAPENGINE_DIR:-$HOME_DIR/htdocs/zapPilot/zapEngine}"
MODE=""
DRY_RUN=0
YES=0
PRUNE_JS=0
PRUNE_PYTHON=0
SYSTEM_CORESIM_DYLD_CACHE="${SYSTEM_CORESIM_DYLD_CACHE:-/Library/Developer/CoreSimulator/Caches/dyld}"
usage() {
cat <<'USAGE'
Usage:
cleanup.sh [--dry-run]
cleanup.sh --light [--dry-run]
cleanup.sh --dev-cache --yes
cleanup.sh --dev-cache --dry-run
cleanup.sh --ios-deep --yes
cleanup.sh --ios-deep --dry-run
Modes:
--light Daily safe cleanup. This is the default when no mode is passed.
--dev-cache Conservative dev/app cache cleanup. Deletes rebuildable caches only,
and preserves Docker, Colima, Claude VM bundles, node_modules, and venvs.
Requires --yes unless --dry-run is set.
--ios-deep Explicit iOS cleanup. Deletes Simulator devices/caches, Xcode DerivedData,
system CoreSimulator dyld caches, and Flutter mobile build output.
Requires --yes unless --dry-run is set.
Options:
--dry-run Print actions without deleting files.
--yes Confirm destructive cleanup for --dev-cache or --ios-deep.
--prune-js Optional: prune pnpm store. Not used by daily cleanup.
--prune-python Compatibility flag; uv unreachable cache is pruned by daily cleanup.
--help Show this help.
Preserved by default:
- ~/.pnpm-store
- zapEngine node_modules
- zapEngine apps/analytics-engine/.venv
- Docker volumes, including local PostgreSQL volumes
- Colima VM disks
- Claude VM bundles
- Xcode Archives
- /Applications/Xcode.app
USAGE
}
log() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
}
warn() {
printf '[%s] WARN: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2
}
die() {
printf '[%s] ERROR: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2
exit 1
}
run_cmd() {
if [[ "$DRY_RUN" -eq 1 ]]; then
printf '[dry-run]'
printf ' %q' "$@"
printf '\n'
return 0
fi
"$@"
local status=$?
if [[ "$status" -ne 0 ]]; then
warn "command failed ($status): $*"
fi
return 0
}
delete_path() {
local path="$1"
if [[ ! -e "$path" && ! -L "$path" ]]; then
log "skip missing: $path"
return 0
fi
if [[ "$DRY_RUN" -eq 1 ]]; then
log "[dry-run] rm -rf -- $path"
return 0
fi
rm -rf -- "$path"
local status=$?
if [[ "$status" -eq 0 ]]; then
log "deleted: $path"
else
warn "failed to delete ($status): $path"
fi
}
delete_system_coresim_dyld_cache() {
local path="$SYSTEM_CORESIM_DYLD_CACHE"
case "$path" in
/Library/Developer/CoreSimulator/Caches/dyld|/System/Volumes/Data/Library/Developer/CoreSimulator/Caches/dyld)
;;
*)
die "refusing to delete non-allowlisted system CoreSimulator cache: $path"
;;
esac
if [[ ! -e "$path" && ! -L "$path" ]]; then
log "skip missing: $path"
return 0
fi
if [[ "$DRY_RUN" -eq 1 ]]; then
log "[dry-run] rm -rf -- $path"
return 0
fi
rm -rf -- "$path" 2>/dev/null
local status=$?
if [[ "$status" -eq 0 ]]; then
log "deleted: $path"
return 0
fi
warn "failed to delete without admin permissions ($status): $path"
if command -v sudo >/dev/null 2>&1; then
log "retrying with sudo; macOS may prompt for your password"
sudo rm -rf -- "$path"
status=$?
if [[ "$status" -eq 0 ]]; then
log "deleted with sudo: $path"
else
warn "failed to delete with sudo ($status): $path"
fi
else
warn "sudo is unavailable; leaving system CoreSimulator dyld cache in place"
fi
}
delete_old_files() {
local dir="$1"
local days="$2"
local label="$3"
if [[ ! -d "$dir" ]]; then
log "skip missing: $dir"
return 0
fi
log "cleaning $label older than ${days}d in $dir"
if [[ "$DRY_RUN" -eq 1 ]]; then
local count
count="$(find "$dir" -type f -mtime +"$days" -print 2>/dev/null | wc -l | tr -d ' ')"
log "[dry-run] would delete ${count:-0} $label file(s)"
return 0
fi
find "$dir" -type f -mtime +"$days" -delete 2>/dev/null || warn "some old files could not be deleted in $dir"
}
show_size() {
local path="$1"
if [[ -e "$path" || -L "$path" ]]; then
du -sh "$path" 2>/dev/null || true
else
printf 'missing\t%s\n' "$path"
fi
}
show_preserved_stack() {
log "preserving active development state by default"
show_size "$ZAPENGINE_DIR/node_modules"
show_size "$ZAPENGINE_DIR/apps/analytics-engine/.venv"
show_size "$HOME_DIR/.pnpm-store"
show_size "$HOME_DIR/.cache/uv"
show_size "$HOME_DIR/.cache"
show_size "$HOME_DIR/Library/Caches/Yarn"
show_size "$HOME_DIR/Library/Containers/com.docker.docker"
show_size "$HOME_DIR/.colima"
show_size "$HOME_DIR/Library/Application Support/Claude/vm_bundles"
}
docker_cleanup() {
if ! command -v docker >/dev/null 2>&1; then
log "skip missing command: docker"
return 0
fi
if ! docker info >/dev/null 2>&1; then
log "skip Docker cleanup; Docker daemon is not accessible"
return 0
fi
log "pruning Docker images, containers, networks, and build cache older than 7d"
run_cmd docker system prune -af --filter until=168h
run_cmd docker builder prune -af --filter until=168h
log "Docker volumes are intentionally preserved"
}
light_cleanup() {
log "=== Cleanup started: light ==="
if command -v brew >/dev/null 2>&1; then
run_cmd brew cleanup --prune=14
else
log "skip missing command: brew"
fi
if command -v npm >/dev/null 2>&1; then
run_cmd npm cache verify
delete_old_files "$HOME_DIR/.npm/_logs" 14 "npm logs"
else
log "skip missing command: npm"
fi
delete_old_files "$HOME_DIR/Library/Logs" 30 "user logs"
delete_path "$HOME_DIR/Library/Caches/com.apple.dt.Xcode"
delete_path "$HOME_DIR/Library/Caches/Yarn"
docker_cleanup
if [[ "$PRUNE_JS" -eq 1 ]]; then
if command -v pnpm >/dev/null 2>&1; then
run_cmd pnpm store prune
else
log "skip missing command: pnpm"
fi
else
log "skip pnpm store prune; pass --prune-js to run it explicitly"
fi
if command -v uv >/dev/null 2>&1; then
run_cmd uv cache prune
else
log "skip missing command: uv"
fi
show_preserved_stack
log "=== Cleanup finished: light ==="
}
conservative_dev_cache_cleanup() {
log "cleaning conservative rebuildable dev/app caches"
delete_path "$HOME_DIR/Library/Caches/ms-playwright"
delete_path "$HOME_DIR/Library/Caches/ms-playwright-go"
delete_path "$HOME_DIR/Library/Caches/typescript"
delete_path "$HOME_DIR/Library/Caches/pip"
delete_path "$HOME_DIR/Library/Caches/pypoetry"
delete_path "$HOME_DIR/Library/Caches/pnpm"
delete_path "$HOME_DIR/Library/Caches/node-gyp"
delete_path "$HOME_DIR/Library/Caches/Jedi"
delete_path "$HOME_DIR/Library/Caches/hardhat-nodejs"
delete_path "$HOME_DIR/Library/Caches/claude-cli-nodejs"
delete_path "$HOME_DIR/Library/Caches/Google"
delete_path "$HOME_DIR/Library/Caches/BraveSoftware"
delete_path "$HOME_DIR/Library/Caches/net.imput.helium"
delete_path "$HOME_DIR/Library/Caches/Trae"
delete_path "$HOME_DIR/Library/Caches/com.openai.atlas"
delete_path "$HOME_DIR/Library/Caches/com.spotify.client"
}
dev_cache_cleanup() {
if [[ "$DRY_RUN" -eq 0 && "$YES" -ne 1 ]]; then
die "--dev-cache is destructive and requires --yes, or use --dry-run first"
fi
log "=== Cleanup started: dev-cache ==="
log "disk before:"
df -h / /System/Volumes/Data 2>/dev/null || df -h /
conservative_dev_cache_cleanup
log "preserving Docker/Colima and Claude VM data"
show_preserved_stack
log "disk after:"
df -h / /System/Volumes/Data 2>/dev/null || df -h /
log "=== Cleanup finished: dev-cache ==="
}
ios_deep_cleanup() {
if [[ "$DRY_RUN" -eq 0 && "$YES" -ne 1 ]]; then
die "--ios-deep is destructive and requires --yes, or use --dry-run first"
fi
log "=== Cleanup started: ios-deep ==="
log "disk before:"
df -h / /System/Volumes/Data 2>/dev/null || df -h /
if command -v xcrun >/dev/null 2>&1; then
run_cmd xcrun simctl shutdown all
else
log "skip missing command: xcrun"
fi
delete_system_coresim_dyld_cache
delete_path "$HOME_DIR/Library/Developer/CoreSimulator/Caches"
delete_path "$HOME_DIR/Library/Developer/CoreSimulator/Devices"
delete_path "$HOME_DIR/Library/Developer/Xcode/DerivedData"
delete_path "$MOBILE_DIR/build"
delete_path "$MOBILE_DIR/.dart_tool/flutter_build"
log "preserving Xcode Archives: $HOME_DIR/Library/Developer/Xcode/Archives"
log "preserving Docker/Colima and Claude VM data"
log "disk after:"
df -h / /System/Volumes/Data 2>/dev/null || df -h /
log "=== Cleanup finished: ios-deep ==="
}
while [[ "$#" -gt 0 ]]; do
case "$1" in
--light)
MODE="light"
;;
--dev-cache)
MODE="dev-cache"
;;
--ios-deep)
MODE="ios-deep"
;;
--dry-run)
DRY_RUN=1
;;
--yes)
YES=1
;;
--prune-js)
PRUNE_JS=1
;;
--prune-python)
PRUNE_PYTHON=1
;;
--help|-h)
usage
exit 0
;;
*)
die "unknown argument: $1"
;;
esac
shift
done
if [[ -z "$MODE" ]]; then
MODE="light"
fi
case "$MODE" in
light)
light_cleanup
;;
dev-cache)
dev_cache_cleanup
;;
ios-deep)
ios_deep_cleanup
;;
*)
die "invalid mode: $MODE"
;;
esac
Last active
May 16, 2026 09:26
-
-
Save david30907d/d15546e89dd0a66a78db1bda2bf93328 to your computer and use it in GitHub Desktop.
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
cronjob: