Skip to content

Merge beta into master for v24.57.21 + v24.57.22 (disk mount, Storage… #804

Merge beta into master for v24.57.21 + v24.57.22 (disk mount, Storage…

Merge beta into master for v24.57.21 + v24.57.22 (disk mount, Storage… #804

Workflow file for this run

name: Build Release Binaries
on:
push:
branches: [master]
paths:
- 'Cargo.toml'
- 'src/**'
- '.github/workflows/release.yml'
workflow_dispatch:
jobs:
build:
strategy:
# Don't cancel the other arch if one fails — we want partial
# builds (e.g. x86_64 succeeds, aarch64 hits a transient GHCR
# flake) to still produce artifacts we can release manually.
# Without this, the v23.12.15 build had x86_64 marked as
# "cancelled" because aarch64 timed out fetching the cross-rs
# docker image.
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-musl
arch: x86_64
runner: ubuntu-latest
- target: aarch64-unknown-linux-musl
arch: aarch64
runner: ubuntu-latest
runs-on: ${{ matrix.runner }}
# Bound the wall-clock so a stuck network call doesn't hold a
# runner for the full 6-hour GitHub default.
timeout-minutes: 30
steps:
- uses: actions/checkout@v5
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Warnings are errors
# Operator rule (2026-06-11): a compiler warning fails the release.
# Runs natively on the runner (fast, no cross/env-passthrough
# dependency) BEFORE the slow cross builds, so a warning fails in
# ~2 minutes instead of 20.
env:
RUSTFLAGS: "-D warnings"
# --locked: build the exact committed Cargo.lock, never re-resolve to
# "latest compatible". A float drifted `time` onto a version that broke
# `cookie` 0.16.2 on rustc 1.96 and failed the v24.39.0 release.
run: cargo check --locked
- name: Install cross
run: cargo install cross --git https://github.com/cross-rs/cross
- name: Pin libz-sys to working version
run: |
cargo update -p libz-sys --precise 1.1.25
- name: Pre-pull cross-rs docker image with retries
# `cross build` pulls ghcr.io/cross-rs/<target>:main on first
# invocation. GHCR's token endpoint occasionally times out and
# the build dies with exit 125 (docker auth failure). Pull
# explicitly with 5 retries + exponential-ish backoff so a
# transient outage doesn't fail the whole release.
run: |
IMAGE="ghcr.io/cross-rs/${{ matrix.target }}:main"
for attempt in 1 2 3 4 5; do
echo "::group::Pull attempt $attempt for $IMAGE"
if docker pull "$IMAGE"; then
echo "::endgroup::"
echo "✓ pulled $IMAGE on attempt $attempt"
exit 0
fi
echo "::endgroup::"
wait=$((attempt * 15))
echo "::warning::pull attempt $attempt failed, sleeping ${wait}s before retry"
sleep $wait
done
echo "::error::Failed to pull $IMAGE after 5 attempts. GHCR may be having an outage; re-run this job (gh run rerun --failed)."
exit 1
- name: Build static binary
run: cross build --release --target ${{ matrix.target }}
- name: Get version
id: version
run: echo "version=$(grep '^version' Cargo.toml | head -1 | sed 's/.*\"\(.*\)\".*/\1/')" >> "$GITHUB_OUTPUT"
- name: Package binary
run: |
mkdir -p dist
cp target/${{ matrix.target }}/release/wolfstack dist/wolfstack-${{ matrix.arch }}
chmod +x dist/wolfstack-${{ matrix.arch }}
- name: Upload artifact
uses: actions/upload-artifact@v6
with:
name: wolfstack-${{ matrix.arch }}
path: dist/wolfstack-${{ matrix.arch }}
release:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write # gh release create
id-token: write # cosign keyless OIDC + attest provenance
attestations: write # actions/attest-build-provenance
steps:
- uses: actions/checkout@v5
with:
# Need full history so we can extract the release commit body
# for release notes, and regenerate CHANGELOG.md.
fetch-depth: 0
- name: Get version
id: version
run: echo "version=$(grep '^version' Cargo.toml | head -1 | sed 's/.*\"\(.*\)\".*/\1/')" >> "$GITHUB_OUTPUT"
- name: Download all artifacts
uses: actions/download-artifact@v7
with:
path: dist
merge-multiple: true
- name: Compute SHA-256 checksums
run: |
cd dist
sha256sum wolfstack-x86_64 wolfstack-aarch64 > SHA256SUMS
- name: Generate build-provenance attestation
# Run BEFORE cosign signing so subject-path matches just the
# binaries (not the .cosign.bundle files that signing produces).
# Provenance covers both x86_64 and aarch64 in one attestation.
uses: actions/attest-build-provenance@v3
with:
subject-path: |
dist/wolfstack-x86_64
dist/wolfstack-aarch64
- name: Install cosign
uses: sigstore/cosign-installer@v3
with:
cosign-release: 'v2.4.1'
- name: Sign binaries with cosign (keyless)
# Keyless OIDC: no private key to manage. The signing identity
# is the workflow itself, anchored to the GitHub Actions OIDC
# token. Verifiers check the cert chain back to Sigstore's
# Fulcio CA + Rekor transparency log. Verification command is
# in the release notes.
run: |
cd dist
for f in wolfstack-x86_64 wolfstack-aarch64; do
cosign sign-blob --yes \
--bundle "${f}.cosign.bundle" \
"$f"
done
- name: Generate release notes from release-commit body
id: notes
run: |
# Find the most recent release commit (subject starts "v<version>:")
# — that's the commit being released. Its body is the release notes.
# Fallback to a generic note if no v* commit found in last 50.
BODY=$(git log -50 --grep='^v[0-9]' -1 --pretty=format:'%b' || echo "")
SUBJECT=$(git log -50 --grep='^v[0-9]' -1 --pretty=format:'%s' || echo "Release")
{
echo "## $SUBJECT"
echo
if [ -n "$BODY" ]; then
echo "$BODY"
echo
fi
cat <<'EOF'
---
## Verifying this release
Each binary is signed via [cosign](https://docs.sigstore.dev/) keyless OIDC (no key distribution — signing identity is the GitHub Actions workflow itself, anchored to the Sigstore Fulcio CA and the Rekor transparency log) and ships with a [SLSA build provenance](https://slsa.dev/spec/v1.0/provenance) attestation.
**Verify the cosign signature:**
```
cosign verify-blob \
--bundle wolfstack-x86_64.cosign.bundle \
--certificate-identity-regexp 'https://github.com/wolfsoftwaresystemsltd/WolfStack/\.github/workflows/release\.yml@.*' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
wolfstack-x86_64
```
**Verify the build provenance:**
```
gh attestation verify wolfstack-x86_64 --repo wolfsoftwaresystemsltd/WolfStack
```
**Verify the SHA-256 checksum:**
```
sha256sum -c SHA256SUMS
```
## Artifacts
- `wolfstack-x86_64` / `wolfstack-aarch64` — static musl binaries (Linux x86_64 and ARM64 / Raspberry Pi 4+).
- `wolfstack-<arch>.cosign.bundle` — cosign signature bundle (cert + signature + Rekor entry).
- `SHA256SUMS` — checksums for both binaries.
For per-version history see [CHANGELOG.md](https://github.com/wolfsoftwaresystemsltd/WolfStack/blob/master/CHANGELOG.md).
EOF
} > release-notes.md
- name: Create or update release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="v${{ steps.version.outputs.version }}"
# Delete existing release if it exists (update scenario)
gh release delete "$VERSION" --yes 2>/dev/null || true
git push --delete origin "$VERSION" 2>/dev/null || true
# Drop local tag too — with fetch-depth: 0 the checkout pulls
# all tags, so a stale local v* tag disagrees with the just-
# deleted remote one. `gh release create` refuses in that
# state ("tag exists locally but has not been pushed"). The
# next step's `--target master` re-creates the tag fresh.
git tag -d "$VERSION" 2>/dev/null || true
# Create release with binaries, signatures, and checksums
gh release create "$VERSION" \
--target master \
--title "WolfStack $VERSION" \
--notes-file release-notes.md \
dist/wolfstack-x86_64 \
dist/wolfstack-x86_64.cosign.bundle \
dist/wolfstack-aarch64 \
dist/wolfstack-aarch64.cosign.bundle \
dist/SHA256SUMS
- name: Regenerate CHANGELOG.md and push back to master
# Best-effort: CHANGELOG.md is regenerated from `git log` after
# each release so the file always reflects the latest tagged
# version. The workflow's path filter excludes CHANGELOG.md, so
# this push doesn't re-trigger the workflow (no loop).
# If push fails (branch protection, race) the release is still
# cut — operator runs `scripts/gen-changelog.sh` locally and
# commits manually.
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
bash scripts/gen-changelog.sh CHANGELOG.md
if git diff --quiet CHANGELOG.md; then
echo "CHANGELOG.md already up to date"
exit 0
fi
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git add CHANGELOG.md
git commit -m "chore: regenerate CHANGELOG.md for v${{ steps.version.outputs.version }}"
git push origin master || {
echo "::warning::Failed to push CHANGELOG.md update — release was cut, but the file is now stale. Run scripts/gen-changelog.sh locally and commit." >&2
exit 0
}