Dangerzone 发布流程实战:从 PGP 签名标签、资产签名到 GitHub 草稿发布的最后一哩路
【免费下载链接】dangerzoneTake potentially dangerous PDFs, office documents, or images and convert them to safe PDFs项目地址: https://gitcode.com/GitHub_Trending/da/dangerzone
本文以 Dangerzone 仓库的发布文档 release.md 为主体,完整拆解一次正式发布的最终阶段:如何创建 PGP 签名的 git tag、打包源码归档、用 grype 扫描容器镜像、通过 dev_scripts/sign-assets.py 计算哈希并签名全部资产、再经 dev_scripts/upload-asset.py 上传至 GitHub 草稿发布,直至正式发布。读完后你可以完整复现 Dangerzone 发布经理在"发布日"执行的每一条命令,并理解每个自动化脚本在底层做了什么、为什么这样做。
一、release.md 在整体发布流程中的位置
Dangerzone 的一次新版本发布横跨多个支持平台(macOS、Windows、Debian/Ubuntu/Fedora Linux、Qubes OS),流程复杂。docs/developer/release/README.md 把整个流程拆成六个阶段:
- Pre-release
- Prepare build environments(构建环境准备)
- Sign and release container image(容器镜像构建与签名,在独立的
dangerzone-image仓库中完成) - Build release artifacts(构建发布产物)
- QA
- Release ← 本文主角
也就是说,release.md描述的是最后一步:前五个阶段完成后,发布负责人已经拥有了一份通过 QA 验证的完整资产集合(安装包、源码包、签名后的容器镜像),剩下的工作全部是"签名、打包、上传、宣布"。这也是为什么文档开篇只有一句话——"When confident that the release doesn't need any more changes"(当确信发布物不再需要任何修改)——就开始逐项执行。
执行本阶段前,可以先做一次版本一致性自检。以当前仓库为例,版本号0.11.0同时出现在以下四处,任何一处不一致都应在发布前修正(这正是 pre-release.md 中列出的任务):
- share/version.txt:
0.11.0 - pyproject.toml:
version = "0.11.0" - install/linux/dangerzone.spec:
Version: 0.11.0 - debian/changelog:
dangerzone (0.11.0) unstable; urgency=low
Pre-release 阶段还会将main分支顶点打上一个 RC 标签(例如git tag -s v0.11.1-rc1),供构建环境构建产物和 QA 验证;等到 QA 通过、发布日到来,才会打上本文要讲的正式版本标签。
二、步骤一:创建并推送 PGP 签名的 git tag
发布的第一项任务是为本版本创建一个PGP 签名的 git tag,例如针对v0.11.0:
git tag -s v0.11.0 git push origin v0.11.0-s参数让 git 使用 GPG 私钥对 tag 本身做签名(与 pre-release 阶段的-rc1标签采用同样的做法)。签名 tag 的意义在于:任何拉取该仓库的人都能用 Dangerzone 的公开发布公钥验证"这个版本号确实由官方发布经理标注",而不是来自被篡改的 fork。Dangerzone 的公开 PGP 公钥随仓库分发,见 share/freedomofpress-dangerzone.pub(附 share/freedomofpress-dangerzone.pub.asc 签名)。这个公钥后续还会被签名脚本用于对资产签名,形成贯穿 tag → 资产 → 校验和文件的完整信任链。
三、步骤二:用 git archive 打包源码归档
接下来从正式 tag 生成tar.gz源码归档:
export VERSION=$(cat share/version.txt) git archive --format=tar.gz -o dangerzone-${VERSION:?}.tar.gz --prefix=dangerzone/ v${VERSION:?}这条命令有几个值得注意的细节:
- 版本号取自 share/version.txt,而不是手写,避免 tag、归档文件名与仓库内容三者不一致;
${VERSION:?}是 bash 的参数展开保护:若VERSION为空,shell 会直接报错退出,而不是静默地生成一个名为dangerzone-.tar.gz的垃圾文件;--prefix=dangerzone/让解包后的顶层目录是dangerzone/,符合 FHS 和源码分发惯例;git archive ... v${VERSION}只归档该 tag 指向的干净快照,不含未提交改动和 git 元数据,保证可复现。
文档同时注明:这一步"已被自动化构建步骤覆盖"(This is covered by our automated build steps),即 CI 构建产物时已经生成过该归档,这里主要是让发布经理本地留有一份与 tag 严格对应的源码包,并作为后续签名资产清单的一部分。
四、步骤三:用 grype 对容器镜像做发布前扫描
Dangerzone 的核心安全机制是把文档转换放进隔离容器,容器镜像由独立的dangerzone-image仓库构建(对应总流程的第 3 阶段)。由于镜像构建完成到真正发布之间可能已经过去了一段时间,release.md 要求在发布前对最终产出的镜像再跑一次漏洞扫描:
docker pull anchore/grype:latest docker run --rm -v ./share/container.tar:/container.tar anchore/grype:latest /container.tar命令把本地保存的镜像 tarball(./share/container.tar)挂载进一次性 grype 容器做离线扫描,无需将镜像推送到任何 registry。这是典型的"发布前最后一道安全闸门":构建期扫描只能代表构建时刻的安全状态,发布时重扫才能覆盖中间新披露的 CVE。
五、步骤四:收集资产、计算 SHA-256 并 GPG 签名
这是整个发布阶段最核心的一步。文档要求把所有资产收集到一个目录,计算 SHA-256 哈希并签名,仓库提供了 dev_scripts/sign-assets.py 来自动化:
# Sign all the assets ./dev_scripts/sign-assets.py ~/release-assets/0.11.0/github --version 0.11.0(原文使用$VERSION变量,此处以当前版本0.11.0展开。)结合源码,可以看清这个脚本的完整行为:
1. 资产清单是硬编码的。脚本顶部定义了必须齐备的四类资产(dev_scripts/sign-assets.py):
DZ_ASSETS = [ "Dangerzone-{version}.msi", # Windows "Dangerzone-{version}-arm64.dmg", # macOS Apple Silicon "Dangerzone-{version}-i686.dmg", # macOS Intel "dangerzone-{version}.tar.gz", # 源码归档 ]ensure_assets_exist()会逐一检查这些文件是否存在,缺任何一个直接抛错终止——这相当于发布前的资产完整性门禁:少一个平台安装包就不允许进入签名环节。
2. 哈希文件格式兼容sha256sum。hash_assets()用hashlib.file_digest(f, "sha256")计算每个资产的摘要,输出{hexdigest} {filename}两空格分隔的格式(dev_scripts/sign-assets.py),与man sha256sum描述的sha256sum输出一致。这意味着用户可以一行命令校验所有下载内容:
sha256sum -c checksums-0.11.0.txt产物写入checksums-0.11.0.txt。
3. 签名策略:资产用分离签名,校验和文件用清签名。脚本对每个资产执行gpg --batch --yes --armor --detach-sig,生成独立的.asc文件(dev_scripts/sign-assets.py);唯一的例外是校验和文件本身——它对checksums文件使用--clearsign内嵌签名,然后把checksums-0.11.0.txt.asc重命名回checksums-0.11.0.txt,使校验和文件自身携带签名、保持原名发布(dev_scripts/sign-assets.py)。签名密钥指纹在脚本中固定为:
DZ_SIGNING_PUBKEY = "DE28AB241FA48260FAC9B8BAA7C9B38522604281"即必须用 Dangerzone 官方发布私钥签名,防止误用其他密钥。
执行成功后,~/release-assets/0.11.0/github目录内将是 4 个资产 + 4 个.asc分离签名 + 1 个已清签名的checksums-0.11.0.txt,共 9 个文件,构成一次完整、可被第三方独立验证的发布物集合。
六、步骤五:批量上传到 GitHub 草稿发布
签名完成后,把所有资产上传到已经创建好的draft(草稿)Release:
find ~/release-assets/0.11.0/github | xargs -n1 ./dev_scripts/upload-asset.py --token ~/token --draftdev_scripts/upload-asset.py 的单文件上传流程从源码看有几层防御与细节:
- 目标 Release 的三种定位方式(dev_scripts/upload-asset.py):
--tag按标签名查询、--release-id直接指定、或--draft自动取最新草稿。草稿定位逻辑get_latest_draft_release()会调用 GitHub Releases API,过滤出draft状态的 release,并且若发现多于一个草稿或零个草稿都直接抛错(dev_scripts/upload-asset.py)——这避免了把资产误传到旧草稿上; - Token 安全管理:
--token参数传的是 token文件路径(如~/token),脚本读取后以Bearer头认证;不传则交互式getpass输入(dev_scripts/upload-asset.py)。此外还有一行防御性断言assert args.file != args.token,确保不会把 token 文件本身当作资产上传; - 上传走 GitHub 的 uploads API:
POST https://uploads.github.com/repos/freedomofpress/dangerzone/releases/{release_id}/assets?name={filename},Content-Type为application/octet-stream。源码注释说明了一个限制:GitHub 该端点不接受 multipart 编码,因此只能整文件读入内存后一次性 POST(dev_scripts/upload-asset.py)。
find ... | xargs -n1把目录内每个文件(资产、签名、校验和)逐一喂给脚本,即草稿 Release 上会同时挂上全部 9 个文件。上传只是草稿阶段的"填装"——此时 Release 对外不可见,可以随时补传、重传。填装完毕后,文档要求把草稿 Release 的目标从 RC 状态更新为最终的正式 git tag(即第二步推送的v0.11.0),这样公开后页面展示的 tag、下载链接与签名资产三者对齐。
七、步骤六:同步网站与 README
在公开 Release 之前,还有两个文档层面的 PR 需要先提交:
- 网站 PR:向 Dangerzone 官网仓库提交 PR,让下载页链接指向新版本安装包;
- 仓库 PR:更新本仓库 README.md,把其中"Version"及指向 INSTALL.md 安装说明的链接对齐到新版本。
这两个 PR 此时只提交、不合并——它们与后面的 packages 仓库 PR 一起,作为"发布公开瞬间"的原子操作批量合并。另外,pre-release 阶段已要求用 docs/templates/release-notes-regular.md(常规版)或 docs/templates/release-notes-security.md(安全修复版)模板写好发布说明并送审,草稿 Release 的文案即来源于此。以当前仓库为例,INSTALL.md 中已经预留了指向v0.11.0各平台下载链接的段落(macOS arm64/i686、Windows MSI),说明"链接先行、资产随后"是该项目的一贯做法。
八、📣 正式发布:六个"合并/公开"动作
文档用单独的 "Publish the release!" 小节列出公开阶段的动作清单,其设计意图是把对外的可见性变更压缩到最短时间窗内,避免"安装包已可下载但 apt 源还没更新"这类中间态:
- 合并
packages仓库中的 PR——该仓库维护 Dangerzone 的 apt 源等软件包分发基础设施,合并后apt update && apt install dangerzone即可装到新版本; - 将 GitHub 草稿 Release 置为公开;
- 合并官网仓库(
dangerzone.rocks)与本仓库(dangerzone)中此前提交的 PR,让网站下载链接和 README 同时生效; - 在 Mastodon 官方账号发布发布公告;
- 如有新增受支持平台,扩展
check_repos.ymlCI 测试,覆盖新平台; - 手动触发
check_repos.ymlCI 测试并确认通过——这一步验证的是发布后各软件源/仓库中的版本确实可被 CI 检测到,是发布闭环的最终确认。
从第 2 步(公开 Release)到第 3 步(合并两个 PR)的顺序值得注意:先让 GitHub 上的资产可下载,再切换 apt 源和官网链接,能最大限度缩短"官网已指向新版本但资产 404"的窗口。
九、小结:一条签名信任链贯穿始终
把release.md的所有步骤串起来看,Dangerzone 的发布流程本质上是在维护一条可独立验证的信任链:
| 环节 | 产物 | 验证手段 |
|---|---|---|
| 版本固化 | PGP 签名 tagv0.11.0 | git 签名验证 + 发布公钥 |
| 源码分发 | dangerzone-0.11.0.tar.gz(git archive) | SHA-256 + GPG 分离签名 |
| 平台安装包 | .msi/.arm64.dmg/.i686.dmg | 同上,由sign-assets.py统一处理 |
| 容器镜像 | 签名镜像(独立仓库构建) | 发布前 grype 漏洞重扫 |
| 校验和 | checksums-0.11.0.txt(内嵌 clearsign) | sha256sum -c+ gpg 验签 |
| 软件源 | packages仓库 PR | check_repos.ymlCI 验证 |
所有签名动作都锚定在同一个官方密钥指纹(DE28AB241FA48260FAC9B8BAA7C9B38522604281)上,草稿发布 + 最后瞬间统一合并的编排则保证了对外可见状态的一致性。对需要为多平台发行版软件设计发布流程的项目而言,docs/developer/release/ 目录下这一整套"checklist 化 + 脚本化"的实践,加上 dev_scripts/ 中可直接复用的签名与上传脚本,是一份相当完整的参考实现。
【免费下载链接】dangerzoneTake potentially dangerous PDFs, office documents, or images and convert them to safe PDFs项目地址: https://gitcode.com/GitHub_Trending/da/dangerzone
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考