Thoroughly remove a macOS application and the files it leaves behind.
Dragging an app to the Trash removes the bundle and nothing else. Support directories, caches, preferences, saved state, login items, launch agents, and any background helper processes stay on disk — sometimes still running, and sometimes ready to restore themselves the moment the app is reinstalled.
botnuke.sh finds those, stops what's running, and moves everything into a
dated quarantine folder.
Nothing is ever deleted, and nothing is touched without --apply.
./botnuke.sh "Some App" # dry run — report only (default)
./botnuke.sh --apply "Some App" # quarantine for realThe name should match the bundle in /Applications without the .app suffix.
Quote it if it contains spaces.
| Flag | Effect |
|---|---|
--apply |
Actually move files. Without it, the script only reports. |
-y, --yes |
Skip the confirmation prompt. |
--extra-pattern RE |
Extra regex for helper processes named differently from the app. |
-h, --help |
Usage. |
--version |
Version. |
An Electron app that also runs a background daemon under an unrelated name:
./botnuke.sh --apply --extra-pattern 'helper-daemon' "Some App"- Lists matching processes, excluding itself and its own children.
- Reads the bundle ID, version, and code signature from
Info.plist. - Finds and unloads LaunchAgents / LaunchDaemons referencing the app.
- Removes login items.
- Asks the app to quit via AppleScript, then escalates to
TERM, thenKILL. - Quarantines the
.appbundle. - Quarantines support files across
~/Library:Application Support,Caches,Logs,Preferences,HTTPStorages,WebKit,Saved Application State,Containers,Group Containers,Application Scripts,CrashReporter,Cookies,Autosave Information— plus dotfile config dirs and anything keyed by bundle ID. - Exports and clears the app's
defaultsdomain (the export is kept). - Reports what it deliberately did not touch.
- Verifies, and tells you what's left.
Reported, never acted on:
- Files needing
sudo— printed as a command for you to review and run. - Keychain items — listed; remove them in Keychain Access.
- TCC privacy grants (Screen Recording, Accessibility, Input Monitoring, Full Disk Access) — these survive deletion and silently reapply if the app is ever reinstalled. Check System Settings → Privacy & Security.
- Server-side credentials. Deleting local files does not revoke an API token or session. If the app held one, revoke it in the vendor's account settings.
- Near misses — files matching the app's first word but not its full name
are listed separately and left alone, so a stray
Somedirectory next toSome Appgets surfaced without being assumed.
- Dry run by default.
--applyis required to change anything. - Move, never delete. Everything lands in
~/botnuke-quarantine-<stamp>/, mirroring its original path, with areport.txt. Inspect it, restore anything you want back, delete the folder when satisfied. - Refuses
sudo. It resolves$HOME; running it elevated would either target the wrong home directory or leave root-owned files in yours. - Won't kill itself. Self-matching is a real hazard here — a script whose
own command line contains the search term will happily
pkillits own process group. Matching PIDs are filtered against$$,$PPID, the script name, and the quarantine path, and the quarantine directory is named to avoid containing the app slug. - Word-boundary matching.
"Some App"becomessome[ _-]?app, matchingSome App,SomeApp,some-app, andsome_app, but notsomething. - No silent failures. Portable
findonly — no-E/-iregex, which vary between BSD and GNU and return empty rather than erroring when unsupported. A file search that finds nothing because the syntax was rejected is worse than one that crashes.
macOS and bash. The script re-executes itself under /bin/bash on startup,
so it works even when invoked in a way that ignores the shebang.
Matching is name-based. An app whose support files are named nothing like the app — or an app with a very generic name — will need the dry run reviewed carefully before you apply. That is what the dry run is for.
Run it once more in dry-run mode after a reboot to confirm nothing came back.
MIT
botnuke at younever nu