From 8c7d135fd9440c7ba9efe344f49bc915a6a341a3 Mon Sep 17 00:00:00 2001 From: Carlos Ortiz Date: Tue, 14 Jul 2026 15:56:47 -0400 Subject: [PATCH] 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 --- .gitea/workflows/weekly-game-build.yml | 104 ++++++++++++++++++++++--- 1 file changed, 95 insertions(+), 9 deletions(-) diff --git a/.gitea/workflows/weekly-game-build.yml b/.gitea/workflows/weekly-game-build.yml index c90e5b2..db4a3f2 100644 --- a/.gitea/workflows/weekly-game-build.yml +++ b/.gitea/workflows/weekly-game-build.yml @@ -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://.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"