fix(release): use GitHub-hosted runners (#685)

This commit is contained in:
tbphp authored and GitHub committed 2026-09-18 09:57:18 +08:00
1 parent d79180fda2
commit 47e3838e0b
4 files changed
+119 -53

No files matched your search

+38 -35
View File
@@ -10,8 +10,8 @@ permissions:
# 竖线分隔在任何错误(含网络错误)时回退到下一个源;默认的逗号只在
# 404/410 时回退,proxy.golang.org 瞬时故障会直接让整个 job 失败。
# 自托管 Runner 保留本机 Go 缓存;代码与编译参数变化由 Go 自动判定失效。
# 不再下载/上传 setup-go 缓存,也无需 main push 预热。
# CI 保留自托管 Runner 的本机 Go 缓存;Release 使用 GitHub 托管的临时 Runner。
# 两条流程都不下载/上传 setup-go 缓存,也无需 main push 预热。
env:
GOPROXY: https://proxy.golang.org|direct
@@ -21,7 +21,7 @@ concurrency:
jobs:
validate-tag:
runs-on: [self-hosted, Linux, ARM64]
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
@@ -143,7 +143,7 @@ jobs:
verify-and-build-web:
needs: validate-tag
runs-on: [self-hosted, macOS, ARM64]
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout tag commit
@@ -165,7 +165,7 @@ jobs:
static-checks:
needs: validate-tag
runs-on: [self-hosted, macOS, ARM64]
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Checkout tag commit
@@ -208,7 +208,7 @@ jobs:
needs: validate-tag
# 同一 commit 的 CI 已全绿时跳过;publication-preflight 接受 skipped 结果。
if: ${{ needs.validate-tag.outputs.ci_verified != 'true' }}
runs-on: [self-hosted, macOS, ARM64]
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Checkout tag commit
@@ -228,7 +228,7 @@ jobs:
race-cpa:
needs: validate-tag
if: ${{ needs.validate-tag.outputs.ci_verified != 'true' }}
runs-on: [self-hosted, Linux, ARM64]
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout tag commit
@@ -250,7 +250,7 @@ jobs:
name: Database contract (${{ matrix.driver }})
needs: validate-tag
if: ${{ needs.validate-tag.outputs.ci_verified != 'true' }}
runs-on: [self-hosted, Linux, ARM64]
runs-on: ubuntu-24.04
timeout-minutes: 15
strategy:
fail-fast: false
@@ -328,23 +328,23 @@ jobs:
matrix:
include:
- goos: linux
runner: [self-hosted, Linux, ARM64]
runner: ubuntu-24.04
goarch: amd64
filename: gpt-load-linux-amd64
- goos: linux
runner: [self-hosted, Linux, ARM64]
runner: ubuntu-24.04
goarch: arm64
filename: gpt-load-linux-arm64
- goos: darwin
runner: [self-hosted, macOS, ARM64]
runner: macos-15
goarch: amd64
filename: gpt-load-macos-amd64
- goos: darwin
runner: [self-hosted, macOS, ARM64]
runner: macos-15
goarch: arm64
filename: gpt-load-macos-arm64
- goos: windows
runner: [self-hosted, Windows, X64]
runner: windows-2025
goarch: amd64
filename: gpt-load-windows-amd64.exe
steps:
@@ -389,7 +389,7 @@ jobs:
build-windows-setup:
needs: build-binaries
runs-on: [self-hosted, Windows, X64]
runs-on: windows-2025
timeout-minutes: 10
steps:
- name: Checkout tag commit
@@ -433,7 +433,7 @@ jobs:
package-metadata:
needs: validate-tag
runs-on: [self-hosted, Linux, ARM64]
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout tag commit
@@ -473,7 +473,7 @@ jobs:
- build-binaries
- build-windows-setup
- package-metadata
runs-on: [self-hosted, Linux, ARM64]
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout tag commit
@@ -533,15 +533,15 @@ jobs:
max-parallel: 5
matrix:
include:
- runner: [self-hosted, Linux, X64]
- runner: ubuntu-24.04
filename: gpt-load-linux-amd64
- runner: [self-hosted, Linux, ARM64]
- runner: ubuntu-24.04-arm
filename: gpt-load-linux-arm64
- runner: macos-15-intel
filename: gpt-load-macos-amd64
- runner: [self-hosted, macOS, ARM64]
- runner: macos-15
filename: gpt-load-macos-arm64
- runner: [self-hosted, Windows, X64]
- runner: windows-2025
filename: gpt-load-windows-amd64.exe
runs-on: ${{ matrix.runner }}
steps:
@@ -584,7 +584,7 @@ jobs:
windows-installer-smoke:
needs: package-checksums
runs-on: [self-hosted, Windows, X64]
runs-on: windows-2025
timeout-minutes: 10
steps:
- name: Checkout tag commit
@@ -623,7 +623,7 @@ jobs:
docker-smoke:
needs: validate-tag
runs-on: [self-hosted, Linux, ARM64]
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- name: Isolate Docker credentials
@@ -660,9 +660,9 @@ jobs:
# 路径 COPY 的是不同的二进制,必须各自验证。使用原生 arm64 runner 而非
# QEMU:QEMU 模拟下的启动行为不能代表真实 arm64 运行时。
- arch: amd64
runner: [self-hosted, Linux, X64]
runner: ubuntu-24.04
- arch: arm64
runner: [self-hosted, Linux, ARM64]
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
steps:
@@ -714,7 +714,7 @@ jobs:
# race-tests、race-cpa 与 database-contract 在 CI 已验证同一 commit 时为 skipped,
# 这里只拒绝真正失败的 gate,不把 skipped 当作缺失的验证。
if: ${{ !cancelled() && !contains(needs.*.result, 'failure') }}
runs-on: [self-hosted, Linux, ARM64]
runs-on: ubuntu-24.04
timeout-minutes: 20
permissions:
# Draft Releases are visible only to identities with push access.
@@ -977,7 +977,7 @@ jobs:
# 间接祖先因 CI 结论复用而 skipped,publish-images 也会被默认条件连带跳过,
# 即便这里列出的直接 needs 全部成功。显式条件只看直接 needs,绕开这个传播。
if: ${{ !cancelled() && needs.validate-tag.result == 'success' && needs.publication-preflight.result == 'success' }}
runs-on: [self-hosted, Linux, ARM64]
runs-on: ubuntu-24.04
timeout-minutes: 45
permissions:
contents: read
@@ -1007,7 +1007,7 @@ jobs:
if: ${{ needs.publication-preflight.outputs.write_mode == 'publish' }}
uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0
with:
platforms: amd64
platforms: arm64
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
@@ -1093,7 +1093,7 @@ jobs:
- publish-images
# 同上:显式条件只看直接 needs,避免默认条件把间接祖先的 skip 传播下来。
if: ${{ !cancelled() && needs.validate-tag.result == 'success' && needs.publication-preflight.result == 'success' && needs.publish-images.result == 'success' }}
runs-on: [self-hosted, Linux, ARM64]
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: write
@@ -1162,6 +1162,9 @@ jobs:
make_latest: false
generate_release_notes: true
fail_on_unmatched_files: true
# 串行上传避免单个 runner 的并发连接中断拖垮整组资产;重试时保留已成功资产。
preserve_order: true
overwrite_files: false
# 目录内容已由上面的清单校验锁定为 .github/release-assets.txt 的全集。
files: release/*
body: |
@@ -1180,7 +1183,7 @@ jobs:
- publish-images
# 同上:显式条件只看直接 needs,避免默认条件把间接祖先的 skip 传播下来。
if: ${{ !cancelled() && needs.validate-tag.result == 'success' && needs.publish-images.result == 'success' }}
runs-on: [self-hosted, Linux, ARM64]
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: read
@@ -1223,7 +1226,7 @@ jobs:
- publish-images
# 同上:显式条件只看直接 needs,避免默认条件把间接祖先的 skip 传播下来。
if: ${{ !cancelled() && needs.validate-tag.result == 'success' && needs.publication-preflight.result == 'success' && needs.publish-github.result == 'success' && needs.publish-images.result == 'success' }}
runs-on: [self-hosted, Linux, ARM64]
runs-on: ubuntu-24.04
timeout-minutes: 45
permissions:
# Draft Releases are visible only to identities with push access.
@@ -1366,7 +1369,7 @@ jobs:
group: gpt-load-v2-image-channels
cancel-in-progress: false
queue: max
runs-on: [self-hosted, Linux, ARM64]
runs-on: ubuntu-24.04
timeout-minutes: 20
permissions:
contents: read
@@ -1436,7 +1439,7 @@ jobs:
environment:
name: render
url: ${{ vars.RENDER_SERVICE_URL }}
runs-on: [self-hosted, Linux, ARM64]
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: read
@@ -1446,7 +1449,7 @@ jobs:
RELEASE_VERSION: ${{ needs.validate-tag.outputs.version }}
IMAGE: ghcr.io/tbphp/gpt-load:${{ needs.validate-tag.outputs.image_exact }}
RENDER_CLI_VERSION: "2.25.0"
RENDER_CLI_SHA256: "7c4dc66ded7acc75e2f828b02d3d6901e9f2e4175d25c4fe5fd6f3aaf66b071b"
RENDER_CLI_SHA256: "3b3f1f839ef36b81f12d84ac7288f1c96f9f7519b39c53fe6f866612f704e7cd"
steps:
- name: Validate deployment inputs
shell: bash
@@ -1461,7 +1464,7 @@ jobs:
shell: bash
run: |
set -euo pipefail
archive="cli_${RENDER_CLI_VERSION}_linux_arm64.zip"
archive="cli_${RENDER_CLI_VERSION}_linux_amd64.zip"
archive_path="${RUNNER_TEMP}/${archive}"
extract_dir="${RUNNER_TEMP}/render-cli-${RENDER_CLI_VERSION}"
url="https://github.com/render-oss/cli/releases/download/v${RENDER_CLI_VERSION}/${archive}"
@@ -1556,7 +1559,7 @@ jobs:
- post-publish-image-smoke
- post-publish-verify
- promote-image-channels
runs-on: [self-hosted, Linux, ARM64]
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
# Draft Releases are visible only to identities with push access.
@@ -19,7 +19,7 @@ func TestWindowsInstallerKeepsPortableBinaryAndAddsSetupAsset(t *testing.T) {
workflow := readRepositoryFile(t, ".github/workflows/release.yml")
setupJob := workflowJobBlock(t, workflow, "build-windows-setup")
for _, required := range []string{
"runs-on: [self-hosted, Windows, X64]",
"runs-on: windows-2025",
"binary-gpt-load-windows-amd64.exe",
"packaging/windows/gpt-load.iss",
"ISCC.exe",
+74 -11
View File
@@ -36,7 +36,7 @@ func TestSelfHostedCIKeepsPlatformGatesAndLocalCaches(t *testing.T) {
}
}
func TestSelfHostedReleaseIsolatesDockerCredentials(t *testing.T) {
func TestReleaseIsolatesDockerCredentials(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
for _, job := range []string{
"docker-smoke", "prebuilt-image-smoke", "publication-preflight", "publish-images",
@@ -56,22 +56,85 @@ func TestSelfHostedReleaseIsolatesDockerCredentials(t *testing.T) {
}
}
func TestReleaseKeepsX64OnlyForNativeAMD64Validation(t *testing.T) {
func TestReleaseUsesGitHubHostedRunners(t *testing.T) {
content := readRepositoryFile(t, ".github/workflows/release.yml")
const x64 = "[self-hosted, Linux, X64]"
if strings.Count(content, x64) != 2 {
t.Fatal("Linux X64 must only run the native binary and prebuilt image AMD64 gates")
if strings.Contains(content, "self-hosted") {
t.Fatal("release workflow must not route release-specific jobs to self-hosted runners")
}
for _, job := range []string{"native-artifact-smoke", "prebuilt-image-smoke"} {
for _, job := range []string{
"validate-tag", "verify-and-build-web", "static-checks", "race-tests", "race-cpa",
"database-contract", "package-metadata", "package-checksums", "docker-smoke",
"publication-preflight", "publish-images", "publish-github", "post-publish-image-smoke",
"post-publish-verify", "promote-image-channels", "deploy-render", "reconcile-publication",
} {
block := workflowJobBlock(t, content, job)
if !strings.Contains(block, x64) || !strings.Contains(block, "[self-hosted, Linux, ARM64]") {
t.Errorf("%s must retain both native Linux architectures", job)
if !strings.Contains(block, "runs-on: ubuntu-24.04") {
t.Errorf("%s is not assigned to the GitHub-hosted Ubuntu runner", job)
}
}
for _, job := range []string{"build-windows-setup", "windows-installer-smoke"} {
block := workflowJobBlock(t, content, job)
if !strings.Contains(block, "runs-on: windows-2025") {
t.Errorf("%s is not assigned to the GitHub-hosted Windows runner", job)
}
}
for _, test := range []struct {
job string
required []string
}{
{
job: "build-binaries",
required: []string{
"runner: ubuntu-24.04\n goarch: amd64",
"runner: ubuntu-24.04\n goarch: arm64",
"runner: macos-15\n goarch: amd64",
"runner: macos-15\n goarch: arm64",
"runner: windows-2025\n goarch: amd64",
},
},
{
job: "native-artifact-smoke",
required: []string{
"runner: ubuntu-24.04\n filename: gpt-load-linux-amd64",
"runner: ubuntu-24.04-arm\n filename: gpt-load-linux-arm64",
"runner: macos-15-intel\n filename: gpt-load-macos-amd64",
"runner: macos-15\n filename: gpt-load-macos-arm64",
"runner: windows-2025\n filename: gpt-load-windows-amd64.exe",
},
},
{
job: "prebuilt-image-smoke",
required: []string{
"runner: ubuntu-24.04\n - arch: arm64",
"runner: ubuntu-24.04-arm",
},
},
} {
block := workflowJobBlock(t, content, test.job)
for _, required := range test.required {
if !strings.Contains(block, required) {
t.Errorf("%s does not include %s", test.job, required)
}
}
}
publish := workflowJobBlock(t, content, "publish-images")
qemu := workflowStepBlock(t, publish, "Set up QEMU")
if !strings.Contains(publish, "runs-on: [self-hosted, Linux, ARM64]") ||
!strings.Contains(qemu, "platforms: amd64") {
t.Fatal("ARM64 image publisher must enable AMD64 emulation for the other target")
if !strings.Contains(qemu, "platforms: arm64") {
t.Fatal("AMD64 hosted image publisher must enable ARM64 emulation for the other target")
}
publishGitHub := workflowStepBlock(t, workflowJobBlock(t, content, "publish-github"), "Create or update GitHub Release draft")
for _, required := range []string{"preserve_order: true", "overwrite_files: false"} {
if !strings.Contains(publishGitHub, required) {
t.Errorf("GitHub Release asset upload does not contain %q", required)
}
}
deployRender := workflowJobBlock(t, content, "deploy-render")
for _, required := range []string{
"cli_${RENDER_CLI_VERSION}_linux_amd64.zip",
"3b3f1f839ef36b81f12d84ac7288f1c96f9f7519b39c53fe6f866612f704e7cd",
} {
if !strings.Contains(deployRender, required) {
t.Errorf("Render deployment does not contain %q", required)
}
}
}
+6 -6
View File
@@ -1394,7 +1394,7 @@ func TestReleaseWorkflowDeploysCurrentMajorChannelToRender(t *testing.T) {
"RELEASE_VERSION: ${{ needs.validate-tag.outputs.version }}",
"IMAGE: ghcr.io/tbphp/gpt-load:${{ needs.validate-tag.outputs.image_exact }}",
`RENDER_CLI_VERSION: "2.25.0"`,
`RENDER_CLI_SHA256: "7c4dc66ded7acc75e2f828b02d3d6901e9f2e4175d25c4fe5fd6f3aaf66b071b"`,
`RENDER_CLI_SHA256: "3b3f1f839ef36b81f12d84ac7288f1c96f9f7519b39c53fe6f866612f704e7cd"`,
} {
if !strings.Contains(job, required) {
t.Fatalf("Render deployment job does not contain %q:\n%s", required, job)
@@ -1411,7 +1411,7 @@ func TestReleaseWorkflowDeploysCurrentMajorChannelToRender(t *testing.T) {
"RENDER_CLI_VERSION",
"RENDER_CLI_SHA256",
"sha256sum --check",
"cli_${RENDER_CLI_VERSION}_linux_arm64.zip",
"cli_${RENDER_CLI_VERSION}_linux_amd64.zip",
"--retry-all-errors",
} {
if !strings.Contains(install, required) {
@@ -2144,11 +2144,11 @@ func TestReleaseWorkflowPostPublishVerifiesDraftAssetsAgainstCurrentRun(t *testi
// 发布前的五平台原生 smoke 仍然是必须的门禁。
nativeJob := workflowJobBlock(t, content, "native-artifact-smoke")
for _, required := range []string{
"[self-hosted, Linux, X64]",
"[self-hosted, Linux, ARM64]",
"ubuntu-24.04",
"ubuntu-24.04-arm",
"macos-15-intel",
"[self-hosted, macOS, ARM64]",
"[self-hosted, Windows, X64]",
"macos-15",
"windows-2025",
"gpt-load-linux-amd64",
"gpt-load-linux-arm64",
"gpt-load-macos-amd64",