[简体中文] / English


Git 仓库迁移小记 - (尝试)逃离中心化的 GitHub

 

背景

之前, 我所有的 Git 仓库一直托管在我的 GitHub 账号 @lyc8503 上. 本来一切岁月静好, 但突然某一天, 毫无征兆地, 我的账号突然就被 GitHub 封了… 提了 Ticket 等了整整 20 天才给我解封, 现在过了 40 天我的账号状态还没有完全恢复正常.

"WHATEVER MICROSOFT IS DOING""WHATEVER MICROSOFT IS DOING"

微软———— 你 tmd 在搞什么, 我无偿给你贡献模型训练数据, 贡献社区活跃度, 你把 GitHub 的可用性做成一坨, 到处塞莫名其妙的 AI 功能, 给我自己手写的代码加 Copilot Co-Author, 现在还要来封我的账号👿

#90Days90IncidentsChallenge#90Days90IncidentsChallenge

回顾看来, 账号被封期间主要给我造成了这些影响:

  1. 之前公开分发源码的仓库全部返回 404, 许多用户通过邮件/博客评论区/聊天软件各种渠道联系我询问状况或索要代码, 回复起来比较费力
  2. 同时我所有的 issues/PRs 也都被迫停滞, 无法回复和跟进
  3. 我的博客 giscus 评论区因为依赖了 GitHub 炸了, www.lyc8503.net 因为在 GitHub Pages 上也无法访问
  4. Actions 无法运行, 我部署博客和其他网站 (如 status.lyc8503.net) 的流水线无法运行了, 必须本地再配一次环境
  5. 尽管代码在本地都有, 但 Gist/Wiki/Releases 这类数据还是被锁了, 我自己也拿不到; 我只有半年前比较过时的一份 GitHub Export 导出的数据
  6. 之前用于接收 donate 的 Buy me a coffee 因为我的 social media 链接 404 怀疑我的用途不真实, 也把我账号封了且多次沟通拒绝解封

被这么搞了一波, 看起来是时候从 GitHub 这个过于中心化且有点 enshittification 迹象的平台迁出, 找点别的替代方案了…

省流

我整了一个同步脚本 lyc8503/git-sync-scripts (GitHub/Codeberg), 用于同步我在不同平台上的仓库.

现在, 你可以在 Codeberg 上找到我了: lyc8503!

(流水账的)探索

OK, 初步总结一下, 所以现在要迁移我的 Git 仓库的话, 主要是需要满足这两个大需求:

  1. 存储我的 Git 仓库, 运行类似 Actions 的 CI/CD 流水线
  2. 公开分发我的代码, 且方便地接受和管理 issues/PRs

至于其他的部分: 评论区我已经迁移到 self-host 的 Waline, 原来的评论数据也已经(拷打 LLM)搓了个脚本转换导入进了 Waline 评论区, 检查了没问题; GitHub Pages 后续会逐渐迁出, 应该会迁移到 Cloudflare Pages; 接收 donate 我也会换个平台. 这些迁移和本文比较不相关, 也比较 trivial, 不再展开.

设想 1: 自建 Git 实例

首先, 作为刚斥巨资升级了 HomeLab 的我, 最直接的想法肯定是, 能不能直接 self-host 一个 GitHub 的替代品, 例如 GitLab, 直接放在 git.lyc8503.net 上提供服务, 一口气满足代码存储和协作开发两个需求.

经过一些简单的搜索和调研, 我发现似乎市面上也没有那么多可以自建的 Git 管理平台选择. 我找到的维护比较积极, 用户量不太少的也就只有 GitLab 和 Gitea 系列 (包括 Gogs/Gitea/Forgejo).

由于我之前被 GitLab 内置 CI 的 pipeline 语法折磨过, 我也不需要 GitLab 那么一堆重量级的 feature 来浪费我好几个 G 的 RAM, 我选择了比较轻量和自称透明开放的 Forgejo.

在我的 HomeLab 上开个 LXC 容器, 根据官方的 deb 包Actions 配置指南, 我很快就搭建起了一个私有的 Forgejo 实例, 并基于 podman 配置好了流水线的 Runner.

更方便的是, Forgejo Actions 的语法几乎兼容 GitHub Actions, 当我把我的博客仓库恢复到 Forgejo 后, 流水线几乎不需要修改就能跑起来:

在我 270K Plus 的 HomeLab 跑的博客构建比 GitHub Actions 快了五倍, 爽了在我 270K Plus 的 HomeLab 跑的博客构建比 GitHub Actions 快了五倍, 爽了

唯一的额外步骤是很多 Actions 需要访问国际互联网的资源, 要给整个容器配一个透明代理, 配好之后就和 GitHub-hosted Runner 体感很像了. Forgejo 的 Runner 虽然不是像 GitHub 一样的独立 VM, 但其推荐的 image 已经足够跑起大部分常规的流水线.


需求 1 已经完美解决了! 现在我只要找个 Cloudflare Tunnel 或者类似的内网转发, 把它暴露到公网, 大家就能来提 issue 和 PR 了, 问题完美解决, 本文完结.

对…对吗? 不对不对, 就算不提我的 HomeLab 时不时需要停机维护的可用性问题, 以及放弃我从不暴露 HomeLab 的服务到公网的安全性考虑, 即使我去租一个可靠的 VPS self-host 我的 Forgejo, 社区贡献者要向我提 issue/PR 的话, 他们必须现在我的实例上注册账号, Fork 仓库, 再提起 PR.

作为管理员, 我不得不维护一个 SMTP 发件服务器, 管理一大堆账号, 为贡献者提供仓库的存储, 提供 Actions 流水线运行所需的资源, 以及最麻烦的 - 处理可能出现的 Abuse, 并时刻担心不要有个人出来发博彩/键政/child porn 把我的网站爆破了; 作为贡献者, 他们需要离开熟悉的平台, 来我的 git 注册账号, 才能给我提交贡献, 后续所有 issue/PR 的跟进, 他们都得在我的 git 平台进行.

这显然太麻烦了, 同时会劝退本就为数不多的贡献者.

设想 1.1: Federation

对于上述的问题, 有很多人在评论和邮件私聊我提到各式各样的联邦方案, 我也自己做了一些搜索.

Forgejo 本身就支持一个基于 ActivityPub 的 ForgeFed 协议, 可以让用户像在 Misskey 上一样, 与其他 self-host 实例的内容交互. 理论上可以跨实例进行 Star/Follow/Merge Request 等操作.

然而… ForgeFed 的支持目前还是很实验性的状态: 首先最大的 Forgejo 实例 - Codeberg 自己都没有启用 ForgeFed.

其次, federation 相关的仓库 forgejo-contrib/federation 的 commits 也完全算不上是活跃, 同时大量的功能又在 WIP, 即使我自己的实例启用了 Federation, 也没有多少实际的可用功能.

除了 ForgeFed, 还有一些其他的, 例如基于 Nostr 的 ngit 或基于 ATProto 的 tangled, 不过也都处于尚未形成规模的早期阶段.

还有一个比较有意思的是 SourceHut, 它并非 federation, 但将很多的功能都绑定到邮箱, 用邮件实现 tickets 的追踪, 无需登录就能交互和使用, 页面和设计风格也很简洁, 甚至无 JS, 我看起来觉得挺喜欢的. 不过它也仍处于 alpha 状态, 并且和很多开发者的习惯有一些出入, 让我再观望一些时间.

设想 2: 迁移到其他平台

好吧, 既然 self-host 没法实现需求 2, 那有没有其他更靠谱的平台直接迁移过去得了呢…

GitLab 不仅有上述迁移成本大的问题, 还禁止中国用户使用, 最近也在搞愚蠢的 AI 大跃进, 直接 pass.

Codeberg 看起来还不错, 并且有越来越多的人迁移到 Codeberg; 缺点是官方提供的免费 quota 相对较少, 很多资源需要向社区人工申请, 并且我看有文章批评 Forgejo 的安全性和性能问题, 我对其是否能继续 scale 感到了一些担忧. 暂时也是观望, 不会直接迁移.

况且, 从一个单点平台迁移到另一个单点平台, 让我有种什么实际改进都没有的感觉, 所以再想想别的方案.

设想 3: 静态 Git 网页

我还有一个 idea 是, 我能不能像 Hexo 这种 SSG 博客, 每次更新后生成纯静态的 Git 网页, 然后找个 Cloudflare Pages 实现超低维护成本和超高可用性.

问 AI 新发现的冷知识: Git 支持一个 dumb protocol, 可以用一个普通的 http server host 一个可以 clone 的 Git 仓库: https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols

经过搜索, 也已经有一些现成的 Git 静态页面生成了: itsy-gitsy/git-arr/stagit

设想 4: 多平台多向同步

我本来我打算自己搓一个类似的 static site generator 的时候, 突然转念一想, 我把所有的 Git 仓库放到 git.lyc8503.net 之后, 似乎我的域名又成了一个单点.

假如哪天 Cloudflare 突然抽风把我账号干了, 甚至是域名被 takedown 了, 那我又要失联了, 不应该把鸡蛋放在一个篮子里.

于是顺着这个思路, 我想到了一个最终决定实施的方案: 把我的仓库写个脚本, 同步到多个平台上. 我也就不用大费周章迁移了, 也不用失去我现在 GitHub 上已有的用户, 算是半个渐进式的方案吧.

我会在每个平台都创建仓库, 贡献者可以在任意一个平台上 star/follow/提 issue/提 MR, 我合入代码后, 代码我会有个脚本自动从合入的平台将代码拉回, 同步到本地, 并同步到其他平台. 四舍五入是去中心化了.

具体的说, 脚本的逻辑大概是这样:

  1. 一次性准备工作: 手动在我的 self-host Git/GitHub/Codeberg 等多个托管网站上创建仓库, 调整必要的设置 (例如设置 desc/关闭 Codeberg 的 Actions, 防止 Actions 重复运行)
  2. 遍历自建 git 的公开仓库, 先 clone self-host 的仓库作为基础
  3. 遍历所有分支, 依次从所有公开 remote 进行 git fetch, 并尝试 fast-forward 本地的分支
  4. 如果有分叉则报错跳过, 提醒用户
  5. 将更新过后的仓库推送回所有远端, 以及本地的 self-host Git

实现好的脚本仓库在: lyc8503/git-sync-scripts (GitHub/Codeberg) 你就说纯手写的一堆 if 不酷吗

我目前选择里一个比较保守的做法, 没有尝试自动 Merge 解决冲突, 仅仅会 fast-forward, 自然也不用 force push, 应该是很保险的, 至少不会导致数据丢失. 这个脚本也只使用通用的 git over SSH 处理仓库内容, 相比 git over HTTPS 这样不用单独申请各个平台的 PAT Token, 还不太会遇到 429 Too Many Requests, 也可以避免接入 API 后写个 Bug 直接把仓库删了的事故.

现在的模式下, 我的所有私有仓库存储在本地自建的 git, 不会推送到任何公共平台. 公开仓库以自建的 git 仓库为基准, 尝试从所有远端拉去更新 (设想中主要是合入的 PR/MR), 再将更新同步到所有远端.

这样以我的本地自建 git 为准, 就算 GitHub 上 fork 了一些其他公开仓库用于 PR, 也会自动被排除在同步之外, 不会有影响.

这个方法已知的缺点是可能会导致 issue 和 PR 的讨论比较分散, 以及每个新仓库需要在所有平台手动创建, 所以我暂时只同步了 Codeberg, 没有放更多平台了, 以后再按需增加更多的.

小结

呼…写完了. 果咩马赛, 我的思路可能有些跳跃和混乱, 希望各位没有看的太晕.

在 Codeberg 上关注我喵谢谢喵!

本文采用 CC BY-NC-SA 4.0 许可协议发布.

作者: lyc8503, 文章链接: https://blog.lyc8503.net/post/git-migration/
如果本文给你带来了帮助或让你觉得有趣, 可以考虑赞助我¬_¬