当用户删除 .app 时卸载它安装的项目,包括 SMJobBless 助手

Uninstalling items installed by an .app when user deletes it, including SMJobBless helpers

精简版:删除应用时是否可以删除应用设置的辅助工具(SMJobBless()等)?如果是,怎么做?

长版:

不幸的是,我们正在开发的 Mac 应用程序需要管理员权限才能执行偶尔的操作,并且它还需要一个后台任务才能使其他应用程序的插件连接到即使应用程序本身不存在也是如此t 运行ning(这个可以是无特权的)。该应用程序将使用 Developer ID 证书进行签名,并且仅在 App Store 之外分发。

我们希望该应用程序尽可能成为 "good citizen",并且在卸载时也是如此。

对于后台任务,我们使用了使用 SMLoginItemSetEnabled() 创建的登录项。这并不奇怪,因为 XPC 消息传递似乎不起作用(我们使用 CFMessagePort 代替 - 欢迎提供替代建议),但如果用户删除应用程序,登录项至少不会加载下次登录时不再。我怀疑系统中某处仍有它的痕迹,但使用了 .app 包中的可执行文件,当它消失时,登录项不再 运行s.

对于偶尔需要管理员权限的操作,我们有一个特权帮助工具,我们的应用程序使用 SMJobBless() 安装它,它实现了一个命名的 XPC 服务,因此任务在收到请求时按需启动来自主应用程序的消息。这是 Apple 在其 Even Better Authorization Sample.

中推荐和描述的内容

辅助可执行文件被 SMJobBless() 复制到 /Library/PrivilegedHelperTools/,嵌入的 launchd.plist 最终出现在 /Library/LaunchDaemons/ 中。尽管 OS 有关于哪个应用 "owns" 助手的信息,但当用户删除该应用时,它似乎并没有卸载它。除了 uninstall.sh 脚本外,Apple 的示例在卸载时没有提及,该脚本显然仅供开发期间使用。当应用程序未 运行ning 时,我们不需要此帮助程序,因此将其安装为成熟的启动守护程序有点过分,但我们也希望避免重复提示密码的用户烦扰也。此外,Apple 建议不要使用其他形式的 运行ning 代码,这些代码比 SMJobBless() 具有管理员权限 - 例如 SMJobSubmit() 被标记为已弃用。

那么我们如何清理自己呢?

我找到了 SMJobRemove(),但是 (a) 在我们的案例中我们什么时候调用它 - 您不能 运行 对 .app 包删除进行编码,或者您可以吗?和 (b) it doesn't actually seem to clean up.

我能想到的只有两件事不太令人满意:

  1. 某种卸载应用程序或脚本。但这看起来也很丑。
  2. 别担心,当用户删除我们的应用程序时留下一团糟。

我认为您现在唯一的解决方案是使用您提到的卸载 shell 代码,以便从磁盘中物理删除特权帮助程序或为其构建卸载程序。无论哪种方式,您都必须要求用户输入 his/her 密码。这是所有需要对系统进行特权访问的安装程序/卸载程序所做的,并且有充分的理由。这就是为什么我像瘟疫一样避免使用特权助手,但我知道有时候你真的不得不这样做。我觉得你把这样的助手留在用户系统里不好,因为它会在用户下次启动计算机时重新加载。

我刚刚检查了 ServiceManagement.h header,他们说 SMJobRemove 将被 API 取代,后者将在未来通过 libxpc 提供。 (有时你真的需要去 headers 获取文档没有给你的额外信息。)希望这个承诺的替代品会为我们卸载它。但是,我会提交错误报告并要求增强功能。

您可以考虑的一种解决方案是在您的 .app 包中包含卸载程序脚本或程序。

然后您可以将这个小工具的路径传递给您的辅助工具(通过 IPC)并让其执行卸载程序,从而删除自身。您必须小心以正确的顺序移除组件,但它可以正常工作。

https://forums.developer.apple.com/thread/66821 的 Apple 开发者论坛上也有类似的问题 - Apple 的建议是手动卸载机制,如果用户不这样做,将消耗尽可能少的资源。

Apple DTS 工作人员进一步建议在特权启动守护程序中实施自卸载机制,通过 XPC 从应用程序中触发。这就是我们要做的。

你说得对,Apple 没有提供 API 来卸载随 SMJobBless 安装的辅助工具,它们也不会自动执行此操作。至于macOS为什么不自动卸载,我有根据的猜测是因为macOS从根本上没有一个统一的“安装”概念。虽然应用程序位于 /Applications(以及其他一些位置)是惯例,但它完全适用于系统上任何地方(包括外部驱动器和网络驱动器)的应用程序定位和 运行。例如,当应用程序消失时,macOS 是否应该卸载帮助工具,因为它们所在的驱动器已断开连接?

如何卸载而言,这样做需要 root 权限,因此实际上让帮助工具本身进行卸载是最简单的选择。你可以让你的应用程序通过 XPC 告诉助手卸载它自己。这是 example in Swift of how to do this; it's part of SwiftAuthorizationSample。基本思路是:

  • 使用launchctl命令行工具卸载辅助工具
  • 删除辅助工具可执行文件
  • 删除辅助工具launchd plist

但是这会涉及一些额外的复杂性,因为 launchctl 不会让您卸载 运行ning 进程。