Skip to content

🤖 Auto-update server-preview-publicized redist files #73

🤖 Auto-update server-preview-publicized redist files

🤖 Auto-update server-preview-publicized redist files #73

Workflow file for this run

name: "Redist Verify"
# Uses `pull_request` (NOT `pull_request_target`): validation runs only PR-supplied
# data (no repo secrets on fork PRs). Auto-merge/approve are gated on the trusted bot
# author (`rocketmodfixadmin`), which a fork cannot spoof.
on:
pull_request:
branches:
- master
paths:
- 'redist/**'
types:
- opened
- synchronize
- reopened
- ready_for_review
jobs:
verify:
name: "Verify Redist Update"
runs-on: ubuntu-latest
env:
ALLOW_AUTO_MERGE_REDIST_PR: ${{ vars.ALLOW_AUTO_MERGE_REDIST_PR }}
permissions:
pull-requests: write
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Resolve variant and redist directory
run: |
set -euo pipefail
# Automated redist PRs use the branch name redist-update/<variant>.
if [[ "${GITHUB_HEAD_REF}" != redist-update/* ]]; then
echo "::error::This validator only handles automated 'redist-update/<variant>' branches (got '${GITHUB_HEAD_REF}')."
exit 1
fi
VARIANT="${GITHUB_HEAD_REF##*/}"
echo "Detected variant: $VARIANT"
# Single source of truth: map variant -> dir via .github/variants.json.
REDIST_DIR=$(jq -r --arg v "$VARIANT" '.[] | select(.variant == $v) | .dir' .github/variants.json)
IS_PREVIEW=$(jq -r --arg v "$VARIANT" '.[] | select(.variant == $v) | .preview' .github/variants.json)
if [ -z "$REDIST_DIR" ] || [ "$REDIST_DIR" = "null" ]; then
echo "::error::Unknown variant '$VARIANT' (not found in .github/variants.json)."
exit 1
fi
if [ ! -d "$REDIST_DIR" ]; then
echo "::error::Redist directory does not exist: $REDIST_DIR"
exit 1
fi
echo "✅ Variant=$VARIANT Dir=$REDIST_DIR Preview=$IS_PREVIEW"
echo "VARIANT=$VARIANT" >> "$GITHUB_ENV"
echo "REDIST_DIR=$REDIST_DIR" >> "$GITHUB_ENV"
# Preview variants track version.preview.json; everyone else version.json.
if [ "$IS_PREVIEW" = "true" ]; then
echo "VERSION_FILE=$REDIST_DIR/version.preview.json" >> "$GITHUB_ENV"
else
echo "VERSION_FILE=$REDIST_DIR/version.json" >> "$GITHUB_ENV"
fi
- name: Validate required DLL and XML files
run: |
set -euo pipefail
echo "📋 Validating required DLL and XML documentation files in $REDIST_DIR..."
REQUIRED_DLLS=(
"Assembly-CSharp.dll"
"SDG.NetPak.Runtime.dll"
"com.rlabrecque.steamworks.net.dll"
"SDG.Glazier.Runtime.dll"
"SDG.HostBans.Runtime.dll"
"SDG.NetTransport.dll"
"SystemEx.dll"
"UnityEx.dll"
"UnturnedDat.dll"
)
# DLLs that must ship with a matching XML doc.
XML_FOR=("Assembly-CSharp.dll" "SDG.NetPak.Runtime.dll")
missing=()
for dll in "${REQUIRED_DLLS[@]}"; do
if [ -f "$REDIST_DIR/$dll" ]; then echo "✅ $dll"; else echo "❌ $dll"; missing+=("$dll"); fi
done
for dll in "${XML_FOR[@]}"; do
xml="${dll%.dll}.xml"
if [ -f "$REDIST_DIR/$xml" ]; then echo "✅ $xml"; else echo "❌ $xml"; missing+=("$xml"); fi
done
if [ ${#missing[@]} -ne 0 ]; then
echo "::error::Missing required files: ${missing[*]}"
exit 1
fi
echo "✅ All required files present."
- name: Validate file hashes against manifest.sha256.json
run: |
set -euo pipefail
HASH_FILE="$REDIST_DIR/manifest.sha256.json"
if [ ! -f "$HASH_FILE" ]; then
echo "::error::Missing $HASH_FILE — cannot verify file integrity."
exit 1
fi
echo "🔒 Verifying SHA-256 of each tracked file against manifest.sha256.json..."
# manifest.sha256.json: { "<filename>": "<sha256>" }. sha256sum -c wants
# "<hash> <path>"; run it from inside the redist dir so names resolve.
(
cd "$REDIST_DIR"
jq -r 'to_entries[] | "\(.value) \(.key)"' manifest.sha256.json | sha256sum -c -
)
echo "✅ All file hashes match the manifest."
- name: Validate this is a newer upstream build
run: |
set -euo pipefail
if [ ! -f "$VERSION_FILE" ]; then
echo "::error::Missing $VERSION_FILE."
exit 1
fi
NEW_VER=$(jq -r '.NuGetVersion' "$VERSION_FILE")
NEW_BUILD=$(jq -r '.BuildId' "$VERSION_FILE")
echo "PR: version=$NEW_VER buildId=$NEW_BUILD ($VERSION_FILE)"
# Compare against the same file on the base branch (self-contained; no
# network dependency on nuget.org).
git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.base.ref }}" >/dev/null 2>&1 || true
BASE_JSON=$(git show "origin/${{ github.event.pull_request.base.ref }}:$VERSION_FILE" 2>/dev/null || echo "")
if [ -z "$BASE_JSON" ]; then
echo "No baseline on '${{ github.event.pull_request.base.ref }}' (new file) — skipping the check."
exit 0
fi
OLD_VER=$(printf '%s' "$BASE_JSON" | jq -r '.NuGetVersion')
OLD_BUILD=$(printf '%s' "$BASE_JSON" | jq -r '.BuildId')
echo "Base: version=$OLD_VER buildId=$OLD_BUILD"
# Gate on the Steam build id (monotonic per app+branch, rises even on a
# rollback); a LOWER id means an older build = regression. The version can dip
# on a preview rollback, so we don't hard-fail on version.
# See ARCHITECTURE.md ("Why the build id, not the version").
if [[ "$NEW_BUILD" =~ ^[0-9]+$ ]] && [[ "$OLD_BUILD" =~ ^[0-9]+$ ]]; then
if [ "$NEW_BUILD" -lt "$OLD_BUILD" ]; then
echo "::error::Build id went backwards ($OLD_BUILD -> $NEW_BUILD): this is an older upstream build than what is already published."
exit 1
fi
else
echo "::warning::Non-numeric build id ('$OLD_BUILD' -> '$NEW_BUILD'); skipping build-id gate."
fi
# Informational only: surface a version dip without blocking (build id already gated).
result=$(python3 .github/scripts/compare_nuget_version.py "$NEW_VER" "$OLD_VER" 2>/dev/null || echo "unknown")
case "$result" in
lt) echo "::warning::NuGet version decreased ($OLD_VER -> $NEW_VER) but the build id advanced — likely an upstream rollback. Allowing (newer build)." ;;
eq) echo "::warning::NuGet version unchanged ($NEW_VER); the publish step will fail on a duplicate version by design." ;;
gt) echo "✅ Newer build and higher version: $OLD_VER ($OLD_BUILD) -> $NEW_VER ($NEW_BUILD)." ;;
*) echo "Build id advanced; version comparison skipped." ;;
esac
# Auto-merge for the trusted bot's PRs when opted in: GITHUB_TOKEN approves,
# PAT enables the merge (it can merge to a protected base).
- name: Auto-approve PR
if: success() && env.ALLOW_AUTO_MERGE_REDIST_PR == 'true' && github.event.pull_request.user.login == 'rocketmodfixadmin'
uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363 # v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
pull-request-number: ${{ github.event.pull_request.number }}
review-message: "All checks passed (files, hashes, version). Auto-approved automated PR."
# Inline `gh pr merge --auto` (was peter-evans/enable-pull-request-automerge):
# when a PR is already mergeable it merges immediately, so sibling redist PRs
# race on master ("Base branch was modified"). Retry that benign race; fail
# loud otherwise. See ARCHITECTURE.md.
- name: Enable PR automerge
if: success() && env.ALLOW_AUTO_MERGE_REDIST_PR == 'true' && github.event.pull_request.user.login == 'rocketmodfixadmin'
env:
GH_TOKEN: ${{ secrets.PAT }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
set -euo pipefail
attempts=6
for i in $(seq 1 "$attempts"); do
if out=$(gh pr merge "$PR_NUMBER" --repo "$REPO" --squash --auto 2>&1); then
printf '%s\n' "$out"
echo "Auto-merge enabled / PR #${PR_NUMBER} merged (attempt ${i}/${attempts})."
exit 0
fi
printf '%s\n' "$out"
if printf '%s' "$out" | grep -qi 'Base branch was modified'; then
echo "::warning::PR #${PR_NUMBER} merge raced a sibling variant merge (attempt ${i}/${attempts}); retrying after backoff..."
[ "$i" -lt "$attempts" ] && sleep $((i * 10))
continue
fi
echo "::error::Enabling auto-merge for PR #${PR_NUMBER} failed (non-transient)."
exit 1
done
echo "::error::PR #${PR_NUMBER} could not be merged after ${attempts} attempts (base branch kept moving)."
exit 1