关于 Arch Linux 的脚本和钩子
摘要
深入探讨 Arch Linux 的 alpm-install-scriptlet 文件和 alpm-hooks,它们的工作原理、在软件包仓库中的使用方式,以及何时使用它们的指南。
<p><a href="https://lobste.rs/s/0mrntr/on_arch_linux_scripts_hooks">评论</a></p>
查看缓存全文
缓存时间: 2026/08/09 14:48
# 关于脚本和钩子
来源:https://devblog.archlinux.page/2026/on-scripts-and-hooks/
在过去的几周里,我们一直在研究官方发行版软件包安装到目标系统后的集成情况。在此背景下,我们查看了大约 120 个软件包源代码仓库([alpm-source-repo(7)](https://man.archlinux.org/man/extra/alpm-docs/alpm-source-repo.7.en))中对 [alpm-install-scriptlet(5)](https://man.archlinux.org/man/alpm-install-scriptlet.5) 和 [alpm-hooks(5)](https://man.archlinux.org/man/alpm-hooks.5) 文件的当前使用情况,以更好地理解这两种集成方式所提供的底层功能和目标用例。在本文中,我们将探讨这两个系统如何工作、Arch Linux 当前如何使用它们,并尝试为何时使用哪种方式提供建议。了解 [alpm-hooks(5)](https://man.archlinux.org/man/alpm-hooks.5) 和 [alpm-install-scriptlet(5)](https://man.archlinux.org/man/alpm-install-scriptlet.5) 文件对于软件包维护者和终端用户都很有帮助,可以更好地理解系统更新如何应用,以及如何处理不属于任何软件包的数据。
## 🤝 集成
软件包安装(在 Arch Linux 和其他发行版上)不仅涉及添加和更新属于某个软件包的文件,还涉及对系统包管理器未跟踪的数据进行集成。例如,这可能包括创建用户和组、创建目录和文件、修改目录和文件的模式和所有权,或者创建、更新和删除各种缓存文件。与其他一切事物一样,也存在一些边缘情况,例如在特定软件包版本升级时发出消息、禁用服务、迁移数据或更改用户或组。
## 2️⃣ 两套系统
目前,存在两种专为操作非软件包文件所拥有的数据而设计的机制:
- [alpm-install-scriptlet(5)](https://man.archlinux.org/man/alpm-install-scriptlet.5) 是一个脚本文件,可能包含在 [alpm-package(7)](https://man.archlinux.org/man/alpm-package.7) 中,但不会安装到目标系统。相反,脚本中的预定义函数会在相应软件包事务前后由包管理系统执行。
- [alpm-hooks(5)](https://man.archlinux.org/man/alpm-hooks.5) 是声明式文件格式的文件,位于目标系统上众所周知的位置,包管理系统在处理特定软件包事务的整体之前和之后执行它们。
上图展示了一个通用的软件包管理器事务(安装、升级或删除多个软件包)。在图表的顶部和底部,我们看到了事务前和事务后的钩子(紫色),它们在软件包事务块(红色)之前和之后运行。这些就是 [alpm-hooks(5)](https://man.archlinux.org/man/alpm-hooks.5)。每个软件包事务(橙色)都有一个可能的安装前/安装后、升级前/升级后、移除前/移除后函数部分(紫色),它们在给定软件包的文件事务(蓝色)之前和之后运行。这些就是 [alpm-install-scriptlet(5)](https://man.archlinux.org/man/alpm-install-scriptlet.5) 文件。请查看[事务示例](https://devblog.archlinux.page/2026/on-scripts-and-hooks/#keyboard-transaction-example),了解这些文件在打包环境中的外观和行为。也请软件包维护者查看[面向软件包维护者的笔记](https://devblog.archlinux.page/2026/on-scripts-and-hooks/#notebook-notes-for-package-maintainers),以了解 Arch Linux 发行版当前的一些边缘情况和潜在解决方案。
### 📜 脚本
从历史上看,系统集成是通过 [PKGBUILD(5)](https://man.archlinux.org/man/PKGBUILD.5) 脚本本身,或借助 [alpm-install-scriptlet(5)](https://man.archlinux.org/man/alpm-install-scriptlet.5) 文件来实现的。[alpm-install-scriptlet(5)](https://man.archlinux.org/man/alpm-install-scriptlet.5) 提供自由形式的 shell 脚本,在专用函数中定义,用于在特定软件包的安装、升级或移除前后运行。每个函数都可以访问与特定软件包事务相关的版本信息,例如软件包的旧版本和新版本。然而,这种方法并非没有缺点:
- 理论上,[alpm-install-scriptlet(5)](https://man.archlinux.org/man/alpm-install-scriptlet.5) 可以用多种 shell 脚本语言编写。实际上,使用的 shell 与包管理器的构建时配置绑定(Arch Linux 使用 Bash)。
- 软件包文件的安装顺序(以及随之而来的其 [alpm-install-scriptlet(5)](https://man.archlinux.org/man/alpm-install-scriptlet.5) 操作的运行时机)取决于包管理器为给定事务确定的顺序(即,在安装一组软件包时,哪个软件包先安装)。一个事务可以包含一个或多个软件包。
- [alpm-install-scriptlet(5)](https://man.archlinux.org/man/alpm-install-scriptlet.5) 无法定义其运行时要求(即需要调用哪些可执行文件)。当它运行时,它所调用的可执行文件或完成其工作所需的其他数据可能(还)不存在。
- 为了可靠地将目录和文件分配给用户,必须在软件包构建时知道其[用户 ID](https://en.wikipedia.org/wiki/User_identifier) 和[组 ID](https://en.wikipedia.org/wiki/Group_identifier),这实际上意味着它们需要被硬编码。
- 缓存文件的创建或更新可能会被执行多次,甚至在错误的时间执行。当软件包 *A* 提供了创建特定于应用程序的缓存文件的工具,而软件包 *B* 和 *C* 都提供了需要创建该缓存文件的文件时,那么 *B* 和 *C* 都需要运行 *A* 所提供的工具。
- 只考虑事务中特定软件包的 [alpm-install-scriptlet(5)](https://man.archlinux.org/man/alpm-install-scriptlet.5)(而不考虑例如先前版本的脚本)。因此,软件包维护者需要为即将安装/升级/移除的版本提前做好规划。
### 🪝 钩子
在 2010 年 [systemd](https://systemd.io/) 的第一个版本中,发布了 [tmpfiles.d(5)](https://man.archlinux.org/man/tmpfiles.d.5) 的初始支持。通过它,可以声明式地创建、调整和清理目录和文件。2015 年,[sysusers.d(5)](https://man.archlinux.org/man/sysusers.d.5) 在 [systemd](https://systemd.io/) 215 中被添加,增加了声明式添加用户和组的可能性。一年后,2016 年,[alpm-hooks(5)](https://man.archlinux.org/man/alpm-hooks.5) 功能随 [pacman(8)](https://man.archlinux.org/man/pacman.8) 5.0 发布。这三个变化催生了新的工作流程,使得更多集成任务可以在事务之前和之后运行。
在这里,[alpm-hooks(5)](https://man.archlinux.org/man/alpm-hooks.5) 提供了严格有序且定时的集成,其中操作可以在软件包文件事务之前或之后执行。操作可以定义其运行时要求,并可通过系统上的软件包或路径变化来触发。关于 [alpm-hooks(5)](https://man.archlinux.org/man/alpm-hooks.5),有几点需要注意:
- 它们非常通用,在目标系统的上下文中运行,并且不了解特定软件包的版本信息。
- 系统管理员可以使用 /etc/pacman.d/hooks/ 声明自己的钩子或禁用现有的钩子。
- 由于钩子是从目标系统的文件系统中读取的,事务前任务只有在提供它们的软件包安装后才可用并运行。
- 事务后任务只有在提供它的软件包保持安装状态时才可用并运行。
## 💡 使用案例
通过查看大约 120 个软件包的 [alpm-source-repo(7)](https://man.archlinux.org/man/extra/alpm-docs/alpm-source-repo.7.en),我们推断出了最常见的使用案例。
### alpm-hooks
- 在移除软件包时,在事务前删除缓存文件。
- 使用 [sysusers.d(5)](https://man.archlinux.org/man/sysusers.d.5) 创建用户和组([20-systemd-sysusers.hook](https://gitlab.archlinux.org/archlinux/packaging/packages/systemd/-/blob/5f46d2afd2a28c011f67bd810e1f83c4adf48266/20-systemd-sysusers.hook) 在事务后运行 [systemd-sysusers(8)](https://man.archlinux.org/man/systemd-sysusers.8))。
- 在事务后使用 [tmpfiles.d(5)](https://man.archlinux.org/man/tmpfiles.d.5) 创建目录、符号链接、文件,设置所有权、权限和 capabilities([21-systemd-tmpfiles.hook](https://gitlab.archlinux.org/archlinux/packaging/packages/systemd/-/blob/5f46d2afd2a28c011f67bd810e1f83c4adf48266/21-systemd-tmpfiles.hook) 运行 [systemd-tmpfiles(8)](https://man.archlinux.org/man/core/systemd/systemd-tmpfiles.8.en))。
- 在安装或升级软件包时,或特定文件路径发生变化时,在事务后创建缓存文件。
- 在事务后为特定软件包迁移可变数据(例如 [25-systemd-catalog.hook](https://gitlab.archlinux.org/archlinux/packaging/packages/systemd/-/blob/5f46d2afd2a28c011f67bd810e1f83c4adf48266/25-systemd-catalog.hook) 用于更新 [journalctl(1)](https://man.archlinux.org/man/journalctl.1) 目录)。
- 在事务后重启或重新加载 systemd 系统单元。为此,需要重新加载/重启的单元通过 [systemctl(1)](https://man.archlinux.org/man/systemctl.1) 标记(例如 `systemctl --runtime set-property Markers=needs-restart`,可以放在 [alpm-install-scriptlet(5)](https://man.archlinux.org/man/alpm-install-scriptlet.5) 或事务前钩子中),然后通过 [35-systemd-enqueue-marked.hook](https://gitlab.archlinux.org/archlinux/packaging/packages/systemd/-/blob/3c84384243b1a582932b31b59bc34d7549a59051/35-systemd-enqueue-marked.hook) 在事务后任务中入队,由该任务重新加载/重启它们。有关 `Markers` 的更多详细信息,请参见 [org.freedesktop.systemd1(5)](https://man.archlinux.org/man/org.freedesktop.systemd1.5)。
### alpm-install-scriptlet
[alpm-install-scriptlet(5)](https://man.archlinux.org/man/alpm-install-scriptlet.5) 中最常见的任务是在升级到特定版本的软件包时向用户显示消息(例如,告知他们配置或数据的破坏性变更)。然而,还有更多与版本绑定的使用案例。以下部分或全部内容也可能由 [alpm-hooks(5)](https://man.archlinux.org/man/alpm-hooks.5) 覆盖(另请参见[面向软件包维护者的笔记](https://devblog.archlinux.page/2026/on-scripts-and-hooks/#notebook-notes-for-package-maintainers))。
- 停止 systemd 系统单元(如果它已启动)(`systemctl stop `)。
- 全局启用 systemd 用户单元(`systemctl --global enable `)。
- 全局重新启用 systemd 用户单元(`systemctl --global reenable `)。
- 全局禁用 systemd 用户单元(`systemctl --global disable `)。
- 启用 systemd 系统单元(`systemctl enable `)
- 禁用 systemd 系统单元(如果它已启用)(`systemctl disable `)。
- 标记服务以进行重启(`systemctl set-property Markers=needs-restart`)。
## 🚨 问题
与所有系统一样,存在边缘情况,以及意外和过时的使用案例。其中一些可能的解决方案将在[面向软件包维护者的笔记](https://devblog.archlinux.page/2026/on-scripts-and-hooks/#notebook-notes-for-package-maintainers)中进一步讨论。总的来说,可以注意到 [alpm-hooks(5)](https://man.archlinux.org/man/alpm-hooks.5) 为某个功能的实现位置和方式提供了更集中化的方法。而如果每个软件包都使用 [alpm-install-scriptlet(5)](https://man.archlinux.org/man/alpm-install-scriptlet.5),则必须反复实现功能,引入大量样板代码。
最常见的问题源于软件包维护者没有意识到 [alpm-hooks(5)](https://man.archlinux.org/man/alpm-hooks.5) 已经提供了特定功能,因此转而使用 [alpm-install-scriptlet(5)](https://man.archlinux.org/man/alpm-install-scriptlet.5) 来实现处理。我们发现的一些常见任务包括创建文件和目录、更改文件和目录的权限或所有权、应用自定义文件属性(例如 [capabilities(7)](https://man.archlinux.org/man/capabilities.7)、[setuid(2)](https://man.archlinux.org/man/setuid.2) 或 [setgid(2)](https://man.archlinux.org/man/setgid.2)),或创建用户和组。
测试对 [alpm-install-scriptlet(5)](https://man.archlinux.org/man/alpm-install-scriptlet.5) 的更改可能相当棘手,因为函数的执行仅由特定的软件包事务(即安装、升级或移除)触发。在某些情况下,无法可靠地触发这些情况(例如,使用 `pacman --upgrade` 进行本地升级时)。
另一方面,对 [alpm-hooks(5)](
相似文章
包管理器需要全局钩子
一篇博文,主张包管理器应支持全局钩子,作为当前包安全措施(如注册表或shell包装器)的更安全、更灵活的替代方案。
AUR中流传的恶意软件包
安全警报:恶意软件包正在Arch用户软件仓库(AUR)中传播,对Arch Linux用户构成风险。
Arch Linux AUR 中的恶意软件现在向 shell 配置插入俄语垃圾信息
Arch Linux AUR 中的恶意软件包被发现向用户的 shell 配置文件中插入俄语垃圾信息,促使维护者进行清理。
yay v13 与 AURpocalypse
yay v13 引入了 Lua 钩子以实现可扩展性,并显示 PKGBUILD 最后修改时间,帮助用户更仔细地审查软件包,以回应 AURpocalypse 事件。
我的编程助手安装了 loadash。如何在 postinstall 运行前彻底阻止虚假包?
讨论了 AI 编程助手安装拼写相似的伪装 npm 包(如 loadash)的问题,并询问在 postinstall 脚本执行前如何阻止这类虚假包。