稍候片刻,月出文自明。
- name: Checkout repository
uses: https://data.forgejo.org/actions/checkout@v6
- name: Set up pnpm
uses: https://github.com/pnpm/action-setup@v4
- name: Set up Docker Buildx
uses: https://github.com/docker/setup-buildx-action@v4- name: Set up Docker Buildx
uses: https://github.com/docker/setup-buildx-action@v4
- name: Log in to CodeFloe Container Registry
uses: https://github.com/docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.CODEFLOE_REGISTRY_USERNAME }}
password: ${{ secrets.CODEFLOE_REGISTRY_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: https://github.com/docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest
type=sha
- name: Build and push Docker image
uses: https://github.com/docker/build-push-action@v7
with:
context: .
push: true
platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}docker login codefloe.com -u <组织成员用户名>
# 在密码提示中输入具有 read:package 权限的 PAT。
docker pull codefloe.com/your-private-org/yohaku:latestgit push --atomic \
--force-with-lease="refs/heads/sync:$old_sync" \
--force-with-lease="refs/heads/main:$old_main" \
origin sync:sync main:main
前言
这篇文章的书写纯属偶然,我的 GitHub 因为账单强制送中,导致 Free 处于无法续费状态,私有仓库的 Workflow (啊没错就是我在前几篇文章里写的同步 Yohaku 上游和构建 Docker 镜像的那几个)完全不能用。搞得要在私有仓库跑 Workflow 的我只能遗憾从 GitHub 搬离去其他能跑私有项目 CI 的 Git 实例。(So, GitHub, fxxk you.avif)
当然,巧合的是,最近找到了一个服务器在欧洲的 Forgejo 实例——CodeFloe,对 AI 的态度较 Codeberg 而言较宽松,而最近他们在新加坡搞了个中转服务器,使得访问体验好了很多,而且诸如 CI/CD(Actions + CrowCI)、Weblate、Renovate、Pages、MCP 等配套服务一应俱全,于是不出意外地成了迁移的目的地。
把仓库从 GitHub 搬到 Forgejo 后,原来的 GitHub Actions 工作流也需要进行一番调整才可在 Forgejo Actions 上运行,于是抓起了 AI (6.1 Sol) 替我完成了这个迁移过程,接下来是它完成的内容。
这次迁移以 CodeFloe 托管的 Forgejo Actions 为目标,涉及三个工作流:同步 GitHub 上游代码、构建并发布 Docker 镜像,以及部署到 EdgeOne Makers。迁移后完成了本地语法检查和关键脚本验证;本文不将这些结果视为已在 CodeFloe runner 上完整运行成功的证明。
下面的配置方法可以作为迁移其他项目的参考。其中,runner 标签和基础镜像属于 CodeFloe 的具体环境,迁移到自建 Forgejo 时需要按实际配置调整。本文依据迁移时的工作流及截至 2026 年 11 月 8 日查阅的官方文档整理。
先确定工作流从哪里读取
Forgejo 默认读取仓库中的 .forgejo/workflows/。如果该目录不存在,才会回退读取 .github/workflows/。
因此,可以把需要迁移的文件移动到:
这里有一个容易忽略的影响:创建 .forgejo/workflows/ 后,Forgejo 不会继续读取 .github/workflows/ 中剩余的工作流。
如果原来有十个 GitHub 工作流,只迁移了三个,那么其余七个仍然可以留给 GitHub 使用,但不会自动在 Forgejo 上执行。迁移前应先确定哪些工作流需要在新平台运行。
同时,还需要在目标仓库设置中启用 Actions,并确认实例有可用的 runner。定时任务应将工作流放到默认分支;手动运行也应先确认目标实例的工作流发现规则。
明确指定 Action 的来源
GitHub Actions 中常见的写法是:
这依赖平台配置的默认 Action 来源。Forgejo 实例的默认来源可能与 GitHub 不同,CodeFloe 的默认 Action 来源也不应直接假定为 GitHub。
迁移时,可以使用完整 URL 消除歧义:
但完整 URL 只解决“从哪里下载”的问题。一个 Action 能被下载,不代表它能在 Forgejo 上正常工作。
需要继续检查它是否调用 GitHub API、是否写死了 github.com、是否依赖 GitHub 的缓存或构件服务,以及它需要怎样的 Node.js 和系统环境。
重新检查 runner,而不是只保留 ubuntu-latest
GitHub 托管 runner 通常预装了大量工具。Forgejo 的 runner 环境由实例管理员决定,即使都叫 ubuntu-latest,实际运行环境也可能不同。
对于同步代码、运行 Node.js CLI 这类任务,本次迁移通过容器固定基础环境:
这个镜像提供了项目需要的 Node.js、git 和 bash。其他项目如果需要 Python、编译工具或额外系统库,仍需自行准备。
Docker 构建则需要另外处理。容器里有 Docker CLI,不等于能够连接 Docker daemon。 CodeFloe 为此提供了 docker runner 标签:
这里明确指定 amd64,是因为项目现有 Dockerfile 中有固定为 x64 的依赖安装。只使用通用标签,可能被分配到不同架构的 runner。
本次针对 CodeFloe 的 Alpine Docker 环境,在执行 JavaScript Action 前安装依赖:
这段命令适用于 Alpine。自建 runner 如果使用 Debian,应改用对应的镜像或包管理方式。CodeFloe 的 arm64 Docker runner 还有独立限制,例如覆盖 container: 镜像可能导致 Docker daemon 不可用,应按平台文档配置。
区分仓库令牌、上游令牌和镜像仓库令牌
认证是这次迁移中最需要重新梳理的部分。原来在 GitHub 上能用的 GITHUB_TOKEN,不能直接承担新平台上的所有职责。
Forgejo 会为工作流创建临时令牌,提供 forgejo.token、FORGEJO_TOKEN 等入口,也保留了 github.token、GITHUB_TOKEN 等兼容名称。
这个自动令牌可以用于操作当前仓库,但有一个关键行为:
因此,不同任务应选择不同凭据:
操作
本次采用的凭据
拉取当前 Forgejo 仓库
自动任务令牌
推送部署适配分支,后续步骤继续部署
自动任务令牌
同步后推送 `main`,需要触发构建和部署
有仓库写权限的 CodeFloe PAT
拉取私有 GitHub 上游
有上游读取权限的 GitHub PAT
发布 CodeFloe 容器包
有包写权限的 CodeFloe PAT
例如,同步工作流需要使用 PAT checkout,使后续推送能够触发下游工作流:
GitHub 上配置的 Secrets 不会随 Git 仓库自动迁移,必须在 CodeFloe 中重新配置。
另外,GitHub 的:
不能作为 Forgejo 中授权成功的依据。Forgejo 对部分权限字段的处理与 GitHub 不同,实际权限应根据自动令牌、PAT 范围、组织权限和分支保护规则确定。使用 OIDC 的工作流也需要单独适配,Forgejo 使用的是 enable-openid-connect 配置。
Docker 工作流要同时迁移构建环境和发布目标
原工作流将镜像发布到 GHCR,认证通常使用 GitHub 用户和 GITHUB_TOKEN。如果目标改为 CodeFloe 容器仓库,就需要调整镜像地址和登录凭据。
可以在工作流中明确声明镜像路径:
镜像路径应使用小写。如果需要发布到某个组织,也不能继续直接使用 ${{ github.repository }},因为代码仓库所属者与镜像所属组织未必相同。
构建步骤可以保留 Docker 官方 Actions:
这里显式初始化 Buildx,并关闭依赖 GitHub 专用服务的构建记录上传和摘要。原配置如果使用了 type=gha 缓存,也应重新确认目标实例是否支持,再决定替换为 registry 缓存或其他方案。
上述 YAML 是工作流片段,需要与前面的 runner、依赖准备和 checkout 步骤组合使用;项目特有的构建参数也需要自行保留。
镜像私有性由所有者决定
迁移容器仓库时,还需要确认访问权限。Forgejo 的包属于用户或组织,不属于代码仓库。
因此,私有代码仓库构建出的镜像,并不会自动成为私有镜像。按照 Forgejo 当前文档,公开组织下的包可以公开读取,私有组织下的包则只允许组织成员读取。
如果希望代码保持原有位置、镜像单独私有,可以创建专门的私有组织,并发布到:
发布用账号需要有该组织的包写权限,PAT 需要允许访问私有资源,不能选择仅公开资源的范围。部署服务器则需要先用有读取权限的组织成员账号登录,再拉取镜像:
将新镜像发布到私有组织,也不会自动改变旧地址下已经发布的包,需要另行处理旧包及部署引用。
同步工作流适合检查 Git 操作本身
原来的同步流程使用第三方 Action 拉取 GitHub 上游,再通过另一个 Action 执行 rebase。这类 Action 可能把目标平台、仓库地址或认证方式写死。
当流程主要由 Git 操作组成时,直接使用 Git 命令通常更容易检查迁移后的行为。本次保留的流程是:
这次改写特别处理了几个问题:
最后一项通过显式 lease 和原子推送实现:
这要求在操作前记录远端分支的提交号。远端支持原子推送时,两个分支可以一起更新;如果运行期间分支发生变化,推送会拒绝覆盖。
rebase 会改变提交历史,是否适合使用仍应遵循项目原有协作方式,并确认分支保护规则允许相应更新。
部署工作流需要验证命令的真实结果
EdgeOne 部署保留了原来的业务步骤:将 main 合并到适配分支,更新子模块,推送分支,再构建并部署。
这里的迁移重点是固定运行环境、使用正确凭据,以及可靠判断部署结果。例如:
pipefail 可以避免部署命令失败后,被成功执行的 tee 掩盖退出状态。
有了 JSON 输出,应解析结构化结果,检查 status 和访问地址,而不是仅靠字符串搜索判断成功。EdgeOne 返回的访问 URL 可能带有必要的查询参数,写入日志和任务摘要时应完整保留。
对于 concurrency,则需要确认目标 Forgejo 版本支持情况。本次同步、镜像发布和部署分别配置了并发组,并设置 cancel-in-progress: false,避免新的触发随意中断正在进行的发布。
最后验证的应是行为,而不只是 YAML
迁移后可以分三层验证。
第一层是静态检查:YAML 能否解析、字段是否正确、Shell 是否存在语法错误,以及 Action URL 是否明确。这一层能发现配置错误,但不能证明认证和网络访问正常。
第二层是脚本行为验证。本次在临时 Git 仓库中验证了正常同步、重复运行、测试模式、合并冲突、rebase 冲突和并发更新保护;部署脚本则覆盖了成功结果、错误结果、CLI 非零退出和无效 JSON。
第三层是在目标平台实际运行。先通过 workflow_dispatch 验证 runner 和凭据,再检查真实的镜像发布、私有镜像拉取,以及推送 main 后能否触发完整链路。本文所记录的迁移已完成前两层,第三层仍需要目标平台的实际运行记录确认。
排查时,可以根据现象缩小范围:
现象
优先检查
工作流没有出现
Actions 是否启用、目录位置、默认分支上的文件
任务一直排队
runner 标签和架构是否匹配
Action 下载失败
完整 URL、版本引用和网络访问
提示缺少 Node.js 或 bash
job 容器的运行环境
无法连接 Docker daemon
是否使用平台支持的 Docker runner
Registry 登录成功但推送失败
PAT 范围、镜像组织权限和命名空间
同步推送后没有触发部署
推送是否使用了自动任务令牌
私有仓库对应的镜像仍可匿名拉取
镜像所属用户或组织的可见性
这次迁移得到的经验是:保留能复用的 YAML 和构建步骤,同时逐项确认它们依赖的平台能力。目录、runner、认证、镜像仓库和下游触发都验证清楚后,原来在 GitHub 上运行的自动化流程,才能在 Forgejo 上保持预期行为。
参考资料