Skip to content

08 · Uninstall 卸载系统

卸载不是“按应用名全盘搜索”。它是一次身份归因:哪些文件有足够证据属于目标应用,哪些只能提示用户复核。

两阶段设计

卸载器先构建应用清单,再为选中应用扫描细节。清单元数据缓存于 ~/.cache/mole/uninstall_app_metadata_v1,TTL 为 7 天。Spotlight/mdls 查询使用极短超时,冷数据由并行 worker 补齐;数量较少时才允许有界 du

缓存复用不仅看时间,还计算应用 inventory fingerprint。新增或 mtime 变化会触发刷新,已移除应用不必让整个缓存失效。这样菜单能快速出现,同时后台逐步修正元数据。

从应用身份到文件证据

.app / package receipt
   │
   ├─ Bundle ID、变体、嵌套 helpers
   ├─ 精确 Library 路径与 defaults
   ├─ Homebrew cask / 官方卸载器
   └─ 系统级残留(只报告)
          │
          ▼
   批量预览与一次确认
          │
          ▼
   `MOLE_UNINSTALL_MODE=1` + `mole_delete`

应用名只是辅助证据。核心依赖 reverse-DNS Bundle ID、Info.plist、包收据与明确的路径形态。实现避免通用 vendor wildcard,也不会因为 GUI 与 CLI 同名就删除 ~/.codex~/.claude 等独立状态。

默认 Trash,永久删除显式选择

卸载默认设置 MOLE_DELETE_MODE=trash--permanent 才改变恢复语义。直接按名称匹配仍要求确认;--dry-run 走同一扫描与计划链。--list 是只读操作,在非 TTY 下自动输出 JSON。

批处理不是 N 次单卸载

批处理先并行扫描所有选中应用,再合并成一次预览和一次 sudo 请求。执行时每个应用独立记结果,单个失败不阻断后续应用。

它还处理“同 Bundle ID 有多个安装副本”:只要兄弟安装仍存在,就跳过共享数据、helper teardown 与可能破坏另一个副本的 brew zap。这是以应用实例和产品身份分层,而不是把 Bundle ID 当作唯一文件所有权证明。

系统级对象只给建议

扫描到 LaunchDaemon、系统扩展、后台项目等系统级残留时,卸载器会把它们从可删除数组中移除,转成 review-only。Local Network 权限、系统扩展和仍在运行的进程也只提示用户去系统设置处理。

这条边界很克制:能发现不等于获得删除授权。系统数据库与扩展注册通常需要官方卸载器或系统 API,手工删文件反而制造半卸载状态。

收尾操作

成功后可清理 defaults、ByHost 偏好、登录 helper、Dock 与 LaunchServices 注册;Homebrew autoremove 和刷新操作异步执行且不占用 TTY。所有实际路径仍通过应用保护与 mole_delete

关键源码索引

下一章比较两个更聚焦的空间回收器:项目产物 Purge 与安装包清理 Installer。

独立源码研究笔记 · 非 Mole 官方文档