Redist Update #11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: "Redist Update" | |
| on: | |
| schedule: | |
| - cron: "*/15 * * * *" # Run every 15 minutes | |
| workflow_dispatch: | |
| inputs: | |
| variant: | |
| description: 'A variant name from .github/variants.json, or "all"' | |
| required: true | |
| default: 'all' | |
| type: string | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| concurrency: | |
| group: unturned-redist-update-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| # Single source of truth: the variant matrix lives in .github/variants.json. | |
| # This job emits TWO matrices from it: | |
| # - sources: one entry per distinct Steam (appId, branch) "source". The | |
| # game is downloaded ONCE per source (see download_sources). | |
| # - variants: the flat per-variant list (see update_variant), each of which | |
| # reuses its source's single download. | |
| # To add/remove a variant, edit .github/variants.json only. | |
| load: | |
| name: "Load matrices" | |
| runs-on: ubuntu-latest | |
| outputs: | |
| variants: ${{ steps.load.outputs.variants }} | |
| sources: ${{ steps.load.outputs.sources }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Load variant + source matrices | |
| id: load | |
| env: | |
| INPUT_VARIANT: ${{ github.event.inputs.variant }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| run: | | |
| set -euo pipefail | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ -n "${INPUT_VARIANT:-}" ] && [ "$INPUT_VARIANT" != "all" ]; then | |
| if ! jq -e --arg v "$INPUT_VARIANT" 'any(.[]; .variant == $v)' .github/variants.json >/dev/null; then | |
| echo "::error::Unknown variant '$INPUT_VARIANT'. Valid variants:" | |
| jq -r '.[].variant' .github/variants.json | |
| exit 1 | |
| fi | |
| base=$(jq -c --arg v "$INPUT_VARIANT" '[.[] | select(.variant == $v)]' .github/variants.json) | |
| else | |
| base=$(jq -c '.' .github/variants.json) | |
| fi | |
| # Group the (possibly filtered) variants by their Steam source. The | |
| # source label "<appId>-<branch|default>" is reused as the artifact | |
| # name that ties download_sources -> update_variant together. | |
| sources=$(printf '%s' "$base" | jq -c ' | |
| group_by(.appId + "|" + .branch) | |
| | map({ | |
| source: (.[0].appId + "-" + (if .[0].branch == "" then "default" else .[0].branch end)), | |
| appId: .[0].appId, | |
| depotId: .[0].depotId, | |
| branch: .[0].branch, | |
| anonymous: .[0].anonymous, | |
| loginId: .[0].loginId, | |
| variants: (map(.variant) | join(",")) | |
| })') | |
| echo "variants=$base" >> "$GITHUB_OUTPUT" | |
| echo "sources=$sources" >> "$GITHUB_OUTPUT" | |
| echo "Sources to probe:" | |
| printf '%s' "$sources" | jq -r '.[] | " \(.source) (variants: \(.variants))"' | |
| # One job per distinct Steam (appId, branch) source. Probe the manifest once; | |
| # if it changed, download the game ONCE and publish just the bits the redist | |
| # tool needs (Managed DLLs + appmanifest + Status.json) as an artifact that | |
| # every variant of this source reuses. This is where the only Steam logins | |
| # happen, so it's the only place that needs serializing. | |
| download_sources: | |
| name: "Download ${{ matrix.source }}" | |
| runs-on: ubuntu-latest | |
| needs: load | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJson(needs.load.outputs.sources) }} | |
| # Anonymous sources (the dedicated-server app) get a unique group → they run | |
| # in parallel. The authenticated (client) sources share ONE Steam account and | |
| # steamcmd's download has no LoginID, so concurrent logins collide; they share | |
| # the "authenticated" group → serialized. There are only ever 2 client sources | |
| # (default + preview), and a concurrency group keeps 1 running + 1 pending, so | |
| # nothing is cancelled (the 3+ case that broke the old per-variant grouping | |
| # cannot occur with only 2 members). | |
| concurrency: | |
| group: redist-dl-${{ matrix.anonymous && matrix.source || 'authenticated' }} | |
| cancel-in-progress: false | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Download depot downloader | |
| uses: robinraju/release-downloader@28fc21f50d76778e7023361aa1f863e717d3d56f # v1 | |
| with: | |
| repository: SteamRE/DepotDownloader | |
| latest: true | |
| fileName: DepotDownloader-linux-x64.zip | |
| out-file-path: depot_downloader | |
| tag: "DepotDownloader_3.4.0" # Pin its version to avoid breaking changes. | |
| extract: true | |
| - name: Probe source manifest | |
| id: probe | |
| env: | |
| STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }} | |
| STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }} | |
| APP_ID: ${{ matrix.appId }} | |
| APP_DEPOT_ID: ${{ matrix.depotId }} | |
| APP_BRANCH_NAME: ${{ matrix.branch }} | |
| SOURCE: ${{ matrix.source }} | |
| ANONYMOUS: ${{ matrix.anonymous }} | |
| LOGIN_ID: ${{ matrix.loginId }} | |
| VARIANTS: ${{ matrix.variants }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p redist/temp_depots | |
| chmod +x depot_downloader/DepotDownloader | |
| beta_args=() | |
| if [ -n "$APP_BRANCH_NAME" ]; then | |
| beta_args=(-beta "$APP_BRANCH_NAME") | |
| fi | |
| # -loginid makes each source a distinct Steam session so the anonymous | |
| # probes can run concurrently. Authenticated sources pass credentials; | |
| # anonymous sources omit them (DepotDownloader defaults to anonymous). | |
| auth_args=(-loginid "$LOGIN_ID") | |
| if [ "$ANONYMOUS" != "true" ]; then | |
| auth_args+=(-username "$STEAM_USERNAME" -password "$STEAM_PASSWORD") | |
| fi | |
| # '|| true': DepotDownloader can exit non-zero even on a successful | |
| # manifest fetch. The id-validation below is the real gate (a genuine | |
| # auth/network failure yields no manifest id and still fails loudly). | |
| manifest_output=$(depot_downloader/DepotDownloader \ | |
| -app "$APP_ID" \ | |
| -depot "$APP_DEPOT_ID" \ | |
| -manifest-only \ | |
| "${auth_args[@]}" \ | |
| "${beta_args[@]}" \ | |
| -dir redist/temp_depots || true) | |
| # Prints e.g. "Manifest 763708736677468005 (date)". Manifest ids are | |
| # 64-bit, so their length varies (~17-20 digits) — grab the number | |
| # after the "Manifest" keyword rather than assuming a fixed width. | |
| current_manifest=$(printf '%s\n' "$manifest_output" | grep -oiE 'manifest[[:space:]]+[0-9]+' | grep -oE '[0-9]+' | head -n 1 || true) | |
| if ! [[ "$current_manifest" =~ ^[0-9]{10,20}$ ]]; then | |
| echo "::error::Could not extract a manifest id for source ${SOURCE} (Steam auth/network issue or output-format change?)." | |
| echo "----- DepotDownloader output (first 50 lines) -----" | |
| printf '%s\n' "$manifest_output" | head -n 50 | |
| exit 1 | |
| fi | |
| echo "Current manifest: $current_manifest" | |
| echo "current_manifest=$current_manifest" >> "$GITHUB_OUTPUT" | |
| # This source needs a download if ANY of its variants' recorded | |
| # manifest ids differs from the live one (or is missing). They normally | |
| # all match; "any differs" lets a lagging variant catch up. | |
| changed=false | |
| IFS=',' read -ra source_variants <<< "$VARIANTS" | |
| for v in "${source_variants[@]}"; do | |
| f="redist/redist-manifests/.manifest.redist-${v}.txt" | |
| prev="" | |
| [ -f "$f" ] && prev=$(cat "$f") | |
| echo " ${v}: recorded=${prev:-<none>}" | |
| [ "$prev" != "$current_manifest" ] && changed=true | |
| done | |
| echo "Source changed: $changed" | |
| echo "source_changed=$changed" >> "$GITHUB_OUTPUT" | |
| - name: Setup SteamCMD | |
| if: steps.probe.outputs.source_changed == 'true' | |
| uses: CyberAndrii/setup-steamcmd@afc45f145b95c175c3a3862d3ca84756537d48e8 # v1 | |
| - name: Download game files | |
| if: steps.probe.outputs.source_changed == 'true' | |
| env: | |
| STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }} | |
| STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }} | |
| APP_ID: ${{ matrix.appId }} | |
| APP_BRANCH_NAME: ${{ matrix.branch }} | |
| ANONYMOUS: ${{ matrix.anonymous }} | |
| run: | | |
| set -uo pipefail | |
| if [ "$ANONYMOUS" = "true" ]; then | |
| login_args=(+login anonymous) | |
| else | |
| login_args=(+login "$STEAM_USERNAME" "$STEAM_PASSWORD") | |
| fi | |
| beta_args=() | |
| if [ -n "$APP_BRANCH_NAME" ]; then | |
| beta_args=(-beta "$APP_BRANCH_NAME") | |
| fi | |
| # steamcmd is flaky: it can exit non-zero on success and fail downloads | |
| # transiently. Its exit code isn't reliable, so we RETRY and gate on the | |
| # real artifact — steamcmd only writes appmanifest_<appid>.acf once the | |
| # update completes (the same "no appmanifest -> force update" remedy | |
| # LinuxGSM uses). The redist tool needs that file downstream. | |
| manifest_present() { | |
| find "$GITHUB_WORKSPACE" -name "appmanifest_${APP_ID}.acf" -print -quit 2>/dev/null | grep -q . | |
| } | |
| attempts=4 | |
| for i in $(seq 1 "$attempts"); do | |
| rc=0 | |
| steamcmd +force_install_dir "$GITHUB_WORKSPACE" "${login_args[@]}" +app_update "$APP_ID" "${beta_args[@]}" -validate +quit || rc=$? | |
| if manifest_present; then | |
| echo "Download complete on attempt $i/$attempts (steamcmd rc=$rc)." | |
| break | |
| fi | |
| echo "::warning::steamcmd attempt $i/$attempts did not produce appmanifest_${APP_ID}.acf (rc=$rc); retrying after backoff..." | |
| [ "$i" -lt "$attempts" ] && sleep $((i * 15)) | |
| done | |
| if ! manifest_present; then | |
| echo "::error::steamcmd failed to download app $APP_ID after $attempts attempts (no appmanifest_${APP_ID}.acf). Likely a transient Steam/anonymous outage — re-run the workflow." | |
| exit 1 | |
| fi | |
| - name: Stash manifest id for consumers | |
| if: steps.probe.outputs.source_changed == 'true' | |
| run: echo "${{ steps.probe.outputs.current_manifest }}" > source-manifest.txt | |
| # Publish only the small bits the redist tool reads — NOT the multi-GB game. | |
| # The artifact's presence is also the "this source changed" signal that | |
| # update_variant keys off (a missing artifact => unchanged => skip). | |
| - name: Upload source files | |
| if: steps.probe.outputs.source_changed == 'true' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: source-${{ matrix.source }} | |
| path: | | |
| source-manifest.txt | |
| Status.json | |
| steamapps/appmanifest_${{ matrix.appId }}.acf | |
| Unturned_Data/Managed/** | |
| Unturned_Headless_Data/Managed/** | |
| if-no-files-found: warn | |
| retention-days: 1 | |
| # One job per variant, FULLY PARALLEL: these never touch Steam — each pulls its | |
| # source's artifact (the single download above), runs the redist tool, and opens | |
| # its rolling PR. No Steam login here means no account contention, so there is | |
| # nothing to serialize. | |
| update_variant: | |
| name: "Update ${{ matrix.variant }}" | |
| runs-on: ubuntu-latest | |
| needs: [load, download_sources] | |
| # Job-level permissions REPLACE the workflow defaults, so this must list | |
| # everything GITHUB_TOKEN needs here: contents (checkout), and actions:read | |
| # for the "list this run's artifacts" gate. (The PR itself is created with | |
| # secrets.PAT, independent of these.) | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| actions: read | |
| # Run even if some source download failed (those variants just find no | |
| # artifact and skip); notify-failure still flags the failed source. Skip only | |
| # if the load job itself failed (then there's no matrix to run). | |
| if: ${{ !cancelled() && needs.load.result == 'success' }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJson(needs.load.outputs.variants) }} | |
| steps: | |
| - name: Compute identifiers | |
| id: ids | |
| env: | |
| APP_ID: ${{ matrix.appId }} | |
| BRANCH: ${{ matrix.branch }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${BRANCH}" ]; then | |
| key="${APP_ID}-default" | |
| else | |
| key="${APP_ID}-${BRANCH}" | |
| fi | |
| echo "source_key=$key" >> "$GITHUB_OUTPUT" | |
| echo "BRANCH_NAME=redist-update/${{ matrix.variant }}" >> "$GITHUB_ENV" | |
| # The source artifact exists only if download_sources saw a manifest change | |
| # for this variant's source. No artifact => nothing to do this run. | |
| - name: Check whether this source changed | |
| id: gate | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| RUN_ID: ${{ github.run_id }} | |
| SOURCE_KEY: ${{ steps.ids.outputs.source_key }} | |
| run: | | |
| set -euo pipefail | |
| # Fail LOUD on an API error. The ONLY thing that may mean "this source | |
| # is unchanged" is a SUCCESSFUL artifacts listing that doesn't contain | |
| # our artifact. If the gh call itself fails (transient 5xx / network / | |
| # rate-limit), we must NOT treat that as "unchanged" — that would | |
| # silently skip a genuinely-changed source under a green run, and | |
| # notify-failure (failure()-only) would never fire. Retry, then fail. | |
| ok=false | |
| names="" | |
| for attempt in 1 2 3; do | |
| if names=$(gh api --paginate "repos/$REPO/actions/runs/$RUN_ID/artifacts" --jq '.artifacts[].name'); then | |
| ok=true | |
| break | |
| fi | |
| echo "::warning::Listing run artifacts failed (attempt ${attempt}/3); retrying after backoff..." | |
| sleep $((attempt * 5)) | |
| done | |
| if [ "$ok" != "true" ]; then | |
| echo "::error::Could not list this run's artifacts after 3 attempts — cannot determine whether source ${SOURCE_KEY} changed. Failing loudly instead of skipping." | |
| exit 1 | |
| fi | |
| if printf '%s\n' "$names" | grep -qx "source-${SOURCE_KEY}"; then | |
| echo "Source ${SOURCE_KEY} changed (artifact present); processing ${{ matrix.variant }}." | |
| echo "changed=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Source ${SOURCE_KEY} unchanged (no artifact); skipping ${{ matrix.variant }}." | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Checkout repository | |
| if: steps.gate.outputs.changed == 'true' | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: master | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Setup .NET | |
| if: steps.gate.outputs.changed == 'true' | |
| uses: actions/setup-dotnet@v5 | |
| env: | |
| DOTNET_NOLOGO: true | |
| DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true | |
| DOTNET_CLI_TELEMETRY_OPTOUT: true | |
| with: | |
| dotnet-version: 10.x | |
| - name: Download update tool | |
| if: steps.gate.outputs.changed == 'true' | |
| uses: robinraju/release-downloader@28fc21f50d76778e7023361aa1f863e717d3d56f # v1 | |
| with: | |
| repository: RocketModFix/UnturnedRedistUpdateTool | |
| latest: true | |
| fileName: UnturnedRedistUpdateTool.zip | |
| out-file-path: redist_tool | |
| extract: true | |
| - name: Download source game files | |
| if: steps.gate.outputs.changed == 'true' | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: source-${{ steps.ids.outputs.source_key }} | |
| path: ${{ github.workspace }} | |
| - name: Run redist updater | |
| if: steps.gate.outputs.changed == 'true' | |
| env: | |
| IS_PREVIEW: ${{ matrix.preview }} | |
| DO_PUBLICIZE: ${{ matrix.publicize }} | |
| APP_ID: ${{ matrix.appId }} | |
| REDIST_DIR: ${{ matrix.dir }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| VARIANT: ${{ matrix.variant }} | |
| run: | | |
| set -euo pipefail | |
| flags="" | |
| # Manual dispatch forces a rebuild even if the tool thinks nothing changed. | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ]; then | |
| flags="$flags --force" | |
| fi | |
| # Only client-preview / server-preview emit the -preview<build> prerelease. | |
| if [ "$IS_PREVIEW" = "true" ]; then | |
| flags="$flags --preview" | |
| fi | |
| flags="$flags -update-files Assembly-CSharp.dll,Assembly-CSharp.xml,SDG.NetPak.Runtime.xml,UnturnedDat.dll,UnityEx.dll,SystemEx.dll,SDG.NetTransport.dll,SDG.NetPak.Runtime.dll,SDG.HostBans.Runtime.dll,SDG.Glazier.Runtime.dll,com.rlabrecque.steamworks.net.dll" | |
| if [ "$DO_PUBLICIZE" = "true" ]; then | |
| flags="$flags -publicize Assembly-CSharp.dll" | |
| fi | |
| echo "Variant: $VARIANT | Event: $EVENT_NAME | Flags: '$flags'" | |
| # Capture output so a failure (or a success that doesn't write .commit) | |
| # leaves diagnostics in the log. | |
| if ! tool_output=$(dotnet redist_tool/UnturnedRedistUpdateTool.dll "$GITHUB_WORKSPACE" "$GITHUB_WORKSPACE/$REDIST_DIR" "$APP_ID" $flags 2>&1); then | |
| echo "::error::Redist updater failed for ${VARIANT}." | |
| echo "----- Tool output (first 80 lines) -----" | |
| printf '%s\n' "$tool_output" | head -n 80 | |
| exit 1 | |
| fi | |
| printf '%s\n' "$tool_output" | |
| # Record the manifest id this build was produced from so the next probe | |
| # sees "no change". Lives outside REDIST_DIR so the discard step below never | |
| # reverts it, even when no package is published. | |
| - name: Record source manifest id | |
| if: steps.gate.outputs.changed == 'true' | |
| env: | |
| VARIANT: ${{ matrix.variant }} | |
| run: | | |
| set -euo pipefail | |
| manifest_id=$(cat source-manifest.txt) | |
| mkdir -p redist/redist-manifests | |
| echo "$manifest_id" > "redist/redist-manifests/.manifest.redist-${VARIANT}.txt" | |
| - name: Resolve commit message and discard unpublishable changes | |
| if: steps.gate.outputs.changed == 'true' | |
| id: generate_commit_message | |
| env: | |
| REDIST_DIR: ${{ matrix.dir }} | |
| run: | | |
| set -euo pipefail | |
| if [ -s .commit ]; then | |
| # The tool bumped the NuGet version -> a real, publishable update. | |
| # Keep all the regenerated files (DLLs, version.json, manifest.sha256.json). | |
| { | |
| echo "message<<COMMIT_EOF" | |
| cat .commit | |
| echo "COMMIT_EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| else | |
| # No .commit means the tool did NOT bump the version: the Steam build / | |
| # depot manifest changed, but the game version (X.Y.Z.N) did not. We | |
| # cannot republish the same NuGet version, so the regenerated DLLs are | |
| # unpublishable (would 409). Discard them in the variant dir and keep | |
| # ONLY the corrected Steam manifest id (recorded above) so this build is | |
| # logged and not re-processed every run. The real DLLs publish on the | |
| # next game-version bump. (A tool failure already failed the step above, | |
| # so a missing .commit here is a legitimate no-op, not a silent error.) | |
| git checkout -- "$REDIST_DIR" 2>/dev/null || true | |
| echo "message=Record ${{ matrix.variant }} Steam manifest (game version unchanged; no package update)" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Check for git changes | |
| if: steps.gate.outputs.changed == 'true' | |
| id: check_git_changes | |
| run: | | |
| if git diff --quiet; then | |
| echo "No changes detected after processing for ${{ matrix.variant }}." | |
| echo "has_git_changes=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Changes detected after processing for ${{ matrix.variant }}." | |
| echo "has_git_changes=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| # peter-evans/create-pull-request commits to a fixed-name branch and keeps | |
| # the open PR continually updated until it is merged/closed; delete-branch | |
| # cleans it up afterwards. No timestamped branches or existing-PR lookups. | |
| - name: Create Pull Request | |
| if: steps.gate.outputs.changed == 'true' && steps.check_git_changes.outputs.has_git_changes == 'true' | |
| id: create_pr | |
| uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8 | |
| with: | |
| add-paths: | | |
| redist/* | |
| token: ${{ secrets.PAT }} | |
| branch: ${{ env.BRANCH_NAME }} | |
| delete-branch: true | |
| base: master | |
| commit-message: ${{ steps.generate_commit_message.outputs.message }} | |
| title: "🤖 Auto-update ${{ matrix.variant }} redist files" | |
| committer: rocketmodfixadmin <rocketmodfixadmin@users.noreply.github.com> | |
| author: rocketmodfixadmin <rocketmodfixadmin@users.noreply.github.com> | |
| body: | | |
| ## Automated Redist Update - ${{ matrix.variant }} | |
| This PR contains automatically updated redist files for the **${{ matrix.variant }}** variant. | |
| ### Changes | |
| ${{ steps.generate_commit_message.outputs.message }} | |
| ### Validation | |
| 🔄 Validation (file presence, SHA-256 hashes, version monotonicity) runs automatically on this PR. | |
| --- | |
| **Triggered by**: ${{ github.event_name == 'workflow_dispatch' && 'Manual dispatch' || 'Scheduled run' }} | |
| **Variant**: ${{ matrix.variant }} | |
| **Branch**: `${{ env.BRANCH_NAME }}` | |
| > Automatically created by the Unturned Redist update workflow. | |
| draft: false | |
| labels: | | |
| automated | |
| redist-update | |
| ${{ matrix.variant }} | |
| - name: Summary | |
| if: always() | |
| run: | | |
| { | |
| echo "## Update Summary for ${{ matrix.variant }}" | |
| echo "- **Branch**: \`${{ env.BRANCH_NAME }}\`" | |
| echo "- **Source Changed**: ${{ steps.gate.outputs.changed }}" | |
| if [ "${{ steps.gate.outputs.changed }}" = "true" ]; then | |
| echo "- **Git Changes**: ${{ steps.check_git_changes.outputs.has_git_changes }}" | |
| if [ -n "${{ steps.create_pr.outputs.pull-request-number }}" ]; then | |
| echo "- **PR**: #${{ steps.create_pr.outputs.pull-request-number }} (${{ steps.create_pr.outputs.pull-request-url }})" | |
| fi | |
| else | |
| echo "- **Status**: Skipped (source unchanged)" | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| # Make silent breakage visible: if any scheduled source download or variant | |
| # update fails, open (or comment on) a tracking issue instead of relying on | |
| # someone watching the Actions tab. | |
| notify-failure: | |
| name: "Notify on failure" | |
| needs: [load, download_sources, update_variant] | |
| if: failure() && github.event_name == 'schedule' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| issues: write | |
| steps: | |
| - name: Open or update tracking issue | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| set -euo pipefail | |
| marker="Redist auto-update workflow failed" | |
| body=$(printf '⚠️ The scheduled redist update workflow failed.\n\nRun: %s\n\nA source download (Steam auth/network, DepotDownloader, steamcmd) or a variant update (the redist tool or PR creation) errored. Check the run for the failing job.' "$RUN_URL") | |
| gh label create update-failure --color B60205 --description "Automated redist update failure" --force >/dev/null 2>&1 || true | |
| existing=$(gh issue list --repo "$REPO" --state open --search "in:title \"$marker\"" --json number --jq '.[0].number // empty') | |
| if [ -n "$existing" ]; then | |
| gh issue comment "$existing" --repo "$REPO" --body "$body" | |
| else | |
| gh issue create --repo "$REPO" --title "⚠️ $marker" --body "$body" --label update-failure | |
| fi | |
| workflow-keepalive: | |
| if: github.event_name == 'schedule' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: write | |
| steps: | |
| - uses: liskin/gh-workflow-keepalive@f72ff1a1336129f29bf0166c0fd0ca6cf1bcb38c # v1 |