Skip to content

ci: adopt cargo-dist for signed binary releases - #262

Merged
jensholdgaard merged 6 commits into
mainfrom
ci/cargo-dist-releases
Jun 19, 2026
Merged

ci: adopt cargo-dist for signed binary releases#262
jensholdgaard merged 6 commits into
mainfrom
ci/cargo-dist-releases

Conversation

@jensholdgaard

@jensholdgaard jensholdgaard commented Jun 19, 2026

Copy link
Copy Markdown
Owner

What

Delivers the binary-release track of the first shipping milestone. Replaces the old changelog-only .github/workflows/release.yml (a single git-cliff + action-gh-release step) with a cargo-dist v0.32.0 pipeline that, on a v* semver tag, cross-compiles the ourios-server binary, then generates checksums, a CycloneDX SBOM, and GitHub build-provenance attestations before publishing the GitHub Release.

The complex workflow is tool-generated (dist init + dist generate), not hand-authored.

Why

The signed release assets — GitHub Artifact Attestations + the CycloneDX SBOM — are what flips OpenSSF Scorecard's Signed-Releases check, which is the remaining gap from the supply-chain hardening track.

Config (dist-workspace.toml)

knob value
cargo-dist-version 0.32.0
targets x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu
installers ["shell"] (npm/msi/homebrew skipped for v0)
cargo-cyclonedx true (CycloneDX SBOM per release)
github-attestations true (provenance via actions/attest)

Targets — musl dropped for v0

I configured all four Linux targets (gnu + musl, both arches) and dist plan planned them cleanly. But the actual musl cross-build (which surfaces datafusion/parquet/ring-via-rustls issues) could not be verified clean in this environment — the host is macOS with only the aarch64-apple-darwin target installed and no Linux/musl cross-toolchain (the project uses containerd, not Docker, so cross isn't wired up locally either). Rather than ship an unverified target into a tag-triggered release pipeline, musl is dropped to gnu-only for v0 (matching the "gnu only for v0" default). The stack is rustls + ring (no openssl/aws-lc), so musl is plausibly fine and can be added in a follow-up once it's verified on a Linux runner. macOS/Windows are out of scope for a Linux server backend.

Distributable selection

All workspace crates are version = "0.0.0" / publish = false, which hides every binary from cargo-dist ("this workspace doesn't have anything for dist to Release"). Per dist's own guidance, dist = true in ourios-server's [package.metadata.dist] opts the one server binary back in as the sole distributable; the library crates stay excluded.

Changelog (git-cliff)

cargo-dist owns the GitHub Release and its release notes, reading them from CHANGELOG.md (Keep-a-Changelog format, already in the repo). This cleanly replaces the git-cliff auto-generation step. cliff.toml is left in the tree but is no longer wired into the release pipeline; pruning it can be a separate cleanup.

One upstream-template fix

cargo-dist v0.32.0's generated workflow has a typo in the SBOM upload glob: steps.cargo-cyclonedx.output.paths (should be outputs.paths). actionlint flags it as an undefined-property expression. Left unfixed, the .cdx.xml SBOM files silently fall out of the upload. Fixed to outputs.paths. The cargo-dist-generated actions are otherwise left exactly as dist emits them (pinned by major tag, by design).

Validation

  • dist --version -> cargo-dist 0.32.0
  • dist plan (dry-run) succeeded:
announcing v0.0.0
  ourios-server 0.0.0
    source.tar.gz
      [checksum] source.tar.gz.sha256
    ourios-server-installer.sh
    ourios-server.cdx.xml
    sha256.sum
    ourios-server-aarch64-unknown-linux-gnu.tar.xz
      [bin] ourios-server
      [misc] CHANGELOG.md, LICENSE, README.md
      [checksum] ourios-server-aarch64-unknown-linux-gnu.tar.xz.sha256
    ourios-server-x86_64-unknown-linux-gnu.tar.xz
      [bin] ourios-server
      [misc] CHANGELOG.md, LICENSE, README.md
      [checksum] ourios-server-x86_64-unknown-linux-gnu.tar.xz.sha256
  • python3 -c "import yaml; yaml.safe_load(...)" -> YAML OK
  • actionlint .github/workflows/release.yml -> only SC2086/SC2129 info/style shellcheck notes inside cargo-dist's own generated run: blocks (left as generated; the repo has no actionlint CI gate). The one real expression error was the output.paths typo, now fixed.
  • cargo metadata --no-deps -> OK (workspace still resolves with the new [package.metadata.dist] + [profile.dist]).

Note

Builds were not run end-to-end locally (cross-compile not possible on this host); the real cross-build + attestation + SBOM steps execute on the GitHub Linux runners when a v* tag is pushed.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Enhanced release automation with support for multi-platform binary builds (Linux x86_64 and ARM64).
    • Added cryptographic build attestations for release asset verification and authenticity.
    • Enabled Software Bill of Materials (SBOM) generation for improved transparency and security.
    • Optimized build profiles for distribution artifacts.

Replace the changelog-only release workflow with a cargo-dist (v0.32.0)
pipeline. On a `v*` semver tag it cross-compiles the `ourios-server`
binary for Linux, generates per-artifact + aggregate sha256 checksums, a
CycloneDX SBOM, and GitHub build-provenance attestations, then publishes
the GitHub Release.

The signed release assets (attestations + SBOM) are what flips OpenSSF
Scorecard's Signed-Releases check.

- targets: x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu (gnu-only
  for v0). musl was tried in config but dropped: the cross-build could
  not be verified clean in this environment (macOS host, no Linux/musl
  toolchain), and shipping an unverified target into a tag-triggered
  pipeline is not worth it for v0. macOS/Windows are out of scope for a
  Linux server backend.
- installers: shell only (npm/msi/homebrew skipped for v0).
- cargo-cyclonedx = true (CycloneDX SBOM per release).
- github-attestations = true (build provenance via actions/attest).
- All crates are `version = 0.0.0` / `publish = false`; `dist = true` in
  ourios-server's `[package.metadata.dist]` opts the one binary back in
  as the sole distributable, leaving the library crates excluded.
- cargo-dist now owns the GitHub Release + its release notes (read from
  CHANGELOG.md), replacing the git-cliff step. cliff.toml is left in
  place but is no longer wired into the release pipeline.

Fixes the one upstream-template typo in the generated workflow
(`steps.cargo-cyclonedx.output.paths` -> `outputs.paths`) so the SBOM
files are actually collected into the upload glob; without it the SBOM
silently drops out. The cargo-dist-generated actions are left pinned as
dist generates them (by major tag, by design).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jensholdgaard
jensholdgaard requested a review from Copilot June 19, 2026 19:17
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Replaces the hand-written GitHub release workflow with an autogenerated cargo-dist pipeline. A new dist-workspace.toml configures targets, SBOM generation, and provenance attestations. Cargo.toml gains a dist build profile, ourios-server opts into dist artifacts, and the workflow is replaced with five jobs: plan, build-local-artifacts, build-global-artifacts, host, and announce.

Changes

cargo-dist Release Pipeline

Layer / File(s) Summary
cargo-dist workspace config and build profile
dist-workspace.toml, Cargo.toml, crates/ourios-server/Cargo.toml
dist-workspace.toml defines cargo-dist 0.32.0 with GitHub CI, shell installers, Linux GNU targets, CycloneDX SBOMs, and provenance attestations. Cargo.toml adds a dist profile inheriting release with thin LTO. ourios-server opts in via package.metadata.dist.dist = true.
Workflow triggers and plan job
.github/workflows/release.yml (lines 1–90)
Global contents: read permissions and updated triggers (pull_request + push tags v[0-9]*). New plan job installs cargo-dist, computes the dist task matrix, and exports manifest, tag, and publishing-enabled flags as job outputs.
build-local-artifacts job
.github/workflows/release.yml (lines 91–190)
Matrix-driven job that optionally uses a container, installs Rust on demand, builds per-target artifacts via dist build, runs actions/attest for provenance, and uploads artifacts and a per-target manifest.
build-global-artifacts job
.github/workflows/release.yml (lines 191–255)
Runs after local builds; executes dist build --artifacts=global, generates CycloneDX SBOMs via cargo cyclonedx, relocates SBOMs into target/distrib/, and uploads global artifacts with the global manifest.
host and announce jobs
.github/workflows/release.yml (lines 256–343)
host (gated, contents: write) runs dist host, creates the GitHub Release via gh release create with prerelease/title/notes from the manifest. announce follows with always() semantics after host.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 Hop hop, the pipeline grows!
From a single script to a cargo-dist flow,
Plans and matrices, artifacts in rows,
SBOMs and attestations—look at it go!
The rabbit now ships with a provenance glow. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adopting cargo-dist for signed binary releases, which is the core objective of the entire changeset.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering what was changed (cargo-dist pipeline), why (supply-chain hardening via signed attestations), configuration details, and validation steps. However, it does not fully follow the repository's template structure.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/cargo-dist-releases

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR switches the project’s release workflow from a simple changelog-driven GitHub Release to a cargo-dist–generated pipeline that builds and publishes signed release artifacts for ourios-server, including checksums, a CycloneDX SBOM, and GitHub provenance attestations.

Changes:

  • Add dist-workspace.toml configuring cargo-dist v0.32.0 for Linux GNU targets, SBOM generation, and GitHub attestations.
  • Opt ourios-server back into cargo-dist outputs via [package.metadata.dist] dist = true while keeping crates publish = false.
  • Replace .github/workflows/release.yml with the cargo-dist generated multi-job pipeline and add a dedicated [profile.dist] build profile.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 19 comments.

File Description
dist-workspace.toml Introduces cargo-dist workspace configuration (targets, SBOM, attestations).
crates/ourios-server/Cargo.toml Marks ourios-server as the sole dist-enabled distributable package.
Cargo.toml Adds a profile.dist build profile inheriting from release with thin LTO.
.github/workflows/release.yml Replaces prior release workflow with cargo-dist build/package/upload/release pipeline.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml Outdated
Hand-pin the floating GitHub Action references in the cargo-dist 0.32.0
generated release pipeline to immutable commit SHAs, matching the repo's
supply-chain hardening posture. Renovate will keep these pinned after any
future `dist generate`:

- actions/checkout@v6      -> df4cb1c069e1874edd31b4311f1884172cec0e10
- actions/upload-artifact@v7   -> 043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
- actions/download-artifact@v8 -> 3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
- actions/attest@v4        -> 59d89421af93a897026c735860bf21b6eb4f7b26

cargo-dist's own actions (axodotdev/*, the installer) are left as dist
pins them.

Also fix three real bugs in the generated workflow. These live in
cargo-dist-generated code and a future `dist generate` would revert them:

- on.push.tags used a regex-style pattern (`**[0-9]+.[0-9]+.[0-9]+*`),
  but GitHub `tags:` uses glob matching where `+` is literal, so it would
  not match `vX.Y.Z`. Use a glob that matches the project's v-prefixed
  semver tags.
- actions/attest subject-path was built from `join(matrix.targets, ', ')`,
  producing one comma+space glob that matches nothing. Compute a
  newline-separated list of per-target globs (which the attest action
  accepts) in a bash step and pass it through.
- The cyclonedx `find . -name '*.cdx.xml' -exec mv ... target/distrib/`
  also matched files already under target/distrib/, risking a self-move
  error. Prune that subtree with `-not -path './target/distrib/*'`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml
Per Copilot review on #262: cargo-dist generates workflow-level
contents: write, which would grant write to every job (incl. PR runs)
and drop the repo's Token-Permissions posture. Narrow the top level to
contents: read and grant contents: write only to the host job, which is
the one that runs gh release create. (Reverts on dist generate;
Renovate doesn't manage permissions, so this is a manual re-apply.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread .github/workflows/release.yml Outdated
Per Copilot review on #262: the v[0-9]*.[0-9]*.[0-9]* glob didn't
reliably match prerelease tags (v0.1.0-rc.1), which cargo-dist supports
and marks as GitHub prereleases. Use 'v[0-9]*' — any v-prefixed version
tag, prereleases included.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
The dist plan job failed (exit 255): cargo-dist's CI-consistency check
diffs the committed release.yml against what it would regenerate, and our
hand-pinned action SHAs (+ perms/tag/bug edits) make it differ. Set
allow-dirty = ["ci"] — cargo-dist's documented escape hatch for a
hand-customized CI file — so plan/generate no longer fail on the
deviations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Line 55: The tag-flag variable derived from github.ref_name is injected into
multiple run commands without proper quoting, creating a shell injection
vulnerability where malicious tags containing metacharacters like $(cmd) or ;cmd
could execute arbitrary commands. To fix this, ensure the tag-flag variable is
either wrapped in single quotes when used in all run commands where it appears,
or better yet, pass the tag value as a properly escaped environment variable
instead of inline command templating, and validate the tag format more strictly
to reject tags containing shell metacharacters beyond the current v[0-9]*
pattern.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6712c38d-970c-4072-88fe-f85ab341d3a8

📥 Commits

Reviewing files that changed from the base of the PR and between a2d1fb7 and 4354236.

📒 Files selected for processing (4)
  • .github/workflows/release.yml
  • Cargo.toml
  • crates/ourios-server/Cargo.toml
  • dist-workspace.toml

Comment thread .github/workflows/release.yml

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
@jensholdgaard
jensholdgaard merged commit 69b1207 into main Jun 19, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants