name: Release on: push: tags: - "v*" workflow_dispatch: inputs: prerelease: description: "Mark the GitHub release as a prerelease" required: false default: "true" type: choice options: ["true", "false"] permissions: contents: write env: CARGO_TERM_COLOR: always jobs: build: name: build ${{ matrix.target }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: - os: ubuntu-latest target: linux-x86_64 binary: geth - os: macos-15-intel target: macos-x86_64 binary: geth - os: macos-15 target: macos-aarch64 binary: geth - os: windows-latest target: windows-x86_64 binary: geth.exe steps: - name: Checkout uses: actions/checkout@v4 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Cache Cargo uses: Swatinem/rust-cache@v2 - name: Build release binary run: cargo build --release -p geth - name: Package Unix artifact if: runner.os != 'Windows' shell: bash run: | set -euo pipefail version="${GITHUB_REF_NAME:-manual}" pkg="geth-${version}-${{ matrix.target }}" mkdir -p "${pkg}" dist cp "target/release/${{ matrix.binary }}" "${pkg}/" cp README.md "${pkg}/" cp LICENSE-* "${pkg}/" cp -R docs "${pkg}/docs" tar -czf "dist/${pkg}.tar.gz" "${pkg}" if command -v sha256sum >/dev/null 2>&1; then sha256sum "dist/${pkg}.tar.gz" | awk '{ print $1 }' > "dist/${pkg}.tar.gz.sha256" else shasum -a 256 "dist/${pkg}.tar.gz" | awk '{ print $1 }' > "dist/${pkg}.tar.gz.sha256" fi - name: Smoke-test Unix artifact if: runner.os != 'Windows' shell: bash run: | set -euo pipefail version="${GITHUB_REF_NAME:-manual}" pkg="geth-${version}-${{ matrix.target }}" expected="$(cat "dist/${pkg}.tar.gz.sha256")" if command -v sha256sum >/dev/null 2>&1; then actual="$(sha256sum "dist/${pkg}.tar.gz" | awk '{ print $1 }')" else actual="$(shasum -a 256 "dist/${pkg}.tar.gz" | awk '{ print $1 }')" fi test "${actual}" = "${expected}" case "${version}" in v[0-9]*) test "$(scripts/install.sh --version "${version}" --print-asset)" = "${pkg}.tar.gz" ;; esac mkdir -p smoke tar -xzf "dist/${pkg}.tar.gz" -C smoke "smoke/${pkg}/${{ matrix.binary }}" --version installer_version="${version}" case "${installer_version}" in v[0-9]*) ;; *) installer_version="v0.0.0-smoke" ;; esac scripts/install.sh \ --version "${installer_version}" \ --archive "dist/${pkg}.tar.gz" \ --install-dir "${PWD}/smoke/install/bin" \ --no-modify-path "smoke/install/bin/geth" --version printf '%064d\n' 0 > smoke/bad.sha256 if scripts/install.sh \ --version "${installer_version}" \ --archive "dist/${pkg}.tar.gz" \ --checksum "smoke/bad.sha256" \ --install-dir "${PWD}/smoke/rejected" \ --no-modify-path; then echo "installer accepted an invalid checksum" >&2 exit 1 fi test -f "smoke/${pkg}/README.md" test -f "smoke/${pkg}/LICENSE-MIT" test -f "smoke/${pkg}/LICENSE-APACHE" test -d "smoke/${pkg}/docs" - name: Package Windows artifact if: runner.os == 'Windows' shell: pwsh run: | $version = if ($env:GITHUB_REF_NAME) { $env:GITHUB_REF_NAME } else { "manual" } $pkg = "geth-$version-${{ matrix.target }}" New-Item -ItemType Directory -Force -Path $pkg, dist | Out-Null Copy-Item "target/release/${{ matrix.binary }}" "$pkg/" Copy-Item README.md "$pkg/" Copy-Item LICENSE-* "$pkg/" Copy-Item docs "$pkg/docs" -Recurse Compress-Archive -Path "$pkg/*" -DestinationPath "dist/$pkg.zip" -Force (Get-FileHash -LiteralPath "dist/$pkg.zip" -Algorithm SHA256).Hash.ToLowerInvariant() | Set-Content -NoNewline "dist/$pkg.zip.sha256" - name: Smoke-test Windows artifact if: runner.os == 'Windows' shell: pwsh run: | $version = if ($env:GITHUB_REF_NAME) { $env:GITHUB_REF_NAME } else { "manual" } $pkg = "geth-$version-${{ matrix.target }}" $expected = (Get-Content -LiteralPath "dist/$pkg.zip.sha256" -Raw).Trim() $actual = (Get-FileHash -LiteralPath "dist/$pkg.zip" -Algorithm SHA256).Hash.ToLowerInvariant() if ($actual -ne $expected) { throw "release archive checksum mismatch" } if ($version -match '^v[0-9]') { $installerAsset = & scripts/install.ps1 -Version $version -PrintAsset if ($installerAsset -ne "$pkg.zip") { throw "installer selected $installerAsset instead of $pkg.zip" } } New-Item -ItemType Directory -Force -Path smoke | Out-Null Expand-Archive -Path "dist/$pkg.zip" -DestinationPath "smoke/$pkg" -Force & "smoke/$pkg/${{ matrix.binary }}" --version $installerVersion = if ($version -match '^v[0-9]') { $version } else { 'v0.0.0-smoke' } $installerArgs = @{ Version = $installerVersion ArchivePath = "dist/$pkg.zip" InstallDir = "$pwd/smoke/install/bin" NoModifyPath = $true } & scripts/install.ps1 @installerArgs & "smoke/install/bin/geth.exe" --version Set-Content -NoNewline -LiteralPath "smoke/bad.sha256" -Value ('0' * 64) $invalidChecksumArgs = $installerArgs.Clone() $invalidChecksumArgs['ChecksumPath'] = "smoke/bad.sha256" $invalidChecksumArgs['InstallDir'] = "$pwd/smoke/rejected" $checksumRejected = $false try { & scripts/install.ps1 @invalidChecksumArgs } catch { $checksumRejected = $true } if (-not $checksumRejected) { throw "installer accepted an invalid checksum" } if (!(Test-Path "smoke/$pkg/README.md")) { throw "README.md missing from archive" } if (!(Test-Path "smoke/$pkg/LICENSE-MIT")) { throw "LICENSE-MIT missing from archive" } if (!(Test-Path "smoke/$pkg/LICENSE-APACHE")) { throw "LICENSE-APACHE missing from archive" } if (!(Test-Path "smoke/$pkg/docs")) { throw "docs missing from archive" } - name: Upload artifact uses: actions/upload-artifact@v4 with: name: geth-${{ matrix.target }} path: dist/* if-no-files-found: error github-release: name: publish GitHub release runs-on: ubuntu-latest needs: build if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' steps: - name: Checkout installer entrypoints uses: actions/checkout@v4 - name: Download artifacts uses: actions/download-artifact@v4 with: path: dist merge-multiple: true - name: Add installer entrypoints and aggregate checksums shell: bash run: | set -euo pipefail cp scripts/install.sh scripts/install.ps1 dist/ cd dist sha256sum geth-*.tar.gz geth-*.zip install.sh install.ps1 > SHA256SUMS - name: Publish release uses: softprops/action-gh-release@v2 with: files: dist/* prerelease: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.prerelease == 'true' }} generate_release_notes: true