从源码到 Release:使用 GitHub Actions 自动构建 RPM 与 DEB
摘要
一套与编程语言和具体打包工具无关的 Linux 软件发布方法:区分编译产物、系统软件包、Actions Artifact 与 Release Asset,并处理版本、架构、兼容性、验证和供应链安全。
从源码到 Release:使用 GitHub Actions 自动构建 RPM 与 DEB
为 Linux 项目提供 RPM 和 DEB 下载,看起来只是“在 CI 里多执行两条打包命令”,实际上涉及版本管理、目录布局、依赖声明、架构命名、目标系统兼容性、安装验证和发布权限。GitHub Actions 可以把这些步骤串成可重复执行的流水线,但它不会自动保证软件包符合所有发行版的规范,也不会让同一个 RPM 或 DEB 天然兼容整个 Linux 生态。
本文不绑定某种编程语言,也不要求使用特定打包工具。示例假设仓库已经提供可从命令行调用的构建脚本,重点讨论如何组织一条通用而可靠的自动发布流程。
先区分四种容易混淆的产物
一条发布流水线通常会接触四种不同对象。
编译产物是编译器或构建系统生成的程序,例如可执行文件、动态库、静态资源和配置模板。它们可能还不能直接安装。
系统软件包是 RPM 或 DEB 文件。除程序文件外,它还包含软件名称、版本、架构、依赖、安装路径、维护脚本和卸载信息。
Actions Artifact是某次工作流运行保存的文件。它适合在任务之间传递构建结果,或者让维护者下载测试。Artifact 有保留期限,删除工作流运行时,对应 Artifact 也会被删除。
Release Asset是附加在 GitHub Release 上的正式下载文件。Release 基于 Git 标签,适合向最终用户分发软件包、压缩包、校验和与发布说明。
因此,合理的流程不是简单地“编译后上传”,而是:
RPM 和 DEB 不是两种压缩格式
DEB 主要用于 Debian、Ubuntu 及其衍生发行版;RPM 主要用于 Fedora、RHEL、Rocky Linux、AlmaLinux、openSUSE 等发行版。但“使用相同包格式”不等于“能够跨发行版直接运行”。
一个软件包能否使用,还取决于目标系统的 ABI、glibc、libstdc++、OpenSSL、图形栈、音频栈以及其他动态库版本。不同发行版对依赖名、默认路径和打包政策也可能有差异。同一个 RPM 不一定适用于所有 RPM 系发行版,同一个 DEB 也不一定适用于所有 Debian 系发行版。
所以,自动化打包解决的是重复构建和发布问题;发行版兼容性仍然需要明确的支持范围与实际测试。
把打包逻辑放在仓库脚本里
不要把所有业务逻辑都堆进工作流 YAML。GitHub Actions 更适合负责准备环境、调用脚本、传递产物和发布 Release。具体的打包过程应尽量保存在仓库中,例如:
这样可以在本地复现 CI 行为,也便于以后迁移到其他 CI 平台。工作流只需要约定统一接口:
脚本应在失败时返回非零状态,并避免把缺少输出文件当作成功。Shell 脚本通常可以从以下设置开始:
不过它不是完整的错误处理方案。脚本仍应明确检查参数、目录、命令和最终文件是否存在。
使用两类触发方式
手动触发适合测试打包流程,标签触发适合正式发布:
v1.2.3 只是一种常见约定,不是 GitHub 的强制规则。项目可以使用其他标签格式,但必须明确标签如何映射到项目版本、Debian 版本和 RPM 版本。
版本号经常同时存在于源码、构建配置、Git 标签、RPM 元数据、DEB 元数据、文件名和 Release 标题中。正式发布前应检查它们是否一致。还要注意,Debian 与 RPM 的版本比较规则并不完全相同,因此复杂版本字符串最好经过包格式专用的规范化,而不是机械地复制同一个字符串。
一份通用的工作流骨架
下面的工作流展示一种常见结构:DEB 和 RPM 分开构建,各自上传 Artifact;只有标签触发且两个构建任务都成功后,才创建 Release。
这份配置是结构模板,不是复制后即可适用于所有项目的成品。install-*-build-deps.sh、package-*.sh 和验证脚本必须由项目按照实际构建系统实现。容器镜像和 Runner 版本也应根据项目支持周期固定并定期更新。
在受控发布流程中,固定环境版本通常比使用 latest 更容易复现。不过固定版本并不意味着永久不更新;它意味着升级由维护者主动完成,并经过测试。
DEB 应怎样构建
对于具有标准 Debian 打包目录的源码,dpkg-buildpackage 是核心构建工具。一个常见命令是:
其中 -us -uc 表示本次构建不签名源码和 .changes 文件,-b 表示构建二进制包。正式进入 Debian 仓库的流程比“为 GitHub Release 生成一个 DEB”严格得多,需要遵循 Debian Policy,并维护完整的源包信息。
也可以使用 debuild、sbuild、pbuilder、语言生态工具或通用打包工具。选择取决于目标:内部工具和第三方下载包可以采用较轻量的方案;准备进入发行版官方仓库的包,则应使用对应发行版认可的标准流程。
无论采用哪种工具,都应准确填写包名、版本、架构、维护者、描述、运行依赖、冲突关系和安装文件列表。
RPM 应怎样构建
标准 RPM 通常由 Spec 文件描述,并使用 rpmbuild 构建。例如:
RPM 同样包含二进制包和源包两类。Spec 文件负责描述源码、构建步骤、安装阶段、文件清单、依赖、脚本和变更记录。
语言生态工具或通用打包器可以简化生成 RPM 的过程,但不应因此跳过元数据检查和安装测试。如果目标是进入 Fedora、EPEL 或其他发行版仓库,还需要遵守相应项目的打包规范,而不是仅以“能生成 .rpm 文件”为完成标准。
不要忽略架构映射
同一硬件架构在不同生态中可能使用不同名称。最常见的例子是 64 位 x86:
64 位 ARM 也经常表现为:
因此,不要把 uname -m、Runner 架构或矩阵变量原样写入所有软件包。应建立明确的映射函数,并让无法识别的架构直接失败:
跨架构构建还涉及交叉编译器、目标系统库和安装测试,不能只靠改文件名完成。
软件包生成后必须验证
文件存在不代表软件包正确。至少应检查元数据和文件列表。
DEB 可以使用:
RPM 可以使用:
还可以引入 lintian 和 rpmlint。但检查器的每一条警告不一定都应该阻止发布,项目需要制定清晰的质量门槛。真正关键的是,编译失败、软件包生成失败、核心测试失败、安装失败和 Release 上传失败不应被忽略。
比静态检查更可靠的是在干净的容器或虚拟机中执行安装测试:
如果程序依赖 systemd、图形桌面、内核模块或真实硬件,仅使用普通容器可能不够,需要虚拟机或专门的集成测试环境。
构建环境决定兼容性下限
在较新的构建系统中生成的软件,可能引用较新的 glibc、libstdc++ 或其他共享库符号,从而无法在较旧系统运行。把二进制文件放进 DEB 或 RPM 并不会消除这种限制。
提高兼容性的常见做法包括:在项目支持的较旧基础环境中构建;为不同发行版或版本分别构建;使用与目标系统一致的容器;准确声明运行依赖;检查动态链接关系;在支持列表中的系统上安装测试。
静态链接有时可以减少运行依赖,但并不适用于所有语言、许可证、系统库和桌面程序。它应被视为一种工程选择,而不是通用解法。
Artifact 与 Release 应各司其职
所有分支或手动运行都可以上传 Artifact,方便维护者检查打包结果;正式 Release 则应只在满足明确条件时创建,例如推送受保护的版本标签且全部构建和测试成功。
GitHub 当前使用 actions/upload-artifact@v4 上传 Artifact;该版本中的 Artifact 是不可变的,同一工作流中应使用唯一名称。下载多个构建任务的产物时,可以使用 actions/download-artifact@v5,再由发布任务集中生成校验和。
Release 创建需要写入仓库内容,因此只在发布 Job 中授予:
其他构建 Job 保持 contents: read,符合最小权限原则。GITHUB_TOKEN 由 GitHub 为每个 Job 自动生成,其权限仅限当前仓库,并会在任务结束后失效。
校验和不是签名
为 Release 生成 SHA-256 校验和很有价值:
它可以帮助用户确认文件没有在下载过程中损坏,也能比较文件是否发生变化。但如果攻击者能够同时替换软件包和校验文件,单独的校验和不能证明发布者身份。
要建立发布者身份与构建来源的信任,还可以考虑 Git 标签签名、RPM 或 Debian 软件包签名、GitHub Artifact Attestations、SBOM 和受保护发布环境。签名流程涉及私钥存储、轮换、撤销和信任分发,值得单独设计,不能只把长期私钥随意写入仓库 Secret 后就视为安全。
第三方 Action 也是供应链依赖
每个 uses: 都会执行外部代码。正式发布流程应优先使用维护清晰的 Action,授予最小权限,并评估是否固定到不可变的提交 SHA。固定大版本标签便于接收兼容更新,固定提交 SHA 则能减少上游标签被移动带来的风险;两者之间需要根据维护成本和威胁模型作出选择。
还应避免把不可信输入直接拼进 Shell。分支名、标签名、Issue 内容、PR 标题和工作流输入都可能包含特殊字符。应通过环境变量传递,并在脚本中引用变量,而不是直接把表达式插入命令文本。
不要用 continue-on-error 掩盖发布失败
continue-on-error: true 可以用于实验性分析或尚未纳入质量门槛的检查,但不应该用于以下步骤:
所谓“宽松检查”应表示某些非核心警告暂时不阻塞流水线,而不是让错误的软件包被当作成功结果发布。
常见问题
本地能编译,Actions 中失败
通常是 CI 环境缺少开发包、编译器、系统头文件、pkg-config 元数据或外部工具。运行时库和开发包往往不是同一个软件包。
软件包可以安装,但程序不能启动
可能是运行依赖没有声明、构建环境过新、动态库名称不一致,或者程序在构建时自动关闭了某项功能。应在干净目标环境中执行真实启动测试。
同一个包不能覆盖多个发行版
包格式相同不代表依赖和 ABI 相同。可以按发行版系列分别构建,并在文件名和 Release 说明中明确支持范围。
Release 返回 403
通常需要检查发布 Job 是否具有 contents: write,仓库或组织策略是否限制了 Actions 权限,以及触发事件是否来自不可信的 Fork。
重跑标签工作流时 Release 已存在
发布脚本需要明确选择失败、覆盖 Asset、更新草稿,还是生成新的版本。不要在没有策略的情况下静默覆盖正式发布文件。启用不可变 Release 时,GitHub 建议先创建草稿、附加全部文件,再发布。
结语
GitHub Actions 能够自动化 RPM 和 DEB 的构建与发布,但可靠交付的关键不在于 YAML 有多长,而在于边界是否清楚:支持哪些发行版和架构,版本如何映射,文件安装到哪里,依赖如何声明,构建环境如何固定,软件包怎样验证,发布任务拥有什么权限,以及用户如何验证下载内容。
一条成熟的流水线应当做到可重复、可审查、可验证,并在任何核心步骤失败时停止发布。只有这样,“自动生成两个安装包”才真正升级为一套可信的软件发布流程。
