Certbot 打包指南:发布流程、PGP 签名与发行版维护者实战要点
【免费下载链接】certbotCertbot is EFF's tool to obtain certs from Let's Encrypt and (optionally) auto-enable HTTPS on your server. It can also act as a client for any other CA that uses the ACME protocol.项目地址: https://gitcode.com/gh_mirrors/ce/certbot
Certbot 是 EFF(Electronic Frontier Foundation)出品的 ACME 协议客户端,用于从 Let's Encrypt 等 ACME CA 自动获取证书并启用 HTTPS。本文以仓库中的 packaging.rst 为核心,系统讲解 Certbot 官方的发布机制、PGP 签名体系,以及面向 Linux 发行版/第三方仓库维护者的打包注意事项,并深入到tools/下的发布脚本源码,帮助读者掌握"如何发布 Certbot"与"如何为你的发行版正确打包 Certbot"两套完整方案。
一、项目结构与可发布的软件包
Certbot 仓库采用多包(multi-package)monorepo 结构,每个子包都有独立的pyproject.toml与setup.py,可独立构建、独立上传 PyPI。从 tools/_release.sh 的SUBPKGS定义可以看出官方实际发布的完整软件包清单:
- 核心包:
certbot、acme - 插件包:
certbot-apache、certbot-nginx - DNS 插件包:
certbot-dns-cloudflare、certbot-dns-digitalocean、certbot-dns-dnsimple、certbot-dns-dnsmadeeasy、certbot-dns-gehirn、certbot-dns-google、certbot-dns-linode、certbot-dns-luadns、certbot-dns-nsone、certbot-dns-ovh、certbot-dns-rfc2136、certbot-dns-route53、certbot-dns-sakuracloud
这些包都会以 wheel 与源码包(sdist)两种形态发布到 PyPI,对应的项目主页分别是acme、certbot、certbot-apache、certbot-nginx以及上述全部 DNS 插件。其中acme是 ACME 协议客户端库,certbot依赖acme,这一点在 certbot/setup.py 中体现得很直接:install_requires明确写了f'acme>={version}'——即 Certbot 对 acme 的最小版本要求总是等于当前 Certbot 自身版本,从而保证接口一致性。
为什么不打包 certbot-compatibility-test
发布清单中刻意不包含certbot-compatibility-test。从源码看有两个原因(见 tools/_release.sh 中的注释):
- 它仅面向 Certbot 开发者内部使用,不面向最终用户;
- 包名以
test结尾,打包后会导致pytest在收集测试时误将其当作测试目录执行,而该目录下又没有真正的单元测试,从而引发混乱。
二、官方发布流程与脚本体系
2.1 版本号与 Git 标签
Certbot 使用 Semantic Versioning(语义化版本)作为版本标识,例如v0.11.1。每次发布对应一个格式为v<版本号>的 Git 标签,例如v1.21.0。发布分支的命名约定是candidate-<版本号>,例如candidate-4.24.0。
2.2 发布脚本:tools/release.sh
官方发布过程由 tools/release.sh 驱动,其工作流程可以拆解为:
- 前置校验:
CheckVersion校验两个版本参数(RELEASE_VERSION与NEXT_VERSION)是否符合1.2.3格式;检查是否存在 OpenPGP 卡片(除非设置了RELEASE_GPG_KEY环境变量显式指定密钥);确认系统已安装script命令(用于记录发布日志)。 - 安全护栏:若设置了
SNAP_BUILD环境变量则拒绝执行——因为该变量会导致插件 wheel 在构建时不声明对 Certbot 的依赖,从而破坏依赖关系。 - 环境备份:将旧的
releases/目录与log文件重命名备份。 - 执行核心脚本:调用
tools/_release.sh RELEASE_VERSION NEXT_VERSION,并通过script命令把全过程输出写入日志。
运行方式(在仓库根目录执行):
./tools/release.sh RELEASE_VERSION NEXT_VERSION # 例如发布 4.24.0、下一个开发版本为 4.25.0: ./tools/release.sh 4.24.0 4.25.02.3 核心构建脚本:tools/_release.sh
真正的构建逻辑在 tools/_release.sh 中,关键步骤包括:
- 环境清理:检测并退出任何激活中的 Python 虚拟环境,避免污染构建。
- 分支管理:若当前不在
candidate-<version>分支上,自动git switch -c创建该分支。 - PGP 密钥选择:若未设置
RELEASE_GPG_KEY,脚本会在 OpenPGP 卡片上查找四把受信任的发布密钥(见下文签名一节),找到后用于后续所有签名操作,并导出GPG_TTY=$(tty)解决 pinentry 交互问题。 - 干净克隆:
git clone . $root克隆一份全新副本再构建,确保产物不被本地残留文件污染。 - 变更日志生成:调用
towncrier build --version "$version" --yes,把newsfragments/目录下的变更片段合并进 CHANGELOG 并提交。 - 版本号写入:
SetVersion函数统一将SUBPKGS_NO_CERTBOT、certbot-compatibility-test、certbot-ci、letstest各setup.py中的version字段以及 certbot/src/certbot/init.py 中的__version__更新为目标版本。 - 构建产物:用
python -m build --installer uv为每个子包构建 sdist 与 wheel,全部汇总到packages/目录。 - 校验和与签名:对全部
*.tar.gz生成SHA256SUMS文件,并用gpg -u "$RELEASE_GPG_KEY" --detach-sign --armor --digest-algo sha256对其进行分离签名。 - 自举验证:创建全新虚拟环境,通过
--find-links .从本地构建产物安装全部子包(版本精确锁定为pkg==version),确保发布产物本身可安装;随后生成 CLI 帮助快照写入 certbot/docs/cli-help.txt 与 acme/docs/jws-help.txt(生成时设置CERTBOT_DOCS=1以使用文档专用的 User-Agent 样例值)。 - 提交与打标签:使用发布密钥对 "Release $version" 提交进行 GPG 签名,并以
--local-user "$RELEASE_GPG_KEY" --sign方式创建签名的v<version>标签;随后从 Git 索引中移除构建产物目录,并提交 "Bump version to $nextversion" 完成下一个开发版本号(NEXT_VERSION.dev0)的推进。
2.4 发布收尾:tools/finish_release.py
构建与上传 PyPI 之后,由 tools/finish_release.py 完成发布收尾,主要包括四件事:
- Snap 渠道晋升:将
certbot及全部 DNS 插件共 18 个 snap(ALL_SNAPS = ['certbot'] + PLUGIN_SNAPS)从 beta 渠道晋升到 stable 渠道;脚本要求已通过snapcraft login登录,并通过snapcraft status校验每个 snap 在指定版本下恰好有 3 个架构(SNAP_ARCH_COUNT = 3)的 revision。 - GitHub 分支同步:将
candidate-<version>分支推送到远端,并创建合并回main的 PR;非补丁版本(第三段版本号为 0)会额外创建1.2.x形式的次要版本分支,补丁版本则创建point-candidate-x.y.z分支并合并回.x分支。 - 社区公告生成:通过
gh release view v<version>抓取发布说明,打印格式化后的论坛公告文本。 - 测试模式:提供
--skip-snaps、--skip-github-sync、--test-version <A.B.C>三个参数,方便在两次发布之间安全演练(snap 晋升与公告打印是幂等操作)。
运行方式:
python tools/finish_release.py [--skip-snaps] [--skip-github-sync] [--test-version 1.2.3]三、PGP 签名体系与校验
自1.21.0起,Certbot 的所有发布包由以下四把 PGP 密钥之一进行加密签名(公钥可在各大主流密钥服务器上获取):
| 密钥指纹 | 说明 |
|---|---|
BF6BCFC89E90747B9A680FD7B6029E8500F7DB16 | 当前发布密钥 |
86379B4F0AF371B50CD9E5FF3402831161D1D280 | 当前发布密钥 |
20F201346BF8F3F455A73F9A780CC99432A28621 | 当前发布密钥 |
F2871B4152AE13C49519111F447BF683AA3B26C3 | 当前发布密钥 |
1.21.0 之前的版本则由旧密钥A2CFB51FA275A7286234E7B24D17C995CD9775F2签名,该公钥至今仍可在主要密钥服务器上找到,便于校验旧版软件包。
这一"四选一"的密钥体系与发布脚本深度绑定:tools/_release.sh在未显式设置RELEASE_GPG_KEY时,会在 OpenPGP 卡片上遍历TRUSTED_KEYS列表,一旦gpg --card-status命中四把密钥中的任意一把即采用之;找不到则报错退出。这意味着只有持有受信任密钥的维护者才能完成一次正式发布,从流程上保证了供应链可信。
四、面向发行版/第三方维护者的打包注意事项
packaging.rst专门为 Linux 发行版打包者与自建仓库维护者列出了五条要点,以下是逐条解读与源码佐证。
4.1 使用打标签的发布版本,不要用 main
请务必使用官方发布的 tagged release(如v1.21.0),而不是main分支快照。main处于持续开发状态,包含未发布的功能与尚未冻结的接口,直接打包会导致版本混乱、依赖破裂,也无法获得正确的签名。官方发布的版本号、签名、变更日志都是围绕 tag 组织的。
4.2 不要打包 certbot-compatibility-test
该包仅供 Certbot 开发者内部使用,正如第二节所述,打包它还会干扰pytest的测试收集。发行版应将其排除在产物之外。
4.3 使用 python -m pytest 运行测试
对打包好的源码运行测试时,请使用:
python -m pytest不要直接运行pytest命令。原因在于:python -m pytest会把当前目录加入PYTHONPATH,而直接执行pytest可执行文件时PYTHONPATH的处理方式不同,可能导致本地模块无法被测试运行器找到,从而出现"测试找不到被测模块"的假失败。
4.4 在包中集成自动化续期
如果你的发行版希望开箱即用地提供证书自动续期功能,需要做三件事:
- 调度
certbot renew -q:将certbot renew -q加入 crontab 或 systemd timer。Certbot 的 renew 子命令会检查所有已签发的证书,仅对临近过期的证书执行续期。 - 加入随机时间偏移:为每台机器设置一个随机的续期执行时间偏移,避免大量客户端在同一时刻并发访问 Let's Encrypt 服务器造成负载尖峰。例如 cron 中不要统一写死
0 0 * * *,而应在小时/分钟字段中引入随机值,或由 systemd timer 使用随机延迟。 - 在所有 Certbot 调用中带上
--preconfigured-renewal(适用于 Certbot >= 1.9.0):该标志可通过命令行或cli.ini配置文件全局设置,作用是让 Certbot 知道"自动续期已由包管理者预先配置好",从而调整其交互输出——它不会再输出"证书不会被自动续期"之类的误导性提示。
--preconfigured-renewal的源码级佐证:该标志定义在 certbot/src/certbot/_internal/cli/init.py 中,是一个action="store_true"的隐藏选项(help=argparse.SUPPRESS),默认值来自flag_default("preconfigured_renewal"),而 certbot/src/certbot/_internal/constants.py 中其默认值为False。在实际行为上,certbot/src/certbot/_internal/main.py 的_report_successful_renewal逻辑会据此决定是否输出"NEXT STEPS"中的自动续期提示:当preconfigured_renewal为假时,会提示用户"Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality";为真时则提示"Certbot has set up a scheduled task to automatically renew this certificate in the background"(见_report_new_cert)。certbot的 Snap 包正是通过 certbot/src/certbot/_internal/snap_config.py 的prepare_env自动追加--preconfigured-renewal来实现这一机制的,可作为发行版打包的参考范本。
配置示例:发行版可以在全局配置文件(例如 Debian 系的/etc/letsencrypt/cli.ini,参考仓库中的 certbot/examples/cli.ini)中写入:
preconfigured-renewal = true该文件中同样可以设置email、authenticator、rsa-key-size等常规选项,这些选项会自动应用于系统上所有证书的签发与续期调用。
4.5 jws 是 acme 的内部调试脚本,无需打包
jws是acme模块内部的命令行脚本,主要用于调试 JWS(JSON Web Signature)签名流程,不面向终端用户,因此不必随发行版打包。它的典型用法是管道式签名/验签:
echo foo | jws sign | jws verify(其帮助文本快照可在 acme/docs/jws-help.txt 中查看。)它之所以存在,是因为 ACME 协议的所有请求都需要 JWS 签名,开发者可以用它快速验证 JWS 逻辑。
4.6 与上游保持沟通,补丁优先贡献回主线
最后一条建议:主动与 Certbot 团队保持联系。维护者非常乐于为打包便利性做出变更;如果你发现必须对源码打补丁才能打包,不要在下游维护补丁(否则每次上游升级都要重新移植),而是直接在上游仓库提交 PR 合入主线。这样既减轻发行版维护负担,也让所有用户受益。
五、FAQ 与常见问题
Q:为什么仓库里
acme/setup.py的版本是5.8.0.dev0这类带.dev0的版本?A:.dev0是发布脚本在"Bump version to $nextversion"步骤写入的下一个开发版本号(见 tools/_release.sh),表示当前main处于5.8.0的开发期,尚未正式发布;正式发布时才会写入干净的x.y.z版本号。Q:如何验证下载到的 Certbot 包确实是官方签发的?A:从主流密钥服务器导入上文四把公钥(或旧版的
A2CFB51FA275A7286234E7B24D17C995CD9775F2),然后对随包发布的SHA256SUMS.asc执行gpg --verify SHA256SUMS.asc SHA256SUMS,再用sha256sum -c SHA256SUMS校验各归档文件完整性。Q:发行版测试为什么必须用
python -m pytest?A:见 4.3 节——pytest可执行文件与python -m pytest在PYTHONPATH处理上的差异会导致本地模块查找失败,这是打包者最容易踩的坑之一。
六、参考资源
- 打包指南原文:certbot/docs/packaging.rst
- 发布脚本(入口):tools/release.sh
- 发布脚本(核心构建):tools/_release.sh
- 发布收尾脚本:tools/finish_release.py
- Certbot 打包配置:certbot/setup.py、certbot/pyproject.toml
--preconfigured-renewal实现:certbot/src/certbot/_internal/cli/init.py、certbot/src/certbot/_internal/main.py、certbot/src/certbot/_internal/snap_config.py- CLI 全局配置示例:certbot/examples/cli.ini
【免费下载链接】certbotCertbot is EFF's tool to obtain certs from Let's Encrypt and (optionally) auto-enable HTTPS on your server. It can also act as a client for any other CA that uses the ACME protocol.项目地址: https://gitcode.com/gh_mirrors/ce/certbot
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考