mitmproxy 版本发布全流程解析:从 Release Checklist 到多平台自动分发
本文基于 mitmproxy 仓库中的官方发布检查清单(release/README.md),结合仓库内完整的发布工具链(release/release.py、release/deploy.py、.github/workflows/release.yml 等)展开深度解析,为项目维护者与关注开源发布工程的读者呈现一套可运行的、覆盖"前置检查 → 构建打包 → 多渠道分发 → 收尾归档"的端到端发布流水线。
一、文档定位与发布范围
release/README.md 是 mitmproxy 维护团队使用的发布检查清单(Release Checklist)。它并非描述某个运行期功能,而是规范"如何从 main 分支产出一个新版本"的工程流程,内容覆盖:
- 发布前的版本预检与触发方式;
- 发布过程中需要的人工确认点;
- 发布完成后在各分发渠道(GitHub Releases、PyPI、Docker Hub、官方文档站、下载服务器、Microsoft Store、Homebrew)的产物去向与验收标准;
- 下一个开发周期的版本回填动作。
该清单与仓库根目录下的 .github/workflows/、release/ 目录中的脚本一一对应,属于"文档描述流程 + 代码实现流程"高度耦合的工程资产。
阅读提示:清单正文中出现的 GitHub Actions、Docker Hub、PyPI 等外部服务 URL 均指向托管平台的公共页面,以下行文以渠道名称与仓库内实现为准展开,具体平台操作以对应服务页面为准。
二、发布前置检查:触发之前的三道关卡
清单中的第 1 至第 4 步构成触发正式发布前必须完成的预检动作。
1. 确认 mitmproxy-rs 是否需要新发布
mitmproxy-rs 是 mitmproxy 的 Rust 底层组件(提供 HTTP/2、QUIC、msgpack 内容视图等能力)。由于主仓库的二进制与文档版本与其版本号存在联动,发布者需先判断该 Rust crate 是否需要先行发布新版本。
2. 核对 CHANGELOG.md 的 "Unreleased" 区段
清单要求确认 CHANGELOG.md 中 "Unreleased" 区段收录了所有待发布变更。当前仓库的 CHANGELOG 采用如下结构(CHANGELOG.md):
# Release History
<!-- ... 贡献者说明注释 ... -->
## Unreleased: mitmproxy next
该区段的标题文本 ## Unreleased: mitmproxy next 并非随意书写,而是 release/release.py 中版本发布脚本定位插入点的锚定字符串(详见下文第三节),因此发布前必须保证其存在且内容完整。
3. 从 GitHub UI 触发 Release workflow
清单指定通过 GitHub Actions 的 Release workflow(对应仓库中的 .github/workflows/release.yml)启动发布。该 workflow 仅支持 workflow_dispatch 手动触发,接收两个输入参数:
| 输入 | 类型 | 默认值 | 含义 |
|---|---|---|---|
version |
string | 必填 | 目标版本号,格式为 major.minor.patch |
skip-branch-status-check |
boolean | false |
是否跳过对分支 CI 状态的检查(便于 fork 测试等场景) |
其 jobs.release 运行在 ubuntu-latest,使用带推送权限的 GH_PUSH_TOKEN 检出代码(用于推送受保护的分支),配置 environment: deploy-release,最后执行发布脚本:
./release/release.py ${{ inputs.version }} ${{ inputs.skip-branch-status-check }}
也就是说:清单第 3 步在 UI 上填写版本号并点击后,真正的自动化工作由 release/release.py 承担。
4. 两次人工确认(approval)
workflow 启动后,运行结果并不会立即放行。清单明确指出需要发布者在 Actions 页面手动确认两次。这在源码中同样留有痕迹——release/release.py 在轮询 CI 时对状态为 waiting(等待审批)的工作流专门打印警示信息:
if workflow["status"] == "waiting":
print(f"⚠️ CI is waiting for approval: {workflow['html_url']}")
结合清单的"确认两次",可以理解为发布链路中存在两个需要人工把关的审批关卡(例如发布到包仓库/商店前的人工闸门),这是出于对公共发布渠道安全性的控制。
三、release/release.py:发布主脚本的自动化流水线
清单触发后执行的 release/release.py 是整个发布的"编排者"。它以两个命令行参数启动(version 与是否跳过分支状态检查),并在不使用任何第三方依赖(仅用标准库)的前提下完成一系列仓库变更与远端操作,实现细节如下。
1. 参数与运行环境自检
脚本首先对版本号做严格校验:
version = sys.argv[1]
assert re.match(r"^\d+\.\d+\.\d+$", version)
随后检查当前工作区是否干净、以及(除非显式跳过)目标分支的 CI 状态是否处于 success:
print("➡️ Working dir clean?")
assert not subprocess.run(["git", "status", "--porcelain"]).stdout
# 若 skip_branch_status_check 为 false,则请求 GitHub API
# 断言分支最近一次提交的 combined status 为 "success"
其中工作区检查通过 git status --porcelain 实现(release/release.py),仓库名可通过环境变量 GITHUB_REPOSITORY 覆盖,便于 fork 测试。
2. 自动更新 CHANGELOG 标题
脚本在 ## Unreleased: mitmproxy next 锚点之后插入带发布日期的版本标题(release/release.py):
date = datetime.date.today().strftime("%d %B %Y")
title = f"## {date}: mitmproxy {version}"
cl, ok = re.subn(r"(?<=## Unreleased: mitmproxy next)", f"\n\n\n{title}", cl)
assert ok == 1
这段逻辑直接印证了清单第 2 步"确保 CHANGELOG 的 Unreleased 区段已更新完毕"——因为自动化会把整段 Unreleased 内容固化为一个带日期的正式版本标题。
3. 重建 Web 前端资产
由于 mitmweb 的静态资源会被打包进发布产物,脚本以 npm ci 安装锁定依赖并执行发布专用构建命令 npm run ci-build-release(release/release.py),构建结果提交到 mitmproxy/tools/web 目录。这意味着发布主流程对 Web 前端采用可重复的、基于锁文件的干净构建,避免本地环境差异污染产物。
4. 回写版本号并完成发布提交
脚本读取 mitmproxy/version.py 并正则替换其中的版本字符串(release/release.py):
ver, ok = re.subn(r'(?<=VERSION = ")[^"]+', version, ver)
随后以 mitmproxy release bot 身份创建提交并打上 v{version} 标签(release/release.py):
git commit -a -m "mitmproxy {version}"
git tag v{version}
5. 分支差异处理:main 分支的版本前滚
发布脚本区分分支行为(release/release.py):若当前位于 main 分支,发布提交之后会立即前滚版本号并追加 .dev 后缀:
next_dev_version = f"{major_version + 1}.0.0.dev"
也就是说,发布 13.0.0 后,main 分支会回到 14.0.0.dev 并追加 "reopen main for development" 提交。这一逻辑与仓库当前 mitmproxy/version.py 中 VERSION = "13.0.0.dev" 的状态完全吻合——主线始终停留在"下一个未发布大版本的开发态"。
6. 推送、创建 GitHub Release 并触发打包流水线
脚本最终执行原子推送(branch + tag 一次完成),再通过 gh CLI 创建 GitHub Release(release/release.py),其 release notes 引用 release/github-release-notes.txt:
subprocess.run(["gh", "release", "create", tag_name,
"--title", f"mitmproxy {version}",
"--notes-file", "release/github-release-notes.txt"], ...)
随后以该 tag 为引用触发名为 main.yml 的工作流(对应 .github/workflows/main.yml),后者才真正负责构建各平台二进制并分发到下载服务器、PyPI、文档站(详见第四节)。
7. 发布后的自动验收与人工观察
推送之后脚本进入轮询循环(每 30 秒查询一次,见 release/release.py),等待 CI 全部完成;随后对关键渠道执行冒烟验收断言(release/release.py):
| 验收对象 | 断言方式 |
|---|---|
| GitHub Releases | GET /repos/{repo}/releases/tags/{tag} 返回 200 |
| PyPI | 查询 mitmproxy 的 release 列表,断言目标版本存在 |
| 文档归档站 | docs.mitmproxy.org/archive/v{major}/ 返回 200 |
| Docker Hub | 目标 {version} tag 的镜像存在 |
此外,main 分支发布还会额外断言 Docker latest tag 在最近两小时内更新过,以确认最新镜像确实被推送。
四、main.yml 流水线:二进制构建与多渠道分发
清单正文第六节("Once everything has been deployed"之后)逐渠道说明发布后的产物去向,其实现主体是触发 main.yml 工作流后执行的 release/deploy.py。该脚本依据 GITHUB_REF 区分 tag(正式版)与 branch(快照)两种发布形态,路径规则为:
- 正式版上传目录使用
v{version}形式的 tag; - 分支快照则上传到
branches/{branch}目录。
1. GitHub Releases 与仓库内 release notes
正式版发布时,GitHub Release 已由 release/release.py 自动创建(见第三节第 6 点)。release/github-release-notes.txt 作为发布说明模板,指向 CHANGELOG 并提示从官网下载页获取安装包。
2. PyPI:wheel 直传
release/deploy.py 在 tag 发布时,会从 release/dist/ 中挑出 mitmproxy-*-py3-none-any.whl 并通过 twine upload 推送至 PyPI。需要注意这里的发布对象是 wheel 包(而非源码包或通用 dist),因此最终用户通过 pip install mitmproxy 获得的是与本次 tag 完全一致的构建产物。
3. Docker 镜像
Docker 镜像的构建与推送同样由 main.yml 流水线完成。release/deploy.py 只负责二进制与文档的上传,而镜像由 CI 构建后推送到 Docker Hub 的 mitmproxy/mitmproxy 仓库,同时产出 {version} 精确 tag 与 latest 滚动 tag。
4. 文档站:stable + archive + dev 三层结构
release/deploy.py 内置的 upload_docs 函数(release/deploy.py)负责把 docs/public 同步到 S3 存储,并调用 AWS CloudFront 创建缓存失效(invalidation),确保新文档立即生效而不会被 CDN 缓存拦截。具体上传策略为:
| 场景 | 目标路径 |
|---|---|
| tag 发布 | /stable(稳定版文档)+ /archive/v{major}(按主版本归档) |
main 分支发布 |
/dev(开发版文档) |
其中 /archive/v{major}/ 正是 release/release.py 验收阶段断言可访问的地址,也解释了清单中"自动更新 stable 文档并创建归档版本"一句的含义。
5. 下载服务器:S3 与 R2 双上传
release/deploy.py 对二进制的上传分为两段(release/deploy.py):
- 始终将
release/dist/同步到 S3(snapshots.mitmproxy.org),覆盖 tag 与分支快照两种形态; - tag 发布时,额外同步到 Cloudflare R2(
downloadsbucket,对应正式下载站),通过--exclude *.msix排除微软商店安装包。
脚本注释提到"希望 R2 能从 S3 自动拉取但尚不可行,因此双上传",并说明 R2 token 只能在部署环境中使用。该双通道设计同时服务"正式下载"与"快照下载"两套入口。
6. Microsoft Store:MSIX 提交流程与审查周期
release/deploy-microsoft-store.py 是一个完整的 Store 提交脚本,只处理以 refs/tags/ 开头的引用。它通过 Azure AD 客户端凭据获取 OAuth token,调用 Microsoft Store 提交 API(manage.devcenter.microsoft.com),自动完成以下动作(对应文件各步骤):
- 获取应用信息、删除残留的 pending submission;
- 创建新提交,将旧包全部标记为
PendingDelete并加入新上传的installer.msix; - 将 MSIX 打包为 zip 上传至 Azure Blob(
x-ms-blob-type: BlockBlob); - 调用 commit 接口正式提交发布。
其环境变量包括 MSFT_APP_ID(应用 ID)、MSFT_TENANT_ID/MSFT_CLIENT_ID/MSFT_CLIENT_SECRET(Azure AD 凭据,secret 每 24 个月需在 Azure Portal 重建)以及可选的 MSFT_APP_FLIGHT(用于向子集用户灰度 CI 测试构建)。这与清单中"Store 有审查流程、二进制可能需要一天才上线"的说明直接对应——Store 侧的人审环节发生在仓库 CI 之外,因此发布者需要预留验收时间。
7. Homebrew:社区联动与手动兜底
清单指出 Homebrew 维护者通常会在一天内自动感知并合入新版本 casks;如确有必要,可在 macOS 上手动发起拉取请求:
brew bump-cask-pr mitmproxy
8. Website:补丁版本无需更新
清单强调:补丁(patch)版本不需要更新官网——官网会自动感知下载服务器上的新版本。只有大/中版本发布才需要在其独立的网站仓库中更新版本号与文档菜单。需要注意,官网源码(www 仓库)不属于当前仓库范围,相关修改步骤(更新站点配置中的版本、更新 header 文档菜单、执行 ./build、在 www-test 预览后运行 ./upload-prod)需在对应的独立仓库中完成。
五、为下一版本做准备:版本号前滚约定
清单第 59 至 61 行("Prepare for next release")要求发布者在 main 分支上完成"下一个主版本"的预置。从 release/release.py 的实现看,只要发布发生在 main 分支,这一动作是自动化完成的:发布提交之后脚本立即将 mitmproxy/version.py 中的 VERSION 改写为 f"{major_version + 1}.0.0.dev" 并提交。
这样的双提交模式带来一个重要约定:main 分支上的 VERSION 恒为 X.0.0.dev 形态的开发版本,仅当 release workflow 被触发并成功完成后才短暂出现正式版本号,随后又回到下一大版本的 .dev 态。结合 mitmproxy/version.py 的 get_dev_version() 逻辑可以进一步理解该字段的运行时语义:
- 在 git 仓库内且存在 tag 历史时,会执行
git describe --tags --long,若与最近 tag 之间存在提交距离(tag_dist > 0),则在版本字符串中追加(+{距离}, commit {短哈希}),标识"非 tag 版本"; - 若以 PyInstaller 冻结的二进制运行(
sys.frozen),则追加binary标识。
因此版本字段既是发布流程的写入目标,也是运行时用户可见版本字符串(mitmproxy --version)的数据源。
六、发布流程时序总结与维护建议
把清单与源码串起来,一次标准的 mitmproxy 主版本发布整体呈现如下时序:
- 维护者确认
mitmproxy-rs无待发版本,核对 CHANGELOG.md 的## Unreleased: mitmproxy next区段完备; - 在 GitHub UI 上手动触发 .github/workflows/release.yml,填写
version(可选勾选skip-branch-status-check); - workflow 在受保护分支上用推送 token 检出代码,执行 release/release.py,在 CI 环节等待两次人工确认;
release.py依次完成:工作区/CI 状态自检 → 以日期标题固化 CHANGELOG →npm ci+ci-build-release重建 web 资产 → 回写 mitmproxy/version.py → 提交mitmproxy {version}并打v{version}tag →(main 分支)前滚到{major+1}.0.0.dev并 reopen → 原子推送 →gh release create→ 以 tag 触发 .github/workflows/main.yml;main.yml构建全平台产物后运行 release/deploy.py:二进制同步至 S3 与 R2 下载服务器、wheel 直传 PyPI、文档同步至stable/archive/v{major}/dev并刷新 CDN;MSIX 则经 release/deploy-microsoft-store.py 提交 Microsoft Store 等待平台审查;release.py轮询至 CI 结束,并断言 GitHub Releases / PyPI / 文档归档 / Docker tag(main 发布还额外校验latest)均可用,输出All done;- 维护者人工验收各渠道下载链接,如需则执行
brew bump-cask-pr mitmproxy;补丁版本无需更新官网,大/中版本再到独立的官网仓库同步版本与文档菜单。
对于希望在本仓库基础上维护自己的发布流水线的读者,可将 release/release.py 的"零第三方依赖 + 纯标准库 + 断言驱动"风格作为参考范式:发布脚本的每一步都带显式断言与可读进度输出,把"清单上的待办"转化为"脚本中可失败即停机的检查点",从而把发布这一高风险操作尽量收敛为确定性、可重复、可审计的工程流程。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00