# 🍎 macOS Launchd Services — 2 plist + 2 scripts: automated daily updates & system cleanup

> Source: <https://gist.github.com/EnesDemir143/f50a04edec0f2e0c552f56c4bfc1848e>
> Published: 2026-05-22 09:45:50+00:00

# 🍎 macOS Launchd Services — Automated Daily Updates & System Cleanup

> **macOS** (13+ Ventura, Apple Silicon & Intel) — 2 launchd plists and 2 shell scripts
> for keeping your dev machine up to date and clean, automatically.
>
> **Zero personal data.** All paths use `$HOME` or `PLACEHOLDER` variables.
> Download → tweak paths → load with launchd — done.
>
> _No system paths, no tokens, no credentials._

---

## 🎯 What This Does

| Component | What | When |
|-----------|------|------|
| `daily-update.sh` + plist | Updates **Homebrew, npm, uv, bun, rustup, pipx, conda, pip3, gh extensions, Poetry, AI CLIs (Claude Code, Copilot, Cursor Agent, HuggingFace...), Mole, Orbstack** and checks for macOS updates | Every day at 09:00 |
| `mo-extra` + plist | **Cleans caches** (brew, npm, bun, uv, pip, go, cargo, Docker) and removes **old ~/Downloads** files (30+ days). Built-in 72h cooldown to avoid redundant runs | Every hour (skips if within cooldown) |

**Result:** Your machine stays updated and lean — automatically, in the background.

---

## 📋 Files

| File | Type | Description |
|------|------|-------------|
| `com.user.daily-update.plist` | launchd plist | Runs `daily-update.sh` every morning at 09:00 |
| `daily-update.sh` | Bash script | 15-step update: brew, npm, uv, bun, rustup, pipx, conda, AI CLIs, macOS |
| `com.user.mo-extra.plist` | launchd plist | Triggers `mo-extra` every hour (cooldown managed in script) |
| `mo-extra` | Bash script | 11 cleanup operations: caches, Docker, Downloads |

---

## 🚀 Quick Start

```bash
# 1. Decide where to keep the scripts
mkdir -p ~/.local/bin

# 2. Copy scripts there
cp daily-update.sh ~/.local/bin/
cp mo-extra ~/.local/bin/
chmod +x ~/.local/bin/daily-update.sh ~/.local/bin/mo-extra

# 3. Option A — Run manually whenever you want
~/.local/bin/daily-update.sh
~/.local/bin/mo-extra

# 4. Option B — Automate with launchd
#    First, edit the plists: replace PROGRAM_PATH and LOG_PATH
#    (see "Customization" below)
cp com.user.daily-update.plist ~/Library/LaunchAgents/
cp com.user.mo-extra.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.user.daily-update.plist
launchctl load ~/Library/LaunchAgents/com.user.mo-extra.plist

# 5. Add aliases for on-demand use (optional)
echo 'alias daily-update="~/.local/bin/daily-update.sh"' >> ~/.zshrc
echo 'alias daily-clean="rm -f ~/.local/state/mo-extra/last-run && ~/.local/bin/mo-extra"' >> ~/.zshrc
```

---

## 🔧 Customization

### Plist Placeholders

| Placeholder | Where | Replace with |
|-------------|-------|-------------|
| `PROGRAM_PATH` | Both plists | Absolute path to your script (e.g., `/Users/jane/.local/bin/daily-update.sh`) |
| `LOG_PATH` | Both plists | Log file path (e.g., `/Users/jane/Library/Logs/daily-update.log`) |
| `ERROR_LOG_PATH` | mo-extra plist | Error log path (e.g., `/Users/jane/Library/Logs/mo-extra-err.log`) |
| `HOME_DIR` | mo-extra plist | Your home directory (e.g., `/Users/jane`) |

### Script Config Variables

Edit the top of each script to tune:

**daily-update.sh:**
- `LOG_FILE` — where logs go (default: `~/Library/Logs/daily-update.log`)
- **Sections are modular** — comment out any tool you don't use (conda, orb, mo, etc.)

**mo-extra:**
- `COOLDOWN_HOURS` — minimum hours between runs (default: 72)
- `DOWNLOADS_OLD_DAYS` — delete Downloads older than N days (default: 30)
- `LOG_DIR` — log directory (default: `~/Library/Logs`)

---

## 📝 Requirements

| Tool | Needs | Notes |
|------|-------|-------|
| **Scripts** | Bash 5+ | macOS ships Bash 3; install via `brew install bash` |
| **brew cleanup** | [Homebrew](https://brew.sh) | Most operations depend on it |
| **npm / bun / uv / go / cargo** | Optional | Sections skip automatically if missing |
| **Docker prune** | Docker or Orbstack | Skips if Docker isn't running |
| **mo clean** | [Mole](https://github.com/tw93/Mole) (`brew install mo`) + `sudo NOPASSWD` | Optional — comment out if unused |

---

## 🧹 Privacy

This gist is **fully generic**:
- No personal paths (`/Users/real-username/...`)
- No API keys, tokens, or passwords
- All user-specific values use `$HOME` or `PLACEHOLDER`
- Example usernames (`jane`, `you`) are clearly examples
- Must be configured before use

---

## 💡 Pro Tips

- **Test without launchd first:** Run the scripts manually to make sure they work
- **Check logs:** `cat ~/Library/Logs/daily-update.log`
- **Force cleanup early:** `rm -f ~/.local/state/mo-extra/last-run && mo-extra`
- **Customize tool list:** Open `daily-update.sh` and remove sections you don't need
- **Add your own tools:** Each section follows the same pattern — just copy/paste

---

> _Part of my dev workflow. macOS native launchd — zero extra dependencies._

