SuperAPI AI Directory

ci-workflow-sync

Deployment & operations

View repository

Updated 10-15 23:48

About this skill

FastGPT CI Workflow Dual-Track Synchronization

FastGPT maintains two sets of CI workflows simultaneously:

  • .github/workflows/ — runs on GitHub-hosted runners , publishes to ghcr.io + Alibaba Cloud + Docker Hub, aimed at the open-source community.
  • .forgejo/workflows/ — runs on internal Forgejo + self-hosted runners , publishes to the registry bundled with Forgejo, aimed at internal image distribution. The internal environment requires proxies, mirror sources, and a self-hosted registry.

The two workflows with the same name must remainfunctionally consistent: build steps, test logic, and trigger conditions aligned. However, the Forgejo version needs to remove GitHub-specific capabilities (attestations, github-script, PR comments) and inject internal proxy/mirror configuration.

Why do this: the internal Forgejo runner cannot access ghcr.io / Alibaba Cloud / Docker Hub login credentials, and has no GitHub PR comment API; but the build and tests themselves must run successfully on the internal network. So it is not "copy-paste", but "equivalent rewriting".

When to use this skill

  • Modifying .github/workflows/ any workflow file under
  • Adding a new build-*.yml / test-*.yaml workflow
  • Changes involve image registry, artifact, permissions, runs-on, trigger
  • When reviewing a PR, finding that only the GitHub side was changed and the Forgejo side was left untouched

Current pairing list (verify before making changes)

Existing Forgejo counterparts (9, must be synchronized when changed):

GitHub Forgejo Type
build-fastgpt.yml ✅ Image build
build-code-sandbox.yml ✅ Image build
build-agent-sandbox.yml ✅ Image build
build-fastgpt-ide-agent.yml ✅ Image build
build-marketplace.yml ✅ Image build
build-mcp-server.yml ✅ Image build
test-fastgpt.yaml ✅ Test + coverage
test-sandbox.yaml ✅ Test
test-rust-agent.yaml ✅ Test (currentlycompletely identical, no conversion)

The list will change as the repository evolves. Before each operation, use ls .forgejo/workflows/ to verify the actual files; do not rely solely on the table above.

GitHub-only, currently with no Forgejo counterpart (13, usually no synchronization needed):

auto-close-issue.yml、build-admin.yml、build-browser-sandbox.yml、build-docs.yml、build-sso-service-image.yml、helm-release.yaml、preview-admin-build.yml、preview-admin-push.yml、preview-docs-build.yml、preview-docs-push.yml、preview-fastgpt-build.yml、preview-fastgpt-push.yml、test-fastgpt-pro.yaml

These are GitHub-exclusive capabilities (PR preview deployment, docs site build, auto-close issue, Pro edition testing), not needed internally. But if the user creates a newbuild image or run testsworkflow, you should proactively ask whether a Forgejo version needs to be created (see the decision tree below).

Decision tree

用户改了 .github/workflows/<name>
  │
  ├─ .forgejo/workflows/<name> 存在?
  │    ├─ 是 → 按【转换模式参考表】同步已有配对
  │    └─ 否 → 该 workflow 是否属于"构建 OCI 镜像 / 跑单元测试 / 内网需要复用"?
  │         ├─ 是 → 询问用户是否新建 Forgejo 版本;若同意,基于 GitHub 版本改写
  │         └─ 否(preview/docs/admin/helm/auto-close 等专属能力)→ 无需同步,告知用户原因
  │
  └─ 用户改的是 .forgejo/workflows/<name>(罕见)
       → 反向核对 .github/workflows/<name> 是否也需要对应改动

Determine whether a new Forgejo version needs to be created

Usually needed:

  • build-*.yml — build and push OCI images (internal deployment depends on it)
  • test-*.yaml — run unit tests / integration tests (internal PR checks depend on it)

Usually not needed:

  • preview-*-push/build.yml — PR preview deployment to GitHub Pages / Vercel
  • build-docs.yml — docs site build
  • helm-release.yaml — Helm chart release
  • auto-close-issue.yml — GitHub issue auto-close
  • Any workflow strongly dependent on GitHub API (actions/github-script, PR comments,gh CLI)

When unsureask the user, do not decide on the user's behalf.

Conversion Pattern Reference Table (GitHub → Forgejo)

The following 11 patterns are distilled from real diffs in the repository. When syncing, compare them one by one;build-type workflows usually match all of them, while test-type workflows match only some(e.g., test-rust-agent.yaml is currently identical on both sides, with zero conversions).

Pattern 1 · Container Registry Address

GitHub hardcodes ghcr.io, while Forgejo uses ${{ github.server_url }} to dynamically derive the registry host.

# GitHub
registry: ghcr.io
password: ${{ secrets.GITHUB_TOKEN }}
# outputs / tags 里
ghcr.io/${{ github.repository_owner }}/<repo>:<tag>

# Forgejo(需先在 step 里算出 REGISTRY)
- name: Set platform variables
  run: |
    REGISTRY="${{ github.server_url }}"
    REGISTRY="${REGISTRY#https://}"
    REGISTRY="${REGISTRY#http://}"
    echo "REGISTRY=${REGISTRY}" >> "$GITHUB_ENV"
    echo "SOURCE_URL=${{ github.server_url }}/${{ github.repository }}" >> "$GITHUB_ENV"
# 之后
registry: ${{ env.REGISTRY }}
password: ${{ secrets.REGISTRY_TOKEN }}
${{ env.REGISTRY }}/${{ github.repository_owner }}/<repo>:<tag>

Pattern 2 · Remove External Registry Login

GitHub logs in to ghcr.io + Alibaba Cloud + Docker Hub at the same time and tags multiple sets; Forgejo only logs in to the self-hosted registry,deletethe login steps for Alibaba Cloud and Docker Hub and the corresponding tag environment variables.

# GitHub(要删)
- name: Login to Ali Hub
  uses: docker/login-action@v3
  with:
    registry: registry.cn-hangzhou.aliyuncs.com
    username: ${{ secrets.FASTGPT_ALI_IMAGE_USER }}
    password: ${{ secrets.FASTGPT_ALI_IMAGE_PSW }}
- name: Login to Docker Hub
  uses: docker/login-action@v3
  with:
    username: ${{ secrets.DOCKER_HUB_NAME }}
    password: ${{ secrets.DOCKER_HUB_PASSWORD }}
# env 计算里
echo "Ali_Tag=..." >> $GITHUB_ENV
echo "Ali_Latest=..." >> $GITHUB_ENV
echo "Docker_Hub_Tag=..." >> $GITHUB_ENV
echo "Docker_Hub_Latest=..." >> $GITHUB_ENV
# TAGS 拼接里
TAGS="$(echo -e "${Git_Tag}\n${Git_Latest}\n${Ali_Tag}\n${Ali_Latest}\n${Docker_Hub_Tag}\n${Docker_Hub_Latest}")"

# Forgejo(只留 Git_*)
TAGS="$(echo -e "${Git_Tag}\n${Git_Latest}")"

Pattern 3 · Action Reference Prefix

GitHub uses actions/*, while Forgejo uses forgejo/*(Forgejo Actions is compatible with GitHub Actions syntax, but its own action images are under the forgejo namespace).

# GitHub
uses: actions/upload-artifact@v4
uses: actions/download-artifact@v4
uses: actions/github-script@v7   # Forgejo 没有对应,整个 step 要删或改写(见模式 10/11)

# Forgejo
uses: forgejo/upload-artifact@v4
uses: forgejo/download-artifact@v4

Pattern 4 · permissions Block

Remove GitHub-specific attestation / id-token / issue / PR write permissions. The Forgejo runner does not do SLSA attestation, nor does it write PR comments.

# GitHub(要删的行)
permissions:
  attestations: write
  id-token: write
  issues: write
  pull-requests: write
# contents: read 通常两边都保留

Pattern 5 · Internal Network Proxy + Mirror Source Injection

The Forgejo runner is on the internal network, so pulling docker.io / npm / apt packages must go through the proxy and mirror. Everybuild jobmust have these two sections added at the beginning.vars.AB_* is a Forgejo repository variable.

# Forgejo 每个 build job 开头加
- name: Capture proxy envs from variables
  run: |
    echo "HTTP_PROXY=${{ vars.AB_HTTP_PROXY }}" >> "$GITHUB_ENV"
    echo "HTTPS_PROXY=${{ vars.AB_HTTPS_PROXY }}" >> "$GITHUB_ENV"
    echo "NO_PROXY=${{ vars.AB_NO_PROXY }}" >> "$GITHUB_ENV"
    echo "APT_MIRROR_UBUNTU=${{ vars.AB_APT_MIRROR_UBUNTU }}" >> "$GITHUB_ENV"
    echo "APT_MIRROR_DEBIAN=${{ vars.AB_APT_MIRROR_DEBIAN }}" >> "$GITHUB_ENV"
    echo "APT_MIRROR_DEBIAN_SECURITY=${{ vars.AB_APT_MIRROR_DEBIAN_SECURITY }}" >> "$GITHUB_ENV"
    echo "NPM_REGISTRY=${{ vars.AB_NPM_REGISTRY }}" >> "$GITHUB_ENV"

- name: Write buildkitd config
  run: |
    REGISTRY_HOST="${{ github.server_url }}"
    REGISTRY_HOST="${REGISTRY_HOST#https://}"
    REGISTRY_HOST="${REGISTRY_HOST#http://}"
    echo "REGISTRY_HOST=${REGISTRY_HOST}" >> "$GITHUB_ENV"
    mkdir -p /tmp/buildkit
    cat > /tmp/buildkit/buildkitd.toml << EOF
    [registry."docker.io"]
      mirrors = ["mirrors.sangfor.com"]
    [registry."${REGISTRY_HOST}"]
      http = true
      insecure = true
    EOF

If a test-type workflow needs to download external binaries (such as MongoDB memory server), it must also inject the proxy and pre-download (see Pattern 11).

Pattern 6 · buildx driver-opts + config

GitHub uses a single line driver-opts: network=host; Forgejo needs to put the proxy, GODEBUG, and buildkitd config all into it.

# GitHub
driver-opts: network=host

# Forgejo
driver-opts: |
  network=host
  env.GODEBUG=madvdontneed=0
  env.HTTP_PROXY=${{ env.HTTP_PROXY }}
  env.HTTPS_PROXY=${{ env.HTTPS_PROXY }}
  env.NO_PROXY=${{ env.REGISTRY_HOST }}
config: /tmp/buildkit/buildkitd.toml

The build step also needs to pass through build-args:

# Forgejo docker/build-push-action
build-args: |
  HTTP_PROXY=${{ env.HTTP_PROXY }}
  HTTPS_PROXY=${{ env.HTTPS_PROXY }}
  NO_PROXY=${{ env.NO_PROXY }}
  APT_MIRROR_UBUNTU=${{ env.APT_MIRROR_UBUNTU }}
  APT_MIRROR_DEBIAN=${{ env.APT_MIRROR_DEBIAN }}
  APT_MIRROR_DEBIAN_SECURITY=${{ env.APT_MIRROR_DEBIAN_SECURITY }}
  NPM_REGISTRY=${{ env.NPM_REGISTRY }}
env:
  GODEBUG: madvdontneed=0

Pattern 7 · artifact Download Method

Forgejo's download-artifact does not support pattern + merge-multiple, so it mustexplicitly download each architecture's digest by name。

# GitHub(多架构 merge)
- uses: actions/download-artifact@v4
  with:
    path: ${{ runner.temp }}/digests
    pattern: digests-<repo>-${{ github.sha }}-*
    merge-multiple: true

# Forgejo(逐个下载,continue-on-error 防缺架构)
- uses: forgejo/download-artifact@v4
  with:
    name: digests-<repo>-${{ github.sha }}-amd64
    path: ${{ runner.temp }}/digests
- uses: forgejo/download-artifact@v4
  with:
    name: digests-<repo>-${{ github.sha }}-arm64
    path: ${{ runner.temp }}/digests

Pattern 8 · runs-on Matrix Default Value

GitHub uses || 'ubuntu-24.04' at the job level as a fallback; Forgejo puts the default value into each entry of the matrix, and the job level directly uses matrix.runs-on。

# GitHub
strategy:
  matrix:
    archs:
      - runs-on: ubuntu-24.04   # 可能省略
      - runs-on: arm64-runner
runs-on: ${{ matrix.runs-on || 'ubuntu-24.04' }}

# Forgejo
strategy:
  matrix:
    archs:
      - runs-on: ubuntu-24.04   # 每条都要显式写
      - runs-on: arm64-runner
runs-on: ${{ matrix.runs-on }}   # 不兜底

Pattern 9 · workflow_dispatch Version Input for Build-Type Workflows

GitHub relies only on tag triggers; Forgejo additionally supports workflow_dispatch manually passing the version number, and branches in the version parsing script.

# Forgejo 顶层 on
on:
  push:
    branches: [main]
    tags: ['v*']
  workflow_dispatch:
    inputs:
      version:
        description: 'Image version tag (e.g. v1.0.0)'
        required: true
        type: string

# 版本解析 step(用 step output 传给下游 job)
- name: Validate release version
  id: version
  env:
    EVENT_NAME: ${{ github.event_name }}
    REF_TYPE: ${{ github.ref_type }}
    TAG_VERSION: ${{ github.ref_name }}
    INPUT_VERSION: ${{ github.event.inputs.version }}
  run: |
    if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
      VERSION="$INPUT_VERSION"
    elif [[ "$REF_TYPE" == "tag" ]]; then
      VERSION="$TAG_VERSION"
    else
      echo "::error::Workflow must be triggered by a tag or workflow_dispatch. Current event: ${EVENT_NAME}"
      exit 1
    fi
    if [[ ! "$VERSION" =~ ^v ]]; then
      echo "::error::Version must start with v. Current value: ${VERSION}"
      exit 1
    fi
    echo "version=${VERSION}" >> "$GITHUB_OUTPUT"

Pattern 10 · Delete detect-changes job (github-script)

GitHub uses actions/github-script@v7 to run a piece of JS that decides which test scopes to run based on the files changed in the PR; Forgejo does not have github-script,delete the entire detect-changes job, and downstream jobs remove needs: detect-changes and if: needs.detect-changes.outputs.* conditions, changing to always run all scopes.

# GitHub(整个 job 删)
detect-changes:
  runs-on: ubuntu-latest
  outputs:
    run_global: ${{ steps.scope.outputs.run_global }}
    # ...
  steps:
    - uses: actions/github-script@v7
      with:
        script: |
          // 根据 PR 文件判断 scope...

# 下游 job
test-global:
  needs: detect-changes
  if: ${{ needs.detect-changes.outputs.run_global == 'true' }}

# Forgejo
test-global:            # 无 needs,无 if
test-service:           # 无 needs,无 if
test-app:               # 无 needs,无 if
report-coverage:
  needs: [test-global, test-service, test-app]
  if: ${{ always() }}   # 不再依赖 should_report

Pattern 11 · Rewrite PR Coverage Comment as Step Summary

GitHub uses actions/github-script to call github.rest.issues.createComment to post a coverage table on the PR; Forgejo does not have a PR comment API, so change it toan inline node script writing to GITHUB_STEP_SUMMARY(it only appears in the workflow run summary, and is not posted to the PR). At the same time, artifact downloads must also be changed to download one by one by name + continue-on-error。

# GitHub
- uses: actions/download-artifact@v4
  with:
    path: coverage-artifacts
    pattern: coverage-*
- name: Report Coverage
  if: github.event_name == 'pull_request'
  uses: actions/github-script@v7
  with:
    script: |
      const fs = require('fs');
      const path = require('path');
      // ... 拼 markdown 表格
      await core.summary.addRaw(body).write();
      await github.rest.issues.createComment({ ... });

# Forgejo
- uses: forgejo/download-artifact@v4
  with:
    name: coverage-global
    path: coverage-artifacts/coverage-global
  continue-on-error: true
- uses: forgejo/download-artifact@v4
  with:
    name: coverage-service
    path: coverage-artifacts/coverage-service
  continue-on-error: true
- uses: forgejo/download-artifact@v4
  with:
    name: coverage-app
    path: coverage-artifacts/coverage-app
  continue-on-error: true
- name: Generate Coverage Summary
  if: github.event_name == 'pull_request'
  run: |
    node -e "
    const fs = require('fs');
    const path = require('path');
    // ... 同样拼 markdown 表格
    fs.writeFileSync(process.env.GITHUB_STEP_SUMMARY, body, { flag: 'a' });
    "

When test-type workflows download external binaries such as MongoDB memory server, they must also go through the proxy:

# Forgejo test job 额外加
- name: Capture proxy envs from variables
  run: |
    echo "HTTP_PROXY=${{ vars.AB_HTTP_PROXY }}" >> "$GITHUB_ENV"
    echo "HTTPS_PROXY=${{ vars.AB_HTTPS_PROXY }}" >> "$GITHUB_ENV"
    echo "NO_PROXY=${{ vars.AB_NO_PROXY }}" >> "$GITHUB_ENV"
    echo "MONGOMS_DOWNLOAD_DIR=$HOME/.cache/mongodb-binaries" >> "$GITHUB_ENV"
- name: Pre-download MongoDB binary
  run: |
    MONGODB_VERSION="7.0.14"
    CACHE_DIR="$HOME/.cache/mongodb-binaries/mongodb-linux-x86_64-ubuntu2204-${MONGODB_VERSION}"
    if [ -f "${CACHE_DIR}/mongod" ]; then
      echo "MongoDB binary already cached"; exit 0
    fi
    mkdir -p "${CACHE_DIR}"
    curl --proxy "${{ vars.AB_HTTP_PROXY }}" -sL \
      "https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2204-${MONGODB_VERSION}.tgz" \
      | tar xz -C /tmp
    cp /tmp/mongodb-linux-x86_64-ubuntu2204-${MONGODB_VERSION}/bin/mongod "${CACHE_DIR}/mongod"

Complete Before/After Snippets

With build-fastgpt.yml as an example of the release job registry login section (real diff excerpt):

# === GitHub 原版 ===
  release:
    needs: build
    runs-on: ubuntu-24.04
    permissions:
      packages: write
      attestations: write        # ← Forgejo 删
      id-token: write            # ← Forgejo 删
    steps:
      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Login to Ali Hub           # ← Forgejo 整段删
        uses: docker/login-action@v3
        with:
          registry: registry.cn-hangzhou.aliyuncs.com
          username: ${{ secrets.FASTGPT_ALI_IMAGE_USER }}
          password: ${{ secrets.FASTGPT_ALI_IMAGE_PSW }}
      - name: Login to Docker Hub        # ← Forgejo 整段删
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_HUB_NAME }}
          password: ${{ secrets.DOCKER_HUB_PASSWORD }}
      - uses: actions/download-artifact@v4
        with:
          pattern: digests-${{ matrix.sub_routes.repo }}-${{ github.sha }}-*
          merge-multiple: true

# === Forgejo 同步后 ===
  release:
    needs: build
    runs-on: ubuntu-24.04
    permissions:
      packages: write
      # 无 attestations / id-token
    steps:
      - name: Set platform variables
        run: |
          REGISTRY="${{ github.server_url }}"
          REGISTRY="${REGISTRY#https://}"
          REGISTRY="${REGISTRY#http://}"
          echo "REGISTRY=${REGISTRY}" >> "$GITHUB_ENV"
          echo "SOURCE_URL=${{ github.server_url }}/${{ github.repository }}" >> "$GITHUB_ENV"
      - name: Capture proxy envs from variables
        run: |
          echo "HTTP_PROXY=${{ vars.AB_HTTP_PROXY }}" >> "$GITHUB_ENV"
          echo "HTTPS_PROXY=${{ vars.AB_HTTPS_PROXY }}" >> "$GITHUB_ENV"
          echo "NO_PROXY=${{ vars.AB_NO_PROXY }}" >> "$GITHUB_ENV"
          # ... 其余 AB_* 变量
      - name: Write buildkitd config
        run: |
          # ... 写 buildkitd.toml(见模式 5)
      - name: Login to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.REGISTRY_TOKEN }}
      # 无 Ali Hub / Docker Hub login
      - name: Download digests
        uses: forgejo/download-artifact@v4
        with:
          name: digests-${{ matrix.sub_routes.repo }}-${{ github.sha }}-amd64
          path: ${{ runner.temp }}/digests
      - uses: forgejo/download-artifact@v4
        with:
          name: digests-${{ matrix.sub_routes.repo }}-${{ github.sha }}-arm64
          path: ${{ runner.temp }}/digests

Synchronization workflow

  1. Read the GitHub version: open .github/workflows/<name>, understand what it does.
  2. Diff existing pair: if .forgejo/workflows/<name> already exists, first diff both sides to see the current diff baseline, then layer on the user's GitHub-side changes.
  3. Apply conversion patterns one by one: compare in the order 1→11 of the 【Conversion Pattern Reference Table】; build-type ones usually all match, test-type ones partially match.
  4. Keep business logic unchanged: build steps, test commands, matrix dimensions, and version number calculation logic should be identical; only change "where it runs / where it pushes to / which action is used".
  5. YAML validation: after syncing, use python -c "import yaml; yaml.safe_load(open('.forgejo/workflows/<name>'))" or actionlint to validate syntax (the repository does not enforce it, but it can avoid low-level indentation errors).
  6. Inform the user: list which points on the Forgejo side were changed in this sync, and whether any GitHub-only capabilities were removed (requires user confirmation that the intranet really does not need them).

Verification checklist

  • .forgejo/workflows/<name> and .github/workflows/<name> 'sbuild/test business stepsare consistent.
  • The Forgejo version has no ghcr.io hardcoded, switched to ${{ env.REGISTRY }}。
  • The Forgejo version has no Alibaba Cloud / Docker Hub login step or corresponding tag variables.
  • All actions/* references have been replaced with forgejo/*;actions/github-script step has been deleted or rewritten.
  • permissions block has no attestations: write / id-token: write / issues: write / pull-requests: write。
  • The build job starts with the two steps "Capture proxy envs" + "Write buildkitd config".
  • buildx driver-opts is in multi-line format with proxy + config: /tmp/buildkit/buildkitd.toml。
  • artifact download uses forgejo/download-artifact explicit download by name, no pattern / merge-multiple。
  • runs-on every matrix entry explicitly specifies a runner, job-level uses matrix.runs-on no fallback.
  • The build-type workflow's on: has workflow_dispatch.inputs.version, the version parsing script handles the workflow_dispatch branch.
  • YAML syntax validation passes.
  • If GitHub-only capabilities were removed (PR comments, attestation, ali/docker hub push), the user has been informed in the reply.

Anti-patterns (do not do)

  • ❌ Directly cp .github/workflows/<name> .forgejo/workflows/<name> —— will bring ghcr.io / actions/* / attestations into the intranet, and the runner will not work.
  • ❌ Only changing the registry address without changing the artifact download method —— pattern + merge-multiple will error on forgejo/download-artifact.
  • ❌ Keeping the actions/github-script step —— Forgejo does not have this action, and the job will fail directly.
  • ❌ Hardcoding the intranet proxy variable as the real proxy URL —— must use the vars.AB_* repository variable, the URL will change.
  • ❌ Adding Alibaba Cloud / Docker Hub login back to the Forgejo version for the sake of "alignment" —— the intranet does not have these credentials, nor is it necessary.
  • ❌ Changing the GitHub side but forgetting to sync the Forgejo side (the most common mistake) —— when changing .github/workflows/ must also check .forgejo/workflows/。
  • ❌ Deleting business logic steps for the sake of "simplification" —— conversion only changes the execution environment, not the build/test content.
  • ❌ For preview-* / build-docs / helm-release Even GitHub-specific workflows are forcibly made into Forgejo versions — they depend on GitHub Pages / Vercel / Helm repo, and the intranet has no corresponding capabilities.