git / moonbite-network / .github/workflows/release-bundles.yml · raw

# Reproducible, attested Windows bundles (solo miner + desktop wallet).## On every `v*` tag (or by hand with workflow_dispatch) this workflow:#   1. downloads the node binaries for the SAME version from the moonbite-core#      GitHub Release and verifies them three ways before touching them:#      SHA256SUMS.txt, Sigstore attestation (gh attestation verify), and#      minisign when deploy/moonbite-release.pub is no longer the placeholder;#   2. builds moonbite-miner-windows-x86_64.zip and#      moonbite-wallet-windows-x86_64.zip with scripts/release/make-bundle.py#      (fixed 1980-01-01 timestamps, sorted members, fixed modes) - TWICE, on#      two separate runner VMs - and fails if the two builds differ;#   3. writes SHA256SUMS.txt, attests every asset with#      actions/attest-build-provenance (Sigstore / SLSA provenance), signs the#      manifest with minisign when the MINISIGN_SECRET_KEY secret exists, and#      uploads everything to this repo's GitHub Release for the tag.## Why: a pseudonymous operator cannot get Authenticode, so SmartScreen will# warn on these files. Users verify instead with scripts/release/verify-release.sh# (or Verify-Release.ps1); docs/REPRODUCIBLE-BUILDS.md explains what each check# proves. Every third-party action is pinned to a full commit SHA.name: Release bundleson:  push:    tags:      - "v*"  workflow_dispatch:    inputs:      core_tag:        description: "moonbite-core release tag to take node binaries from (default: this tag)"        required: false        default: ""      release_tag:        description: "Tag of THIS repo to attach the bundles to (default: the ref being run)"        required: false        default: ""env:  CORE_REPO: "moonbitecoin/moonbite-core"  CORE_ASSET: "moonbite-miner-windows-x86_64.zip"  ATTEST_OWNER: "moonbitecoin"  TZ: "UTC"  LC_ALL: "C"  PYTHONHASHSEED: "0"jobs:  build:    name: "build bundles (pass ${{ matrix.pass }})"    runs-on: ubuntu-22.04    strategy:      fail-fast: false      matrix:        pass: [1, 2]    steps:      - name: Checkout        uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4      - name: Set up Python        uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5        with:          python-version: "3.12"      - name: Resolve versions        run: |          set -e          CORE_TAG="${{ github.event.inputs.core_tag }}"          REL_TAG="${{ github.event.inputs.release_tag }}"          [ -n "$CORE_TAG" ] || CORE_TAG="${GITHUB_REF_NAME}"          [ -n "$REL_TAG" ]  || REL_TAG="${GITHUB_REF_NAME}"          case "$CORE_TAG" in v*) ;; *) echo "::error::core_tag must look like vX.Y.Z (got '$CORE_TAG')"; exit 1;; esac          echo "CORE_TAG=$CORE_TAG" >> "$GITHUB_ENV"          echo "REL_TAG=$REL_TAG"   >> "$GITHUB_ENV"          echo "core: $CORE_REPO@$CORE_TAG  ->  release: $REL_TAG"      # Downloaded assets are untrusted until verified; they go into their own      # empty directory and nothing is executed from it.      - name: Download node binaries from the core release        env:          GH_TOKEN: ${{ github.token }}        run: |          set -e          mkdir -p core-dl          gh release download "$CORE_TAG" --repo "$CORE_REPO" --dir core-dl \            --pattern "$CORE_ASSET" --pattern "SHA256SUMS.txt" --pattern "SHA256SUMS.txt.minisig" \            || true          ls -l core-dl          test -f "core-dl/$CORE_ASSET"       || { echo "::error::$CORE_ASSET not found on $CORE_REPO release $CORE_TAG"; exit 1; }          test -f core-dl/SHA256SUMS.txt     || { echo "::error::SHA256SUMS.txt not found on $CORE_REPO release $CORE_TAG"; exit 1; }      - name: Verify core assets (sha256 + Sigstore attestation + minisign)        env:          GH_TOKEN: ${{ github.token }}        run: |          set -euo pipefail          cd core-dl          echo "== sha256"          sha256sum -c SHA256SUMS.txt --ignore-missing --strict          echo "== Sigstore attestation (built by $CORE_REPO's release workflow)"          gh attestation verify "$CORE_ASSET" --owner "$ATTEST_OWNER" --repo "$CORE_REPO"          echo "== minisign"          PUB="$GITHUB_WORKSPACE/deploy/moonbite-release.pub"          KEYLINE="$(grep -v '^[[:space:]]*#' "$PUB" | grep -v '^[[:space:]]*$' | grep -v '^untrusted comment' | head -1 || true)"          if [ -z "$KEYLINE" ] || [ "${KEYLINE#REPLACE_ME}" != "$KEYLINE" ]; then            echo "::notice::deploy/moonbite-release.pub is still the placeholder; minisign check skipped (sha256 + attestation enforced)."          elif [ ! -f SHA256SUMS.txt.minisig ]; then            echo "::error::release key is configured but $CORE_REPO $CORE_TAG has no SHA256SUMS.txt.minisig"; exit 1          else            sudo apt-get update -qq && sudo apt-get install -y -qq minisign            minisign -Vm SHA256SUMS.txt -P "$KEYLINE"          fi      - name: Extract node binaries        run: |          set -e          mkdir -p core-x          python -I -c 'import sys,zipfile; zipfile.ZipFile(sys.argv[1]).extractall(sys.argv[2])' "core-dl/$CORE_ASSET" core-x          find core-x -type f | sort          for f in moonbited.exe moonbite-cli.exe; do            P="$(find core-x -type f -name "$f" | head -1)"            test -n "$P" || { echo "::error::$f missing from $CORE_ASSET"; exit 1; }            cp "$P" "core-x/$f"          done      - name: Build bundles (deterministic)        run: |          set -e          mkdir -p dist          rm -f dist/SHA256SUMS.txt          python -I scripts/release/make-bundle.py --sort \            --out dist/moonbite-miner-windows-x86_64.zip --root moonbite-miner --sums dist/SHA256SUMS.txt \            core-x/moonbited.exe core-x/moonbite-cli.exe \            release/miner/mine.ps1 release/miner/Mine-MoonBite.bat release/miner/README.md          python -I scripts/release/make-bundle.py --sort \            --out dist/moonbite-wallet-windows-x86_64.zip --root moonbite-wallet --sums dist/SHA256SUMS.txt \            release/miner/moonbite-wallet.exe docs/WALLET.md=README.md          # Pin the exact node binaries the miner bundle carries, so the manifest          # ties this release to the verified core release.          sha256sum core-x/moonbited.exe core-x/moonbite-cli.exe | sed 's# core-x/# *#' >> dist/SHA256SUMS.txt          echo "# node binaries from $CORE_REPO release $CORE_TAG" >> dist/SHA256SUMS.txt          LC_ALL=C sort -k2 -o dist/SHA256SUMS.txt dist/SHA256SUMS.txt          cat dist/SHA256SUMS.txt      - name: Upload artifact        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4        with:          name: "bundles-pass${{ matrix.pass }}"          path: dist/          if-no-files-found: error  reproducibility:    name: "Reproducibility check"    needs: build    runs-on: ubuntu-22.04    steps:      - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4        with:          name: bundles-pass1          path: a      - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4        with:          name: bundles-pass2          path: b      - name: Compare the two independent builds        run: |          set -e          echo "== pass 1"; cat a/SHA256SUMS.txt          echo "== pass 2"; cat b/SHA256SUMS.txt          (cd a && sha256sum *.zip) > sa.txt          (cd b && sha256sum *.zip) > sb.txt          if ! diff sa.txt sb.txt; then            echo "::error::bundles are NOT reproducible across runners"; exit 1          fi          echo "REPRODUCIBLE: both passes produced identical zips"  release:    name: Publish bundles    needs: [build, reproducibility]    if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'    runs-on: ubuntu-22.04    permissions:      contents: write       # upload assets to the Release      id-token: write       # Sigstore OIDC identity      attestations: write   # store attestations on this repo    env:      # `secrets` is not usable in step `if:`; expose presence only.      HAVE_MINISIGN_KEY: ${{ secrets.MINISIGN_SECRET_KEY != '' }}    steps:      - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4        with:          name: bundles-pass1          path: dist      - name: Resolve release tag        run: |          REL_TAG="${{ github.event.inputs.release_tag }}"          [ -n "$REL_TAG" ] || REL_TAG="${GITHUB_REF_NAME}"          echo "REL_TAG=$REL_TAG" >> "$GITHUB_ENV"      - name: Attest build provenance (Sigstore)        uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2        with:          subject-path: |            dist/*.zip            dist/SHA256SUMS.txt      - name: Sign SHA256SUMS.txt with minisign (optional)        if: env.HAVE_MINISIGN_KEY == 'true'        env:          MINISIGN_SECRET_KEY: ${{ secrets.MINISIGN_SECRET_KEY }}          MINISIGN_PASSWORD: ${{ secrets.MINISIGN_PASSWORD }}        run: |          set -euo pipefail          sudo apt-get update -qq && sudo apt-get install -y -qq minisign          umask 077          KEY="$RUNNER_TEMP/minisign.key"          printf '%s\n' "$MINISIGN_SECRET_KEY" > "$KEY"          TC="MoonBite bundles ${REL_TAG} SHA256SUMS.txt, commit ${GITHUB_SHA}"          if [ -n "${MINISIGN_PASSWORD:-}" ]; then            printf '%s\n' "$MINISIGN_PASSWORD" | minisign -S -s "$KEY" -t "$TC" -m dist/SHA256SUMS.txt          else            minisign -S -W -s "$KEY" -t "$TC" -m dist/SHA256SUMS.txt          fi          rm -f "$KEY"          cat dist/SHA256SUMS.txt.minisig      - name: Note unsigned manifest        if: env.HAVE_MINISIGN_KEY != 'true'        run: echo "::notice::MINISIGN_SECRET_KEY not configured; SHA256SUMS.txt published unsigned (Sigstore attestation still applies)."      - name: Upload to GitHub Release        uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2        with:          tag_name: ${{ env.REL_TAG }}          files: dist/*          generate_release_notes: true          body: |            MoonBite Windows bundles: solo miner (node + CLI + mine.ps1) and desktop wallet.            These files are not Authenticode-signed (see docs/REPRODUCIBLE-BUILDS.md);            SmartScreen will warn. Verify instead:            - `scripts/release/verify-release.sh <file>` or `scripts/release/Verify-Release.ps1 <file>`            - `sha256sum -c SHA256SUMS.txt --ignore-missing`            - `gh attestation verify <file> --owner moonbitecoin`            - `minisign -Vm SHA256SUMS.txt -p deploy/moonbite-release.pub` when a `.minisig` is attached            Both zips were built twice on separate runners in this run and matched            byte for byte. The node binaries inside come from the verified            moonbite-core release of the same version (hashes listed in SHA256SUMS.txt).