Redist Update #429
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: | |
| # Emits two matrices from .github/variants.json (the single source of truth): | |
| # `sources` (one per Steam appId+branch, downloaded once) and `variants` (flat | |
| # per-variant list). Add/remove variants by editing variants.json. | |
| 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@v7 | |
| - name: Load variant + source matrices | |
| id: load | |
| env: | |
| INPUT_VARIANT: ${{ github.event.inputs.variant }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| # Opt-in 2nd Steam account (see ARCHITECTURE.md); empty/false = unchanged. | |
| STEAM_SECOND_ACCOUNT_ENABLED: ${{ vars.STEAM_SECOND_ACCOUNT_ENABLED }} | |
| 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 variants by Steam source (appId+branch); the source label doubles as | |
| # the artifact name linking download_sources -> update_variant. Also derive per | |
| # source: steamAccount (0=anon, 1=primary, 2=2nd account for the preview client) | |
| # and dlgroup (download concurrency group). See ARCHITECTURE.md. | |
| second="${STEAM_SECOND_ACCOUNT_ENABLED:-false}" | |
| sources=$(printf '%s' "$base" | jq -c --arg second "$second" ' | |
| 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(",")), | |
| steamAccount: (if .[0].anonymous then 0 | |
| elif .[0].branch == "" then 1 | |
| elif ($second == "true") then 2 | |
| else 1 end), | |
| dlgroup: (if ((.[0].anonymous) or ($second == "true")) | |
| then (.[0].appId + "-" + (if .[0].branch == "" then "default" else .[0].branch end)) | |
| else "authenticated" end) | |
| })') | |
| 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 Steam source: probe the manifest once; if changed, download the game | |
| # once and upload the bits the tool needs (Managed DLLs + appmanifest + Status.json) | |
| # as an artifact every variant of this source reuses. The only Steam logins happen here. | |
| download_sources: | |
| name: "Download ${{ matrix.source }}" | |
| runs-on: ubuntu-latest | |
| needs: load | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJson(needs.load.outputs.sources) }} | |
| # Per-source download group (matrix.dlgroup): server sources run parallel; the two | |
| # client sources share one account so they're serialized, unless a 2nd account is | |
| # enabled (then each gets its own group). See ARCHITECTURE.md. | |
| concurrency: | |
| group: redist-dl-${{ matrix.dlgroup }} | |
| cancel-in-progress: false | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| - 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 }} | |
| STEAM_USERNAME_2: ${{ secrets.STEAM_USERNAME_2 }} | |
| STEAM_PASSWORD_2: ${{ secrets.STEAM_PASSWORD_2 }} | |
| STEAM_ACCOUNT: ${{ matrix.steamAccount }} | |
| 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 | |
| # account 2 = optional 2nd Steam account (preview client); see ARCHITECTURE.md. | |
| if [ "$STEAM_ACCOUNT" = "2" ]; then | |
| STEAM_USERNAME="$STEAM_USERNAME_2" | |
| STEAM_PASSWORD="$STEAM_PASSWORD_2" | |
| if [ -z "$STEAM_USERNAME" ] || [ -z "$STEAM_PASSWORD" ]; then | |
| echo "::error::Source ${SOURCE} is assigned Steam account 2 but STEAM_USERNAME_2 / STEAM_PASSWORD_2 are not set." | |
| exit 1 | |
| fi | |
| 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 | |
| # DepotDownloader can drop its connection mid-probe and emit no "Manifest <id>" | |
| # line — a known transient (SteamRE/DepotDownloader #223, #691). Retry, gate on | |
| # a valid id, fail loud only when exhausted. | |
| current_manifest="" | |
| manifest_output="" | |
| attempts=4 | |
| for i in $(seq 1 "$attempts"); do | |
| # '|| true': DepotDownloader can exit non-zero even on a successful | |
| # manifest fetch. The id-validation below is the real gate. | |
| manifest_output=$(depot_downloader/DepotDownloader \ | |
| -app "$APP_ID" \ | |
| -depot "$APP_DEPOT_ID" \ | |
| -manifest-only \ | |
| "${auth_args[@]}" \ | |
| "${beta_args[@]}" \ | |
| -dir redist/temp_depots || true) | |
| # Grab the number after "Manifest" (64-bit id, ~17-20 digits, variable 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 | |
| break | |
| fi | |
| echo "::warning::Probe attempt ${i}/${attempts} for source ${SOURCE} produced no manifest id (likely a transient Steam connection drop); retrying after backoff..." | |
| [ "$i" -lt "$attempts" ] && sleep $((i * 15)) | |
| done | |
| if ! [[ "$current_manifest" =~ ^[0-9]{10,20}$ ]]; then | |
| echo "::error::Could not extract a manifest id for source ${SOURCE} after ${attempts} attempts (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" | |
| # Download if ANY variant's recorded manifest id differs from the live one. | |
| 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 }} | |
| STEAM_USERNAME_2: ${{ secrets.STEAM_USERNAME_2 }} | |
| STEAM_PASSWORD_2: ${{ secrets.STEAM_PASSWORD_2 }} | |
| STEAM_ACCOUNT: ${{ matrix.steamAccount }} | |
| APP_ID: ${{ matrix.appId }} | |
| APP_BRANCH_NAME: ${{ matrix.branch }} | |
| ANONYMOUS: ${{ matrix.anonymous }} | |
| run: | | |
| set -uo pipefail | |
| # Pick the credential set for this source (see "Probe source manifest"): | |
| # account 2 is the optional second account for the preview client source | |
| # so it logs into a DIFFERENT account than the default client source. | |
| if [ "$STEAM_ACCOUNT" = "2" ]; then | |
| STEAM_USERNAME="$STEAM_USERNAME_2" | |
| STEAM_PASSWORD="$STEAM_PASSWORD_2" | |
| if [ -z "$STEAM_USERNAME" ] || [ -z "$STEAM_PASSWORD" ]; then | |
| echo "::error::This source is assigned Steam account 2 but STEAM_USERNAME_2 / STEAM_PASSWORD_2 are not set." | |
| exit 1 | |
| fi | |
| fi | |
| 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's exit code is unreliable, so retry and gate on the real artifact: | |
| # appmanifest_<appid>.acf, written only on a completed update and needed by the tool. | |
| 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 | |
| # Upload only the small bits the tool reads (not the multi-GB game). The | |
| # artifact's presence is also the "source changed" signal update_variant keys off. | |
| - 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: pull the source artifact, run the tool, open | |
| # the rolling PR. No Steam login here, so nothing to serialize. | |
| update_variant: | |
| name: "Update ${{ matrix.variant }}" | |
| runs-on: ubuntu-latest | |
| needs: [load, download_sources] | |
| # Job-level permissions REPLACE workflow defaults: contents (checkout) + actions:read | |
| # (the artifacts gate). The PR is created with secrets.PAT, independent of these. | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| actions: read | |
| # Run even if a source download failed (those variants find no artifact and skip); | |
| # only skip if load itself failed. | |
| 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 API error: only a SUCCESSFUL listing without our artifact means | |
| # "unchanged". A failed gh call must NOT be read as unchanged. 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@v7 | |
| 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 so the next probe sees "no change". Outside REDIST_DIR so | |
| # the discard step never reverts it. | |
| - 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" | |
| printf '%s\n' "$(cat .commit)" | |
| echo "COMMIT_EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| else | |
| # No .commit => version unchanged (Steam build moved but game version didn't). | |
| # Can't republish the same NuGet version, so discard the regenerated DLLs and | |
| # keep only the recorded manifest id; the DLLs publish on the next version bump. | |
| 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 | |
| # Commits to a fixed-name branch and keeps the rolling PR updated until merged; | |
| # delete-branch cleans up afterwards. | |
| - 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" | |
| # On any scheduled-run failure, open (or comment on) a tracking issue so breakage | |
| # isn't silent. | |
| 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") | |
| # --repo is required here: no checkout, and gh doesn't read GITHUB_REPOSITORY. | |
| gh label create update-failure --repo "$REPO" --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 | |
| # Re-enable this scheduled workflow (GitHub disables them after 60 days idle). | |
| # Inlined gh-api call, no external dependency. continue-on-error: the default token | |
| # occasionally 401s here; keepalive is non-critical and self-healing, so a blip | |
| # must not redden the run. | |
| continue-on-error: true | |
| permissions: | |
| actions: write | |
| steps: | |
| - name: Re-enable this scheduled workflow | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| # GITHUB_WORKFLOW_REF is "<owner>/<repo>/.github/workflows/<file>@<ref>". | |
| # Validate the shape, derive the workflow file path, and re-enable it. | |
| case "${GITHUB_WORKFLOW_REF:?}" in | |
| "${GITHUB_REPOSITORY:?}"/.github/workflows/*.y*ml@*) ;; | |
| *) echo "::error::Unexpected GITHUB_WORKFLOW_REF: ${GITHUB_WORKFLOW_REF}"; exit 1 ;; | |
| esac | |
| workflow="${GITHUB_WORKFLOW_REF%%@*}" | |
| workflow="${workflow#"${GITHUB_REPOSITORY}"/.github/workflows/}" | |
| gh api -X PUT "repos/${GITHUB_REPOSITORY}/actions/workflows/${workflow}/enable" |