PyStudio Termux 构建踩坑记(三):Gitee、ModelScope 和 Cloudflare Pages 清单站
这篇讲下载入口怎么从“找个国内仓库放一下”变成一套更清楚的镜像架构。最早我想用 Gitee 做国内镜像,后来发现它适合轻量文本,不适合承载大量 release 附件和工具链包。
最后的分工更明确:GitHub Release 保存权威产物,ModelScope 承担大文件镜像,专用清单仓库保存 JSON,Cloudflare Pages 对外提供清单站。清单越轻,大文件镜像越独立,后面才容易按区域优化。
Gitee 的问题不是不能用,而是不适合在这套系统里承担大文件镜像角色。
Gitee 方案为什么失败
一开始选 Gitee 的理由很直接:大陆访问速度更好,轻量 JSON 清单应该很适合放上去。
实际踩坑后发现,Gitee release 附件有几个限制:
- 单个附件大小有限制。
- 仓库附件总容量有限制。
- 大量
.deb或工具链压缩包会很快逼近上限。 - 上传失败后重试成本高。
比如 C/C++ 工具链压缩包很容易超过 100 MiB。即使某些项目等级能放到更大的单文件,也挡不住总容量限制。把构建产物全推到 Gitee,不是长期方案。
所以 Gitee 只适合“轻量索引”,不适合“包仓库镜像”。后来我们干脆舍弃 Gitee 同步,避免架构里多一个半可用的入口。
ModelScope 更适合放大文件
ModelScope 的定位更适合承载较大的数据集文件,因此它被用来做大文件镜像:
1 | https://modelscope.cn/datasets/yourba/pystudio-termux-builds |
常见路径形态类似:
1 | repo/<owner>/<repo>/<release-tag>/<profile>/<arch>/<file> |
同步脚本使用的 token 应该保存在 CI secret 里,例如:
1 | MODELSCOPE_TOKEN=<MODELSCOPE_TOKEN> |
文章、日志和仓库里都不应该出现真实 token。
ModelScope 的角色很明确:
- 存放
.deb。 - 存放
Packages.xz镜像。 - 存放 bootstrap 产物镜像。
- 给国内下载提供备用路径。
它不负责构建,也不应该成为唯一权威源。
清单站应该只放轻量 JSON
大文件镜像解决后,还需要一个稳定入口告诉 app:
- 当前有哪些 bootstrap。
- 有哪些 package-set。
- 各架构的包索引在哪里。
- 哪些镜像优先。
- ModelScope 是否同步过。
- 调试索引在哪里。
这类文件都很小,适合静态站托管。
最后选了一个专用仓库:
1 | https://github.com/vg188/pystudio-termux-manifests |
这个仓库只放最终清单文件,例如:
1 | runtime-packages.json |
它不放源码,不放构建脚本,不放 patch,不放 .deb。
这样 Cloudflare Pages 可以直接绑定这个仓库,同步出来的站点就是纯清单站:
1 | https://pystudio-termux-manifests.pages.dev/runtime-packages.json |
为什么不直接用构建仓库的 GitHub Pages
构建仓库里有 workflow、脚本、patch、迁移清单、调试辅助文件。虽然 GitHub Pages 可以只部署一个生成目录,但从架构上看,它仍然把“构建系统”和“清单发布站”绑在同一个仓库。
专用清单仓库更干净:
- Cloudflare Pages 可以直接绑定仓库。
- 仓库内容就是线上内容。
- app agent 检查清单时不会看到构建系统文件。
- 后续切换 Pages、Workers、R2 或其他静态托管时更简单。
构建仓库只需要在 workflow 末尾把 dist/manifest-site 推到清单仓库。
GitHub Actions 跨仓库推送的坑
GITHUB_TOKEN 默认通常只适合操作当前仓库。构建仓库要推送到 vg188/pystudio-termux-manifests,需要一个额外 token。
做法是:
1 | MANIFESTS_REPOSITORY_TOKEN=<GITHUB_PAT> |
然后在 workflow 里使用:
1 | remote_url="https://x-access-token:${MANIFESTS_TOKEN}@github.com/${MANIFEST_SITE_REPOSITORY}.git" |
注意不要把 ${MANIFESTS_TOKEN} 打印到日志。GitHub 会帮忙遮蔽 secrets,但脚本里仍然应该避免 echo token。
清单里的镜像优先级
优先级不是写死在 App 里,而是跟随清单发布。下载失败时,App 才有可解释的回退路径。
现在默认清单入口是 Cloudflare Pages:
1 | { |
ModelScope 作为 manifest mirror 和大文件 mirror 存在:
1 | { |
实际 app 侧推荐策略:
- 先拉 Cloudflare Pages 清单。
- 清单失败时回退 GitHub raw。
- 下载大文件时优先使用清单里 priority 更低的镜像。
- 国内场景可优先选择
region = "CN"的 ModelScope。 - 校验 SHA256,失败就换镜像。
结论
这次镜像架构最后变成:
1 | GitHub Actions |
Gitee 的问题不是不能用,而是不适合承担这个系统里的大文件镜像角色。清单站越轻,越容易迁移;大文件镜像越独立,越容易按区域优化。