# Gitea Actions — weekly PACKAGED GAME builds (cooked, staged, standalone). # Produces Development AND Shipping Windows builds and publishes each to the # artifacts bucket. This is the shippable/playtest artifact, distinct from the # per-push editor binaries (build-editor.yml). # # PLATFORM EXPANSION: platform is modeled as a matrix axis mapped to a runner # label. Windows builds on the `windows` host-mode runner. To add Linux/Mac later: # 1) stand up a runner on a Linux/Mac machine (labels `linux` / `macos`), # 2) add an entry to the matrix below ({ os: linux, platform: Linux, ... }). # No other changes needed — the BuildGraph target is platform-parameterized. name: Weekly Game Build on: schedule: - cron: '0 6 * * 1' # Mondays 06:00 UTC — adjust to your cadence workflow_dispatch: # manual "build now" concurrency: group: weekly-game-build cancel-in-progress: false # don't kill an in-flight cook with a new trigger jobs: package: strategy: fail-fast: false # a Shipping failure shouldn't cancel Development matrix: include: - { os: windows, platform: Win64, configuration: Development } - { os: windows, platform: Win64, configuration: Shipping } runs-on: ${{ matrix.os }} env: UE_ROOT: D:\Unreal\UE_5.7 PROJECT_NAME: ASGCGameJam2026 BUILDGRAPH_SCRIPT: D:\asgcgj\build-infra\buildgraph\BuildEditor.xml # --- 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 with: lfs: true - name: Compute canonical BUILD_ID shell: powershell run: | $short = "$env:GITHUB_SHA".Substring(0,8) $buildId = "${{ matrix.configuration }}-$env:GITHUB_REF_NAME-$short" "BUILD_ID=$buildId" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 Write-Host "BUILD_ID=$buildId" - name: BuildGraph — packaged game (${{ matrix.configuration }} / ${{ matrix.platform }}) shell: powershell run: | & "$env:UE_ROOT\Engine\Build\BatchFiles\RunUAT.bat" BuildGraph ` -Script="$env:BUILDGRAPH_SCRIPT" ` -Target="PackagedGame" ` -set:ProjectName=$env:PROJECT_NAME ` -set:ProjectDir="$env:GITHUB_WORKSPACE" ` -set:ProjectPath="$env:GITHUB_WORKSPACE\$env:PROJECT_NAME.uproject" ` -set:Platform=${{ matrix.platform }} ` -set:Configuration=${{ matrix.configuration }} ` -set:BuildVersion=$env:BUILD_ID ` -set:OutputDir="$env:GITHUB_WORKSPACE\LocalBuilds" if ($LASTEXITCODE -ne 0) { throw "BuildGraph failed ($LASTEXITCODE)" } - 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: | $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" } $base = "r2:$env:R2_BUCKET/builds/$plat/$cfg" # Immutable, sha-tagged copy + a 'latest' pointer per platform/config. 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 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"