CI: publish weekly game builds to Cloudflare R2 + emit Keystone builds manifest

Switch the publish step from gcloud/GCS to rclone against Cloudflare R2
(zero egress), self-provision rclone on the runner, and add a
publish-manifest job that writes builds/keystone-builds.json so Keystone's
/builds page lists clean, channel-labelled entries (latest per
platform/channel) instead of raw object names.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 15:56:47 -04:00
parent c67a261609
commit 8c7d135fd9
+95 -9
View File
@@ -31,9 +31,16 @@ jobs:
env:
UE_ROOT: D:\Unreal\UE_5.7
PROJECT_NAME: ASGCGameJam2026
ARTIFACTS_BUCKET: keystone-501223-build-artifacts
BUILDGRAPH_SCRIPT: D:\asgcgj\build-infra\buildgraph\BuildEditor.xml
GCLOUD_BIN: C:\Users\aorti\AppData\Local\Google\Cloud SDK\google-cloud-sdk\bin
# --- Cloudflare R2 (S3-compatible) publish target ---
# rclone reads its whole remote config from these env vars (remote name = "r2").
# Keys come from Gitea Actions secrets so they never live in the repo.
R2_BUCKET: asgc-build-artifacts # TODO: set to your R2 bucket name
RCLONE_CONFIG_R2_TYPE: s3
RCLONE_CONFIG_R2_PROVIDER: Cloudflare
RCLONE_CONFIG_R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }} # https://<ACCOUNT_ID>.r2.cloudflarestorage.com
RCLONE_CONFIG_R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
RCLONE_CONFIG_R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
steps:
# Packaged builds COOK content, so LFS is required (unlike editor binaries).
- uses: actions/checkout@v4
@@ -63,19 +70,98 @@ jobs:
-set:OutputDir="$env:GITHUB_WORKSPACE\LocalBuilds"
if ($LASTEXITCODE -ne 0) { throw "BuildGraph failed ($LASTEXITCODE)" }
- name: Publish packaged build
- name: Ensure rclone
shell: powershell
run: |
if (Get-Command rclone -ErrorAction SilentlyContinue) { Write-Host "rclone already present"; exit 0 }
$ver = "v1.68.2"
$url = "https://downloads.rclone.org/$ver/rclone-$ver-windows-amd64.zip"
$tmp = "$env:RUNNER_TEMP\rclone.zip"
$dst = "$env:RUNNER_TEMP\rclone"
Invoke-WebRequest -Uri $url -OutFile $tmp
Expand-Archive -Path $tmp -DestinationPath $dst -Force
$exe = (Get-ChildItem -Path $dst -Recurse -Filter rclone.exe | Select-Object -First 1).DirectoryName
"$exe" | Out-File -FilePath $env:GITHUB_PATH -Append -Encoding utf8
Write-Host "Installed rclone $ver -> $exe"
- name: Publish packaged build (R2)
shell: powershell
run: |
$env:PATH += ";$env:GCLOUD_BIN"
$cfg = "${{ matrix.configuration }}".ToLower()
$plat = "${{ matrix.os }}"
$zip = "$env:GITHUB_WORKSPACE\LocalBuilds\$env:PROJECT_NAME-${{ matrix.platform }}-${{ matrix.configuration }}.zip"
if (-not (Test-Path $zip)) { throw "Packaged zip not found: $zip" }
$sha = "$env:GITHUB_SHA".Substring(0,8)
$base = "gs://$env:ARTIFACTS_BUCKET/builds/$plat/$cfg"
$base = "r2:$env:R2_BUCKET/builds/$plat/$cfg"
# Immutable, sha-tagged copy + a 'latest' pointer per platform/config.
gcloud storage cp $zip "$base/$env:PROJECT_NAME-$env:BUILD_ID.zip"
gcloud storage cp $zip "$base/latest.zip"
rclone copyto $zip "$base/$env:PROJECT_NAME-$env:BUILD_ID.zip"
rclone copyto $zip "$base/latest.zip"
"$env:GITHUB_SHA" | Out-File -Encoding ascii latest.sha
gcloud storage cp latest.sha "$base/latest.sha"
rclone copyto latest.sha "$base/latest.sha"
Write-Host "Published $cfg $plat build -> $base/"
# Writes keystone-builds.json so Keystone's /builds page shows clean, labelled
# entries (one "latest" per platform/channel) instead of raw object names +
# the stray latest.sha. Runs once AFTER the matrix so Development/Shipping don't
# race on the same manifest object. `if: always()` so a failed config still lets
# the surviving one publish (it lists only builds whose latest.zip actually exists).
publish-manifest:
needs: package
if: always()
runs-on: windows
env:
PROJECT_NAME: ASGCGameJam2026
R2_BUCKET: asgc-build-artifacts # TODO: set to your R2 bucket name (match the package job)
RCLONE_CONFIG_R2_TYPE: s3
RCLONE_CONFIG_R2_PROVIDER: Cloudflare
RCLONE_CONFIG_R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
RCLONE_CONFIG_R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
RCLONE_CONFIG_R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
steps:
- name: Ensure rclone
shell: powershell
run: |
if (Get-Command rclone -ErrorAction SilentlyContinue) { Write-Host "rclone already present"; exit 0 }
$ver = "v1.68.2"
$url = "https://downloads.rclone.org/$ver/rclone-$ver-windows-amd64.zip"
$tmp = "$env:RUNNER_TEMP\rclone.zip"
$dst = "$env:RUNNER_TEMP\rclone"
Invoke-WebRequest -Uri $url -OutFile $tmp
Expand-Archive -Path $tmp -DestinationPath $dst -Force
$exe = (Get-ChildItem -Path $dst -Recurse -Filter rclone.exe | Select-Object -First 1).DirectoryName
"$exe" | Out-File -FilePath $env:GITHUB_PATH -Append -Encoding utf8
Write-Host "Installed rclone $ver -> $exe"
- name: Build & publish keystone-builds.json
shell: powershell
run: |
$short = "$env:GITHUB_SHA".Substring(0,8)
$builtAt = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
# (platform, channel, config-folder). Add Linux/Mac rows here when those
# runners exist — the /builds page groups by the `platform` field.
$targets = @(
@{ platform = "windows"; channel = "Development"; folder = "development" },
@{ platform = "windows"; channel = "Shipping"; folder = "shipping" }
)
$entries = @()
foreach ($t in $targets) {
$key = "builds/$($t.platform)/$($t.folder)/latest.zip"
$present = rclone lsf "r2:$env:R2_BUCKET/$key" 2>$null
if (-not $present) { Write-Host "skip $key (not present yet)"; continue }
$obj = [ordered]@{
path = $key
platform = $t.platform
channel = $t.channel
name = "$env:PROJECT_NAME - $($t.platform) ($($t.channel))"
version = "$env:GITHUB_REF_NAME-$short"
builtAt = $builtAt
}
# -Compress per-entry, then join inside [] — avoids PS 5.1 collapsing a
# single-element array to a bare object (which would fail the schema).
$entries += ($obj | ConvertTo-Json -Depth 5 -Compress)
}
if ($entries.Count -eq 0) { throw "No builds present to index — manifest not written." }
$json = "{`n `"version`": 1,`n `"builds`": [$([string]::Join(', ', $entries))]`n}"
$json | Out-File -Encoding utf8 keystone-builds.json
Write-Host $json
rclone copyto keystone-builds.json "r2:$env:R2_BUCKET/builds/keystone-builds.json"
Write-Host "Published builds manifest ($($entries.Count) entries) -> r2:$env:R2_BUCKET/builds/keystone-builds.json"