Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
name: Release

# A version tag (v0.1.0, v0.2.0-rc.1) on a commit in main publishes that
# version: attested binaries on the GitHub Release, then the crate on
# crates.io. Users verify a binary against this file's path, so renaming it
# breaks the verify command in the README.

on:
push:
tags: ["v*"]

permissions:
contents: read

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
check:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false

- name: Tag matches the crate version
run: |
version=$(cargo metadata --locked --no-deps --format-version 1 | jq -r '.packages[0].version')
if [ "$GITHUB_REF_NAME" != "v$version" ]; then
echo "::error::tag $GITHUB_REF_NAME does not match the crate version $version"
exit 1
fi

- name: Tagged commit is on main
# Catches a version tag pushed on the wrong commit by mistake.
run: |
git merge-base --is-ancestor "$GITHUB_SHA" origin/main \
|| { echo "::error::$GITHUB_SHA is not on main"; exit 1; }
Comment thread
27Bslash6 marked this conversation as resolved.

- name: Package the crate
# Builds the packaged crate, so the publish job can skip that build.
run: cargo package --locked

build:
name: build (${{ matrix.target }})
needs: check
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
matrix:
include:
# musl builds are static, so they run on any Linux distribution.
- { os: ubuntu-latest, target: x86_64-unknown-linux-musl }
- { os: ubuntu-24.04-arm, target: aarch64-unknown-linux-musl }
- { os: macos-latest, target: aarch64-apple-darwin }
env:
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Install the musl toolchain
if: runner.os == 'Linux'
# ring compiles C, which needs musl-gcc.
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends musl-tools

- name: Add the target
run: rustup target add "$TARGET"

- name: Test the shipped target
run: cargo test --locked --release --target "$TARGET"

- name: Build
run: cargo build --locked --release --target "$TARGET"

- name: Archive
run: |
dir="ck-$GITHUB_REF_NAME-$TARGET"
mkdir "$dir"
cp "target/$TARGET/release/ck" LICENSE README.md "$dir/"
tar -czf "$dir.tar.gz" "$dir"

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ matrix.target }}
path: ck-*.tar.gz
if-no-files-found: error

release:
needs: build
runs-on: ubuntu-latest
timeout-minutes: 15
# Signing happens here rather than in the build jobs, so dependency build
# scripts never run with a token that can sign or publish.
permissions:
contents: write # create the release
id-token: write # sign the attestation
attestations: write # store the attestation
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: dist
merge-multiple: true

- name: Unpack the binaries
# Attesting the binary too lets a user verify ck after unpacking it.
run: for archive in dist/*.tar.gz; do tar -xzf "$archive" -C dist; done

- name: Attest the archives and the binaries in them
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-path: |
dist/*.tar.gz
dist/ck-*/ck

- name: Create the GitHub Release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
# A rerun, after a failed upload or publish, finds the release
# already there: replace its archives with this run's attested ones
# so the publish job still runs. gh creates a release with assets as
# a draft and publishes it after the uploads, so a run that died
# mid-upload can leave a draft behind; publish it here.
if gh release view "$GITHUB_REF_NAME" > /dev/null 2>&1; then
gh release upload "$GITHUB_REF_NAME" --clobber dist/*.tar.gz
gh release edit "$GITHUB_REF_NAME" --draft=false
exit
fi
Comment thread
27Bslash6 marked this conversation as resolved.
prerelease=()
case "$GITHUB_REF_NAME" in *-*) prerelease=(--prerelease) ;; esac
gh release create "$GITHUB_REF_NAME" --verify-tag --generate-notes "${prerelease[@]}" \
dist/*.tar.gz

publish:
needs: release
runs-on: ubuntu-latest
timeout-minutes: 15
# crates.io accepts a token minted here only from this workflow in this
# environment: trusted publishing, so the repo holds no registry secret.
environment: release
permissions:
contents: read
id-token: write # exchange for a crates.io token
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

# Needs the crate to exist on crates.io with this workflow and the
# `release` environment registered as its trusted publisher. crates.io
# attaches a publisher only to an existing crate, so 0.0.0 is published
# by hand with an API token first. Every tagged version comes after it,
# because this job cannot publish a version that already exists.
- uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5
id: auth

- name: Publish to crates.io
# The check job already built the packaged crate. A version can never
# be republished, so this runs last, after the GitHub Release exists.
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: cargo publish --locked --no-verify
Comment thread
27Bslash6 marked this conversation as resolved.
40 changes: 38 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,44 @@

`ck`, the command-line client for [CacheKit](https://cachekit.io).

Pre-release: nothing is published yet. To try it, build from a checkout with
`cargo build --release` and put `target/release/ck` on your `PATH`.
## Install

There is no install script. Install with Cargo, or download a release binary
and verify where it came from.

### With Cargo

```text
cargo install --locked cachekit-cli
```

This builds `ck` from the crate on crates.io and puts it in `~/.cargo/bin`.
Each crates.io release is published by this repository's release workflow.

### Release binaries

Each [release](https://github.com/cachekit-io/cachekit-cli/releases) carries
static Linux binaries for `x86_64` and `aarch64`, and a macOS binary for Apple
silicon. On other platforms, install with Cargo. Download the archive for your
platform, then verify it with the [GitHub CLI](https://cli.github.com/) before
you unpack it:

```text
gh attestation verify ck-v0.1.0-x86_64-unknown-linux-musl.tar.gz --repo cachekit-io/cachekit-cli \
--signer-workflow cachekit-io/cachekit-cli/.github/workflows/release.yml \
--deny-self-hosted-runners
```

The check passes only if the file was built by this repository's release
workflow on a GitHub-hosted runner. `--owner cachekit-io` alone is not enough:
it accepts a file built by any workflow in any `cachekit-io` repository. The
`ck` binary inside the archive is attested too, so the same command verifies it
after unpacking.

### From a checkout

Build with `cargo build --release --locked` and put `target/release/ck` on
your `PATH`.

## `ck run`

Expand Down
Loading