#!/bin/bash # Mole - Application Protection # System critical and data-protected application lists set -euo pipefail if [[ -n "${MOLE_APP_PROTECTION_LOADED:-}" ]]; then return 0 fi readonly MOLE_APP_PROTECTION_LOADED=1 _MOLE_CORE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" [[ -z "${MOLE_BASE_LOADED:-}" ]] && source "$_MOLE_CORE_DIR/base.sh" # ============================================================================ # App Management Functions # ============================================================================ # System critical components that should NEVER be uninstalled readonly SYSTEM_CRITICAL_BUNDLES=( "com.apple.*" # System essentials "loginwindow" "dock" "systempreferences" "finder" "safari" "keychain*" "security*" "bluetooth*" "wifi*" "network*" "tcc" "notification*" "accessibility*" "universalaccess*" "HIToolbox*" "textinput*" "TextInput*" "keyboard*" "Keyboard*" "inputsource*" "InputSource*" "keylayout*" "KeyLayout*" "GlobalPreferences" ".GlobalPreferences" # Input methods (critical for international users) "com.tencent.inputmethod.QQInput" "com.sogou.inputmethod.*" "com.baidu.inputmethod.*" "com.apple.inputmethod.*" "com.googlecode.rimeime.*" "im.rime.*" "org.pqrs.Karabiner*" "*.inputmethod" "*.InputMethod" "*IME" "com.apple.inputsource*" "com.apple.TextInputMenuAgent" "com.apple.TextInputSwitcher" ) # Apps with important data/licenses - protect during cleanup but allow uninstall readonly DATA_PROTECTED_BUNDLES=( # ============================================================================ # System Utilities & Cleanup Tools # ============================================================================ "com.nektony.*" # App Cleaner & Uninstaller "com.macpaw.*" # CleanMyMac, CleanMaster "com.freemacsoft.AppCleaner" # AppCleaner "com.omnigroup.omnidisksweeper" # OmniDiskSweeper "com.daisydiskapp.*" # DaisyDisk "com.tunabellysoftware.*" # Disk Utility apps "com.grandperspectiv.*" # GrandPerspective "com.binaryfruit.*" # FusionCast # ============================================================================ # Password Managers & Security # ============================================================================ "com.1password.*" # 1Password "com.agilebits.*" # 1Password legacy "com.lastpass.*" # LastPass "com.dashlane.*" # Dashlane "com.bitwarden.*" # Bitwarden "com.keepassx.*" # KeePassXC "org.keepassx.*" # KeePassX "com.authy.*" # Authy "com.yubico.*" # YubiKey Manager # ============================================================================ # Development Tools - IDEs & Editors # ============================================================================ "com.jetbrains.*" # JetBrains IDEs (IntelliJ, DataGrip, etc.) "JetBrains*" # JetBrains Application Support folders "com.microsoft.VSCode" # Visual Studio Code "com.visualstudio.code.*" # VS Code variants "com.sublimetext.*" # Sublime Text "com.sublimehq.*" # Sublime Merge "com.microsoft.VSCodeInsiders" # VS Code Insiders "com.apple.dt.Xcode" # Xcode (keep settings) "com.coteditor.CotEditor" # CotEditor "com.macromates.TextMate" # TextMate "com.panic.Nova" # Nova "abnerworks.Typora" # Typora (Markdown editor) "com.uranusjr.macdown" # MacDown # ============================================================================ # Development Tools - Database Clients # ============================================================================ "com.sequelpro.*" # Sequel Pro "com.sequel-ace.*" # Sequel Ace "com.tinyapp.*" # TablePlus "com.dbeaver.*" # DBeaver "com.navicat.*" # Navicat "com.mongodb.compass" # MongoDB Compass "com.redis.RedisInsight" # Redis Insight "com.pgadmin.pgadmin4" # pgAdmin "com.eggerapps.Sequel-Pro" # Sequel Pro legacy "com.valentina-db.Valentina-Studio" # Valentina Studio "com.dbvis.DbVisualizer" # DbVisualizer # ============================================================================ # Development Tools - API & Network # ============================================================================ "com.postmanlabs.mac" # Postman "com.konghq.insomnia" # Insomnia "com.CharlesProxy.*" # Charles Proxy "com.proxyman.*" # Proxyman "com.getpaw.*" # Paw "com.luckymarmot.Paw" # Paw legacy "com.charlesproxy.charles" # Charles "com.telerik.Fiddler" # Fiddler "com.usebruno.app" # Bruno (API client) # Network Proxy & VPN Tools (protect all variants) "*clash*" # All Clash variants (ClashX, ClashX Pro, Clash Verge, etc) "*Clash*" # Capitalized variants "com.nssurge.surge-mac" # Surge "mihomo*" # Mihomo Party and variants "*openvpn*" # OpenVPN Connect and variants "*OpenVPN*" # OpenVPN capitalized variants "net.openvpn.*" # OpenVPN bundle IDs # ============================================================================ # Development Tools - Git & Version Control # ============================================================================ "com.github.GitHubDesktop" # GitHub Desktop "com.sublimemerge" # Sublime Merge "com.torusknot.SourceTreeNotMAS" # SourceTree "com.git-tower.Tower*" # Tower "com.gitfox.GitFox" # GitFox "com.github.Gitify" # Gitify "com.fork.Fork" # Fork "com.axosoft.gitkraken" # GitKraken # ============================================================================ # Development Tools - Terminal & Shell # ============================================================================ "com.googlecode.iterm2" # iTerm2 "net.kovidgoyal.kitty" # Kitty "io.alacritty" # Alacritty "com.github.wez.wezterm" # WezTerm "com.hyper.Hyper" # Hyper "com.mizage.divvy" # Divvy "com.fig.Fig" # Fig (terminal assistant) "dev.warp.Warp-Stable" # Warp "com.termius-dmg" # Termius (SSH client) # ============================================================================ # Development Tools - Docker & Virtualization # ============================================================================ "com.docker.docker" # Docker Desktop "com.getutm.UTM" # UTM "com.vmware.fusion" # VMware Fusion "com.parallels.desktop.*" # Parallels Desktop "org.virtualbox.app.VirtualBox" # VirtualBox "com.vagrant.*" # Vagrant "com.orbstack.OrbStack" # OrbStack # ============================================================================ # System Monitoring & Performance # ============================================================================ "com.bjango.istatmenus*" # iStat Menus "eu.exelban.Stats" # Stats "com.monitorcontrol.*" # MonitorControl "com.bresink.system-toolkit.*" # TinkerTool System "com.mediaatelier.MenuMeters" # MenuMeters "com.activity-indicator.app" # Activity Indicator "net.cindori.sensei" # Sensei # ============================================================================ # Window Management & Productivity # ============================================================================ "com.macitbetter.*" # BetterTouchTool, BetterSnapTool "com.hegenberg.*" # BetterTouchTool legacy "com.manytricks.*" # Moom, Witch, Name Mangler, Resolutionator "com.divisiblebyzero.*" # Spectacle "com.koingdev.*" # Koingg apps "com.if.Amphetamine" # Amphetamine "com.lwouis.alt-tab-macos" # AltTab "net.matthewpalmer.Vanilla" # Vanilla "com.lightheadsw.Caffeine" # Caffeine "com.contextual.Contexts" # Contexts "com.amethyst.Amethyst" # Amethyst "com.knollsoft.Rectangle" # Rectangle "com.knollsoft.Hookshot" # Hookshot "com.surteesstudios.Bartender" # Bartender "com.gaosun.eul" # eul (system monitor) "com.pointum.hazeover" # HazeOver # ============================================================================ # Launcher & Automation # ============================================================================ "com.runningwithcrayons.Alfred" # Alfred "com.raycast.macos" # Raycast "com.blacktree.Quicksilver" # Quicksilver "com.stairways.keyboardmaestro.*" # Keyboard Maestro "com.manytricks.Butler" # Butler "com.happenapps.Quitter" # Quitter "com.pilotmoon.scroll-reverser" # Scroll Reverser "org.pqrs.Karabiner-Elements" # Karabiner-Elements "com.apple.Automator" # Automator (system, but keep user workflows) # ============================================================================ # Note-Taking & Documentation # ============================================================================ "com.bear-writer.*" # Bear "com.typora.*" # Typora "com.ulyssesapp.*" # Ulysses "com.literatureandlatte.*" # Scrivener "com.dayoneapp.*" # Day One "notion.id" # Notion "md.obsidian" # Obsidian "com.logseq.logseq" # Logseq "com.evernote.Evernote" # Evernote "com.onenote.mac" # OneNote "com.omnigroup.OmniOutliner*" # OmniOutliner "net.shinyfrog.bear" # Bear legacy "com.goodnotes.GoodNotes" # GoodNotes "com.marginnote.MarginNote*" # MarginNote "com.roamresearch.*" # Roam Research "com.reflect.ReflectApp" # Reflect "com.inkdrop.*" # Inkdrop # ============================================================================ # Design & Creative Tools # ============================================================================ "com.adobe.*" # Adobe Creative Suite "com.bohemiancoding.*" # Sketch "com.figma.*" # Figma "com.framerx.*" # Framer "com.zeplin.*" # Zeplin "com.invisionapp.*" # InVision "com.principle.*" # Principle "com.pixelmatorteam.*" # Pixelmator "com.affinitydesigner.*" # Affinity Designer "com.affinityphoto.*" # Affinity Photo "com.affinitypublisher.*" # Affinity Publisher "com.linearity.curve" # Linearity Curve "com.canva.CanvaDesktop" # Canva "com.maxon.cinema4d" # Cinema 4D "com.autodesk.*" # Autodesk products "com.sketchup.*" # SketchUp # ============================================================================ # Communication & Collaboration # ============================================================================ "com.tencent.xinWeChat" # WeChat (Chinese users) "com.tencent.qq" # QQ "com.alibaba.DingTalkMac" # DingTalk "com.alibaba.AliLang.osx" # AliLang (retain login/config data) "com.alibaba.alilang3.osx.ShipIt" # AliLang updater component "com.alibaba.AlilangMgr.QueryNetworkInfo" # AliLang network helper "us.zoom.xos" # Zoom "com.microsoft.teams*" # Microsoft Teams "com.slack.Slack" # Slack "com.hnc.Discord" # Discord "org.telegram.desktop" # Telegram "ru.keepcoder.Telegram" # Telegram legacy "net.whatsapp.WhatsApp" # WhatsApp "com.skype.skype" # Skype "com.cisco.webexmeetings" # Webex "com.ringcentral.RingCentral" # RingCentral "com.readdle.smartemail-Mac" # Spark Email "com.airmail.*" # Airmail "com.postbox-inc.postbox" # Postbox "com.tinyspeck.slackmacgap" # Slack legacy # ============================================================================ # Task Management & Productivity # ============================================================================ "com.omnigroup.OmniFocus*" # OmniFocus "com.culturedcode.*" # Things "com.todoist.*" # Todoist "com.any.do.*" # Any.do "com.ticktick.*" # TickTick "com.microsoft.to-do" # Microsoft To Do "com.trello.trello" # Trello "com.asana.nativeapp" # Asana "com.clickup.*" # ClickUp "com.monday.desktop" # Monday.com "com.airtable.airtable" # Airtable "com.notion.id" # Notion (also note-taking) "com.linear.linear" # Linear # ============================================================================ # File Transfer & Sync # ============================================================================ "com.panic.transmit*" # Transmit (FTP/SFTP) "com.binarynights.ForkLift*" # ForkLift "com.noodlesoft.Hazel" # Hazel "com.cyberduck.Cyberduck" # Cyberduck "io.filezilla.FileZilla" # FileZilla "com.apple.Xcode.CloudDocuments" # Xcode Cloud Documents "com.synology.*" # Synology apps # ============================================================================ # Screenshot & Recording # ============================================================================ "com.cleanshot.*" # CleanShot X "com.xnipapp.xnip" # Xnip "com.reincubate.camo" # Camo "com.tunabellysoftware.ScreenFloat" # ScreenFloat "net.telestream.screenflow*" # ScreenFlow "com.techsmith.snagit*" # Snagit "com.techsmith.camtasia*" # Camtasia "com.obsidianapp.screenrecorder" # Screen Recorder "com.kap.Kap" # Kap "com.getkap.*" # Kap legacy "com.linebreak.CloudApp" # CloudApp "com.droplr.droplr-mac" # Droplr # ============================================================================ # Media & Entertainment # ============================================================================ "com.spotify.client" # Spotify "com.apple.Music" # Apple Music "com.apple.podcasts" # Apple Podcasts "com.apple.FinalCutPro" # Final Cut Pro "com.apple.Motion" # Motion "com.apple.Compressor" # Compressor "com.blackmagic-design.*" # DaVinci Resolve "com.colliderli.iina" # IINA "org.videolan.vlc" # VLC "io.mpv" # MPV "com.noodlesoft.Hazel" # Hazel (automation) "tv.plex.player.desktop" # Plex "com.netease.163music" # NetEase Music # ============================================================================ # License Management & App Stores # ============================================================================ "com.paddle.Paddle*" # Paddle (license management) "com.setapp.DesktopClient" # Setapp "com.devmate.*" # DevMate (license framework) "org.sparkle-project.Sparkle" # Sparkle (update framework) ) # Legacy function - preserved for backward compatibility # Use should_protect_from_uninstall() or should_protect_data() instead readonly PRESERVED_BUNDLE_PATTERNS=("${SYSTEM_CRITICAL_BUNDLES[@]}" "${DATA_PROTECTED_BUNDLES[@]}") # Check whether a bundle ID matches a pattern (supports globs) bundle_matches_pattern() { local bundle_id="$1" local pattern="$2" [[ -z "$pattern" ]] && return 1 # Use bash [[ ]] for glob pattern matching (works with variables in bash 3.2+) # shellcheck disable=SC2053 # allow glob pattern matching if [[ "$bundle_id" == $pattern ]]; then return 0 fi return 1 } # Check if app is a system component that should never be uninstalled should_protect_from_uninstall() { local bundle_id="$1" for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}"; do if bundle_matches_pattern "$bundle_id" "$pattern"; then return 0 fi done return 1 } # Check if app data should be protected during cleanup (but app can be uninstalled) should_protect_data() { local bundle_id="$1" # Protect both system critical and data protected bundles during cleanup for pattern in "${SYSTEM_CRITICAL_BUNDLES[@]}" "${DATA_PROTECTED_BUNDLES[@]}"; do if bundle_matches_pattern "$bundle_id" "$pattern"; then return 0 fi done return 1 } # Find and list app-related files (consolidated from duplicates) find_app_files() { local bundle_id="$1" local app_name="$2" local -a files_to_clean=() # ============================================================================ # User-level files (no sudo required) # ============================================================================ # Application Support [[ -d ~/Library/Application\ Support/"$app_name" ]] && files_to_clean+=("$HOME/Library/Application Support/$app_name") [[ -d ~/Library/Application\ Support/"$bundle_id" ]] && files_to_clean+=("$HOME/Library/Application Support/$bundle_id") # Caches [[ -d ~/Library/Caches/"$bundle_id" ]] && files_to_clean+=("$HOME/Library/Caches/$bundle_id") [[ -d ~/Library/Caches/"$app_name" ]] && files_to_clean+=("$HOME/Library/Caches/$app_name") # Preferences [[ -f ~/Library/Preferences/"$bundle_id".plist ]] && files_to_clean+=("$HOME/Library/Preferences/$bundle_id.plist") [[ -d ~/Library/Preferences/ByHost ]] && while IFS= read -r -d '' pref; do files_to_clean+=("$pref") done < <(find ~/Library/Preferences/ByHost \( -name "$bundle_id*.plist" \) -print0 2> /dev/null) # Logs [[ -d ~/Library/Logs/"$app_name" ]] && files_to_clean+=("$HOME/Library/Logs/$app_name") [[ -d ~/Library/Logs/"$bundle_id" ]] && files_to_clean+=("$HOME/Library/Logs/$bundle_id") # Saved Application State [[ -d ~/Library/Saved\ Application\ State/"$bundle_id".savedState ]] && files_to_clean+=("$HOME/Library/Saved Application State/$bundle_id.savedState") # Containers (sandboxed apps) [[ -d ~/Library/Containers/"$bundle_id" ]] && files_to_clean+=("$HOME/Library/Containers/$bundle_id") # Group Containers [[ -d ~/Library/Group\ Containers ]] && while IFS= read -r -d '' container; do files_to_clean+=("$container") done < <(find ~/Library/Group\ Containers -type d \( -name "*$bundle_id*" \) -print0 2> /dev/null) # WebKit data [[ -d ~/Library/WebKit/"$bundle_id" ]] && files_to_clean+=("$HOME/Library/WebKit/$bundle_id") [[ -d ~/Library/WebKit/com.apple.WebKit.WebContent/"$bundle_id" ]] && files_to_clean+=("$HOME/Library/WebKit/com.apple.WebKit.WebContent/$bundle_id") # HTTP Storage [[ -d ~/Library/HTTPStorages/"$bundle_id" ]] && files_to_clean+=("$HOME/Library/HTTPStorages/$bundle_id") # Cookies [[ -f ~/Library/Cookies/"$bundle_id".binarycookies ]] && files_to_clean+=("$HOME/Library/Cookies/$bundle_id.binarycookies") # Launch Agents (user-level) [[ -f ~/Library/LaunchAgents/"$bundle_id".plist ]] && files_to_clean+=("$HOME/Library/LaunchAgents/$bundle_id.plist") # Application Scripts [[ -d ~/Library/Application\ Scripts/"$bundle_id" ]] && files_to_clean+=("$HOME/Library/Application Scripts/$bundle_id") # Services [[ -d ~/Library/Services/"$app_name".workflow ]] && files_to_clean+=("$HOME/Library/Services/$app_name.workflow") # QuickLook Plugins [[ -d ~/Library/QuickLook/"$app_name".qlgenerator ]] && files_to_clean+=("$HOME/Library/QuickLook/$app_name.qlgenerator") # Preference Panes [[ -d ~/Library/PreferencePanes/"$app_name".prefPane ]] && files_to_clean+=("$HOME/Library/PreferencePanes/$app_name.prefPane") # Screen Savers [[ -d ~/Library/Screen\ Savers/"$app_name".saver ]] && files_to_clean+=("$HOME/Library/Screen Savers/$app_name.saver") # Frameworks [[ -d ~/Library/Frameworks/"$app_name".framework ]] && files_to_clean+=("$HOME/Library/Frameworks/$app_name.framework") # Autosave Information [[ -d ~/Library/Autosave\ Information/"$bundle_id" ]] && files_to_clean+=("$HOME/Library/Autosave Information/$bundle_id") # Contextual Menu Items [[ -d ~/Library/Contextual\ Menu\ Items/"$app_name".plugin ]] && files_to_clean+=("$HOME/Library/Contextual Menu Items/$app_name.plugin") # Spotlight Plugins [[ -d ~/Library/Spotlight/"$app_name".mdimporter ]] && files_to_clean+=("$HOME/Library/Spotlight/$app_name.mdimporter") # Color Pickers [[ -d ~/Library/ColorPickers/"$app_name".colorPicker ]] && files_to_clean+=("$HOME/Library/ColorPickers/$app_name.colorPicker") # Workflows [[ -d ~/Library/Workflows/"$app_name".workflow ]] && files_to_clean+=("$HOME/Library/Workflows/$app_name.workflow") # Unix-style configuration directories and files (cross-platform apps) [[ -d ~/.config/"$app_name" ]] && files_to_clean+=("$HOME/.config/$app_name") [[ -d ~/.local/share/"$app_name" ]] && files_to_clean+=("$HOME/.local/share/$app_name") [[ -d ~/."$app_name" ]] && files_to_clean+=("$HOME/.$app_name") [[ -f ~/."${app_name}rc" ]] && files_to_clean+=("$HOME/.${app_name}rc") # Only print if array has elements to avoid unbound variable error if [[ ${#files_to_clean[@]} -gt 0 ]]; then printf '%s\n' "${files_to_clean[@]}" fi } # Find system-level app files (requires sudo) find_app_system_files() { local bundle_id="$1" local app_name="$2" local -a system_files=() # System Application Support [[ -d /Library/Application\ Support/"$app_name" ]] && system_files+=("/Library/Application Support/$app_name") [[ -d /Library/Application\ Support/"$bundle_id" ]] && system_files+=("/Library/Application Support/$bundle_id") # System Launch Agents [[ -f /Library/LaunchAgents/"$bundle_id".plist ]] && system_files+=("/Library/LaunchAgents/$bundle_id.plist") # System Launch Daemons [[ -f /Library/LaunchDaemons/"$bundle_id".plist ]] && system_files+=("/Library/LaunchDaemons/$bundle_id.plist") # Privileged Helper Tools [[ -d /Library/PrivilegedHelperTools ]] && while IFS= read -r -d '' helper; do system_files+=("$helper") done < <(find /Library/PrivilegedHelperTools \( -name "$bundle_id*" \) -print0 2> /dev/null) # System Preferences [[ -f /Library/Preferences/"$bundle_id".plist ]] && system_files+=("/Library/Preferences/$bundle_id.plist") # Installation Receipts [[ -d /private/var/db/receipts ]] && while IFS= read -r -d '' receipt; do system_files+=("$receipt") done < <(find /private/var/db/receipts \( -name "*$bundle_id*" \) -print0 2> /dev/null) # System Logs [[ -d /Library/Logs/"$app_name" ]] && system_files+=("/Library/Logs/$app_name") [[ -d /Library/Logs/"$bundle_id" ]] && system_files+=("/Library/Logs/$bundle_id") # System Frameworks [[ -d /Library/Frameworks/"$app_name".framework ]] && system_files+=("/Library/Frameworks/$app_name.framework") # System QuickLook Plugins [[ -d /Library/QuickLook/"$app_name".qlgenerator ]] && system_files+=("/Library/QuickLook/$app_name.qlgenerator") # System Preference Panes [[ -d /Library/PreferencePanes/"$app_name".prefPane ]] && system_files+=("/Library/PreferencePanes/$app_name.prefPane") # System Screen Savers [[ -d /Library/Screen\ Savers/"$app_name".saver ]] && system_files+=("/Library/Screen Savers/$app_name.saver") # System Caches [[ -d /Library/Caches/"$bundle_id" ]] && system_files+=("/Library/Caches/$bundle_id") [[ -d /Library/Caches/"$app_name" ]] && system_files+=("/Library/Caches/$app_name") # Only print if array has elements if [[ ${#system_files[@]} -gt 0 ]]; then printf '%s\n' "${system_files[@]}" fi } # Force quit an application force_kill_app() { # Args: app_name [app_path]; tries graceful then force kill; returns 0 if stopped, 1 otherwise local app_name="$1" local app_path="${2:-""}" # Get the executable name from bundle if app_path is provided local exec_name="" if [[ -n "$app_path" && -e "$app_path/Contents/Info.plist" ]]; then exec_name=$(defaults read "$app_path/Contents/Info.plist" CFBundleExecutable 2> /dev/null || echo "") fi # Use executable name for precise matching, fallback to app name local match_pattern="${exec_name:-$app_name}" # Check if process is running using exact match only if ! pgrep -x "$match_pattern" > /dev/null 2>&1; then return 0 fi # Try graceful termination first pkill -x "$match_pattern" 2> /dev/null || true sleep 2 # Check again after graceful kill if ! pgrep -x "$match_pattern" > /dev/null 2>&1; then return 0 fi # Force kill if still running pkill -9 -x "$match_pattern" 2> /dev/null || true sleep 2 # If still running and sudo is available, try with sudo if pgrep -x "$match_pattern" > /dev/null 2>&1; then if sudo -n true 2> /dev/null; then sudo pkill -9 -x "$match_pattern" 2> /dev/null || true sleep 2 fi fi # Final check with longer timeout for stubborn processes local retries=3 while [[ $retries -gt 0 ]]; do if ! pgrep -x "$match_pattern" > /dev/null 2>&1; then return 0 fi sleep 1 ((retries--)) done # Still running after all attempts pgrep -x "$match_pattern" > /dev/null 2>&1 && return 1 || return 0 } # Calculate total size of files (consolidated from duplicates) calculate_total_size() { local files="$1" local total_kb=0 while IFS= read -r file; do if [[ -n "$file" && -e "$file" ]]; then local size_kb size_kb=$(get_path_size_kb "$file") ((total_kb += size_kb)) fi done <<< "$files" echo "$total_kb" }