From 2b1a263c47defa5d380cb2c38df4b0131521a18b Mon Sep 17 00:00:00 2001 From: nekomoto911 Date: Tue, 12 May 2026 22:08:33 +0800 Subject: [PATCH 1/6] ci(release): containerize build for glibc compat, add PR dry-run + actionlint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Self-hosted runner is Ubuntu 24.04 (glibc 2.39); staging-mainnet nodes are Debian 11 (glibc 2.31). Binaries built directly on the runner require GLIBC_2.32+ symbols and fail to start on target with `GLIBC_2.34 not found`. Changes to .github/workflows/release.yml: - Build inside container image, default `rust:1.93.0-bullseye` (glibc 2.31), overridable via `vars.RELEASE_BUILD_IMAGE` for future OS bumps. - Assert `rustc --version` matches `rust-toolchain.toml` so image drift surfaces as a clear error instead of silently building with the wrong toolchain. - Install gh CLI inside the container (the rust:* base image lacks it). - Require the GitHub Release to exist before tag push (UI-driven release notes flow); CI now only uploads assets and fails fast if no release. - Drop workflow_dispatch and the auto-create-release fallback — single trigger source avoids the dispatch-vs-tag commit mismatch and keeps release notes human-authored. - Add a pull_request trigger scoped to .github/workflows/release.yml so changes to this file run a full container build as a dry-run; binaries are uploaded as a 7-day artifact for reviewer inspection. Real upload step is skipped on PR events. New .github/workflows/lint-workflows.yml: - Runs actionlint@1.7.7 on PRs touching any .github/workflows/** file. --- .github/workflows/lint-workflows.yml | 26 ++++++ .github/workflows/release.yml | 130 +++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 .github/workflows/lint-workflows.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/lint-workflows.yml b/.github/workflows/lint-workflows.yml new file mode 100644 index 00000000..76e0aab4 --- /dev/null +++ b/.github/workflows/lint-workflows.yml @@ -0,0 +1,26 @@ +name: Lint workflows + +on: + pull_request: + paths: + - '.github/workflows/**' + +concurrency: + group: lint-workflows-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + actionlint: + name: actionlint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install actionlint + run: | + set -euo pipefail + bash <(curl -fsSL https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) 1.7.7 + ./actionlint --version + + - name: Run actionlint + run: ./actionlint -color diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..e69f89bd --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,130 @@ +name: Release + +on: + push: + tags: ['gravity-mainnet-*'] + pull_request: + paths: + - '.github/workflows/release.yml' + +concurrency: + group: release-${{ github.event.pull_request.number || github.ref_name }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: + contents: write + +jobs: + build-and-release: + name: Build and release + runs-on: [self-hosted, linux] + # Image pins glibc + rust toolchain for forward-compatible binaries. + # Override per-repo via Settings → Variables → Actions → RELEASE_BUILD_IMAGE. + # Default targets Debian 11 (glibc 2.31) to match current staging/mainnet OS. + container: + image: ${{ vars.RELEASE_BUILD_IMAGE || 'rust:1.93.0-bullseye' }} + timeout-minutes: 180 + env: + RUSTFLAGS: "--cfg tokio_unstable" + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Assert rustc matches rust-toolchain.toml + shell: bash + run: | + set -euo pipefail + expected=$(grep -E '^channel[[:space:]]*=' rust-toolchain.toml | sed -E 's/.*"([^"]+)".*/\1/') + actual=$(rustc --version | awk '{print $2}') + echo "expected: $expected" + echo "actual: $actual" + if [ "$expected" != "$actual" ]; then + echo "rustc in container image does not match rust-toolchain.toml." >&2 + echo "bump vars.RELEASE_BUILD_IMAGE to rust:${expected}-, or update rust-toolchain.toml." >&2 + exit 1 + fi + + - name: Install gh CLI + shell: bash + run: | + set -euo pipefail + if command -v gh >/dev/null 2>&1; then + gh --version + exit 0 + fi + GH_VERSION="2.62.0" + curl -fsSL "https://github.com/cli/gh/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" \ + | tar -xz -C /tmp + install -m0755 "/tmp/gh_${GH_VERSION}_linux_amd64/bin/gh" /usr/local/bin/gh + gh --version + + - name: Build gravity_node and gravity_cli + shell: bash + run: | + set -euo pipefail + cargo build --profile quick-release --bin gravity_node --bin gravity_cli + + - name: Stage release assets and compute sha256 + id: stage + shell: bash + run: | + set -euo pipefail + OUT="${RUNNER_TEMP}/release-assets" + rm -rf "$OUT" && mkdir -p "$OUT" + for bin in gravity_node gravity_cli; do + src="target/quick-release/$bin" + if [ ! -x "$src" ]; then + echo "$src not built or not executable" >&2 + exit 1 + fi + cp "$src" "$OUT/$bin" + ( cd "$OUT" && sha256sum "$bin" > "$bin.sha256" ) + done + ls -lh "$OUT" + echo "--- sha256 ---" + cat "$OUT"/*.sha256 + echo "dir=$OUT" >> "$GITHUB_OUTPUT" + + - name: Upload PR dry-run artifact + if: github.event_name == 'pull_request' + uses: actions/upload-artifact@v4 + with: + name: release-dryrun-${{ github.sha }} + path: ${{ steps.stage.outputs.dir }} + if-no-files-found: error + retention-days: 7 + + - name: Resolve tag + id: tag + if: github.event_name != 'pull_request' + shell: bash + run: | + set -euo pipefail + TAG="${GITHUB_REF##refs/tags/}" + case "$TAG" in + gravity-mainnet-*) ;; + *) echo "tag must start with gravity-mainnet-: $TAG" >&2; exit 1 ;; + esac + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + + - name: Upload assets to GitHub release + if: github.event_name != 'pull_request' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ steps.tag.outputs.tag }} + ASSETS_DIR: ${{ steps.stage.outputs.dir }} + shell: bash + run: | + set -euo pipefail + if ! gh release view "$TAG" >/dev/null 2>&1; then + echo "release $TAG does not exist." >&2 + echo "create it first via GitHub UI (Releases → Draft a new release) with notes, then publish — that will push the tag and re-trigger this workflow." >&2 + exit 1 + fi + gh release upload "$TAG" \ + "$ASSETS_DIR/gravity_node" \ + "$ASSETS_DIR/gravity_node.sha256" \ + "$ASSETS_DIR/gravity_cli" \ + "$ASSETS_DIR/gravity_cli.sha256" \ + --clobber From 89405b92602d46d40fb86bc303050c74ecdaff5b Mon Sep 17 00:00:00 2001 From: nekomoto911 Date: Tue, 12 May 2026 23:32:00 +0800 Subject: [PATCH 2/6] ci(release): fix gh-cli version pin and disable actionlint shellcheck - gh v2.62.0 was never published; download 404'd and broke the dry-run. Bump to v2.92.0 (current latest) and add --retry 3 for transient self-hosted-runner network blips. - actionlint's bundled shellcheck integration treats info-level issues (SC2086 unquoted vars, SC2053 unquoted RHS) as errors, which fails on pre-existing scripts in gate.yml / rust-ci.yml that are out of scope for this PR. Disable shellcheck via `-shellcheck=`; the workflow still validates yaml + expression syntax. Re-enable after a separate cleanup. --- .github/workflows/lint-workflows.yml | 6 +++++- .github/workflows/release.yml | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint-workflows.yml b/.github/workflows/lint-workflows.yml index 76e0aab4..fb09a87e 100644 --- a/.github/workflows/lint-workflows.yml +++ b/.github/workflows/lint-workflows.yml @@ -23,4 +23,8 @@ jobs: ./actionlint --version - name: Run actionlint - run: ./actionlint -color + # Skip shellcheck integration: pre-existing inline scripts in + # gate.yml / rust-ci.yml have SC2086 / SC2053 noise that is out of + # scope for this workflow's purpose (yaml + expression validation). + # Re-enable by dropping `-shellcheck=` once those are cleaned up. + run: ./actionlint -color -shellcheck= diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e69f89bd..df46e4a9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,8 +53,8 @@ jobs: gh --version exit 0 fi - GH_VERSION="2.62.0" - curl -fsSL "https://github.com/cli/gh/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" \ + GH_VERSION="2.92.0" + curl -fsSL --retry 3 "https://github.com/cli/gh/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" \ | tar -xz -C /tmp install -m0755 "/tmp/gh_${GH_VERSION}_linux_amd64/bin/gh" /usr/local/bin/gh gh --version From b396a52f358c2fcaac90f6db83809bd5a1a92aea Mon Sep 17 00:00:00 2001 From: nekomoto911 Date: Wed, 13 May 2026 00:43:06 +0800 Subject: [PATCH 3/6] =?UTF-8?q?ci(release):=20fix=20gh-cli=20download=20UR?= =?UTF-8?q?L=20repo=20(cli/gh=20=E2=86=92=20cli/cli)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gh CLI source repo is `cli/cli`, not `cli/gh` — the prior URL 404'd on every release tag, not just 2.62.0. Both previous failed dry-runs were hitting this same path bug, masked as "version doesn't exist". Verified `https://github.com/cli/cli/releases/download/v2.92.0/gh_2.92.0_linux_amd64.tar.gz` returns HTTP 200. --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index df46e4a9..755d7098 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -54,7 +54,7 @@ jobs: exit 0 fi GH_VERSION="2.92.0" - curl -fsSL --retry 3 "https://github.com/cli/gh/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" \ + curl -fsSL --retry 3 "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" \ | tar -xz -C /tmp install -m0755 "/tmp/gh_${GH_VERSION}_linux_amd64/bin/gh" /usr/local/bin/gh gh --version From a57e0035de1f32eddc4a7a6e45b21f6af54e1d6d Mon Sep 17 00:00:00 2001 From: nekomoto911 Date: Wed, 13 May 2026 00:52:56 +0800 Subject: [PATCH 4/6] ci(release): install build deps in container (clang, libssl-dev, etc.) bindgen needs libclang at build time; the bare `rust:1.93.0-bullseye` image doesn't ship it, so the previous dry-run died at: thread 'main' panicked at bindgen-0.72.1/lib.rs:616 Unable to find libclang: ... Mirror the apt list from gravity_e2e/build_docker.sh (the known-working in-container build env), minus python/test packages. On bullseye the `clang` package does not pull the unversioned libclang.so symlink that clang-sys searches for, so libclang-dev is added explicitly (bookworm gets it transitively, which is why e2e didn't need to call it out). --- .github/workflows/release.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 755d7098..db50b60c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,6 +31,23 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Install build dependencies + # Mirrors gravity_e2e/build_docker.sh apt list (the working + # in-container build env), minus the python/test bits we don't need. + # libclang-dev added explicitly: on bullseye the `clang` package does + # not ship the unversioned libclang.so symlink that bindgen searches + # for, unlike bookworm where it's pulled in transitively. + shell: bash + run: | + set -euo pipefail + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y --no-install-recommends \ + clang llvm libclang-dev \ + build-essential pkg-config \ + libssl-dev libudev-dev \ + procps git jq curl ca-certificates + - name: Assert rustc matches rust-toolchain.toml shell: bash run: | From d75fc1f2b341780ae2ba8c917b01e7babd8824d5 Mon Sep 17 00:00:00 2001 From: nekomoto911 Date: Wed, 13 May 2026 11:23:51 +0800 Subject: [PATCH 5/6] Revert to native runner build (drop container approach) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Decision change: instead of containerizing the build to match production glibc, the runner OS itself will be aligned to production OS. Native build is simpler — no container plumbing, no apt-install on every run, target/ cache reuse between builds. - Drop the `container:` block and the `vars.RELEASE_BUILD_IMAGE` override. - Drop the in-container `Install build dependencies` step (apt-get clang/llvm/...). - Drop the in-container `Install gh CLI` curl-tarball step. - Drop the `cli/gh → cli/cli` URL fix (step no longer exists). - Add a `Show build environment` step that logs runner os-release / ldd / rustc / cargo so any future runner OS drift from production is visible in the run record. - Keep the `Assert rustc matches rust-toolchain.toml` check (rephrased for the native-runner context). - Keep PR dry-run trigger + artifact upload (still valuable for review). Invariant now: runner OS must equal production node OS. Currently staging-mainnet is Debian 11 / glibc 2.31, runner is Ubuntu 24.04 / glibc 2.39 — that gap must be closed on the runner side before this workflow can produce shippable binaries. Runner prerequisites (install once on the runner host): - rust toolchain per rust-toolchain.toml - gh CLI - clang llvm libclang-dev build-essential pkg-config libssl-dev libudev-dev --- .github/workflows/release.yml | 54 +++++++++++++---------------------- 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index db50b60c..37042209 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,11 +18,17 @@ jobs: build-and-release: name: Build and release runs-on: [self-hosted, linux] - # Image pins glibc + rust toolchain for forward-compatible binaries. - # Override per-repo via Settings → Variables → Actions → RELEASE_BUILD_IMAGE. - # Default targets Debian 11 (glibc 2.31) to match current staging/mainnet OS. - container: - image: ${{ vars.RELEASE_BUILD_IMAGE || 'rust:1.93.0-bullseye' }} + # Native build on the runner — no container. + # Invariant: runner OS must match production node OS, otherwise the + # binary's required glibc may exceed what target nodes provide and + # they'll fail to start with `GLIBC_X.YY not found`. Verify before + # changing the runner: `cat /etc/os-release && ldd --version` + # on both runner and target should agree (or runner glibc ≤ target). + # Runner must have these installed once: + # - rust toolchain matching rust-toolchain.toml (rustup default ) + # - gh CLI (`apt install gh` via cli.github.com apt repo) + # - build deps: clang llvm libclang-dev build-essential pkg-config + # libssl-dev libudev-dev timeout-minutes: 180 env: RUSTFLAGS: "--cfg tokio_unstable" @@ -31,22 +37,16 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Install build dependencies - # Mirrors gravity_e2e/build_docker.sh apt list (the working - # in-container build env), minus the python/test bits we don't need. - # libclang-dev added explicitly: on bullseye the `clang` package does - # not ship the unversioned libclang.so symlink that bindgen searches - # for, unlike bookworm where it's pulled in transitively. + - name: Show build environment + # Logs runner OS / glibc / rustc so any future divergence from + # production OS is visible in the release run record. shell: bash run: | set -euo pipefail - export DEBIAN_FRONTEND=noninteractive - apt-get update -qq - apt-get install -y --no-install-recommends \ - clang llvm libclang-dev \ - build-essential pkg-config \ - libssl-dev libudev-dev \ - procps git jq curl ca-certificates + grep -E '^(NAME|VERSION|VERSION_CODENAME)=' /etc/os-release + ldd --version | head -1 + rustc --version + cargo --version - name: Assert rustc matches rust-toolchain.toml shell: bash @@ -57,25 +57,11 @@ jobs: echo "expected: $expected" echo "actual: $actual" if [ "$expected" != "$actual" ]; then - echo "rustc in container image does not match rust-toolchain.toml." >&2 - echo "bump vars.RELEASE_BUILD_IMAGE to rust:${expected}-, or update rust-toolchain.toml." >&2 + echo "rustc on runner does not match rust-toolchain.toml." >&2 + echo "update the runner toolchain (rustup install $expected && rustup default $expected) or update rust-toolchain.toml." >&2 exit 1 fi - - name: Install gh CLI - shell: bash - run: | - set -euo pipefail - if command -v gh >/dev/null 2>&1; then - gh --version - exit 0 - fi - GH_VERSION="2.92.0" - curl -fsSL --retry 3 "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" \ - | tar -xz -C /tmp - install -m0755 "/tmp/gh_${GH_VERSION}_linux_amd64/bin/gh" /usr/local/bin/gh - gh --version - - name: Build gravity_node and gravity_cli shell: bash run: | From 0547ffba01a5da91f375f92f34331bacdf138aca Mon Sep 17 00:00:00 2001 From: nekomoto911 Date: Wed, 13 May 2026 16:19:06 +0800 Subject: [PATCH 6/6] ci(release): also trigger on gravity-testnet-* tags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Testnet releases follow the same publish flow as mainnet — human builds the release notes in GitHub UI, publishes, tag push triggers binary upload via this workflow. Trigger filter and the Resolve tag case-guard updated symmetrically so the validation message stays accurate. --- .github/workflows/release.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 37042209..164b12b2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,9 @@ name: Release on: push: - tags: ['gravity-mainnet-*'] + tags: + - 'gravity-mainnet-*' + - 'gravity-testnet-*' pull_request: paths: - '.github/workflows/release.yml' @@ -106,8 +108,8 @@ jobs: set -euo pipefail TAG="${GITHUB_REF##refs/tags/}" case "$TAG" in - gravity-mainnet-*) ;; - *) echo "tag must start with gravity-mainnet-: $TAG" >&2; exit 1 ;; + gravity-mainnet-*|gravity-testnet-*) ;; + *) echo "tag must start with gravity-mainnet- or gravity-testnet-: $TAG" >&2; exit 1 ;; esac echo "tag=$TAG" >> "$GITHUB_OUTPUT"