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
65 changes: 65 additions & 0 deletions .github/workflows/homebrew-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Publish Homebrew formula

# Pushes the riskkernel formula to the Homebrew tap on each version tag, so
# `brew install riskkernel` works (and stays current with releases).
#
# INERT until set up — needs two things (see docs/HOMEBREW.md):
# 1. a tap repo: prashar32/homebrew-riskkernel
# 2. a repo secret HOMEBREW_TAP_TOKEN: a fine-grained PAT with contents:write
# on that tap repo.
# Without the secret this workflow logs a notice and exits 0 — it never blocks a
# release (the binaries + GitHub release are cut by release.yml regardless).
on:
push:
tags: ["v*"]
workflow_dispatch:

permissions:
contents: read

jobs:
publish:
name: Publish formula to the Homebrew tap
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Render and push the formula
env:
TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
GH_TOKEN: ${{ github.token }}
TAP_REPO: prashar32/homebrew-riskkernel
run: |
set -euo pipefail
if [ -z "${TAP_TOKEN}" ]; then
echo "::notice::HOMEBREW_TAP_TOKEN not set — skipping Homebrew publish. See docs/HOMEBREW.md to enable."
exit 0
fi

# The version to publish: the tag that triggered this, or the latest
# release when run manually from a branch.
if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
version="${GITHUB_REF_NAME#v}"
else
version="$(gh release view --repo "${GITHUB_REPOSITORY}" --json tagName -q .tagName | sed 's/^v//')"
fi
echo "Publishing Homebrew formula for v${version}"

# Pull the release's checksums and render the formula from them.
gh release download "v${version}" --repo "${GITHUB_REPOSITORY}" --pattern checksums.txt --dir /tmp/rk-rel --clobber
formula="$(bash scripts/gen-homebrew-formula.sh "${version}" /tmp/rk-rel/checksums.txt)"

# Commit it to the tap.
git clone --depth 1 "https://x-access-token:${TAP_TOKEN}@github.com/${TAP_REPO}.git" /tmp/tap
mkdir -p /tmp/tap/Formula
printf '%s\n' "${formula}" > /tmp/tap/Formula/riskkernel.rb
cd /tmp/tap
git config user.name "Adarsh Prashar"
git config user.email "adarsh.prashar32@gmail.com"
git add Formula/riskkernel.rb
if git diff --cached --quiet; then
echo "Formula already up to date for v${version} — nothing to push."
exit 0
fi
git commit -m "riskkernel ${version}"
git push
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ surface is governed by [`COMPATIBILITY.md`](COMPATIBILITY.md).
## [Unreleased]

### Added
- **Homebrew formula publishing.** A release workflow + formula generator publish the
`riskkernel` formula to a Homebrew tap on each version tag, so `brew install
riskkernel` installs a prebuilt binary and stays current with releases — the same
per-artifact publish pattern as the Python and TypeScript SDKs. It's inert until the
tap repo (`homebrew-riskkernel`) and a `HOMEBREW_TAP_TOKEN` secret are configured
(one-time maintainer step) and never blocks a release without them. See
[`docs/HOMEBREW.md`](docs/HOMEBREW.md).
- **Native AWS Bedrock provider.** Run Bedrock-hosted models through RiskKernel with
full budgets / approvals / audit / OTel — set the standard AWS env vars
(`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, optional `AWS_SESSION_TOKEN`,
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ riskkernel init # scaffold a .env + a runnable example in the current dir
riskkernel serve # start the daemon (reads .env)
```

(or `make build` from a clone). Tab-complete the CLI in your shell:
(or `make build` from a clone, or Homebrew — see [`docs/HOMEBREW.md`](docs/HOMEBREW.md)).
Tab-complete the CLI in your shell:

```bash
riskkernel completion bash > /etc/bash_completion.d/riskkernel # bash
Expand Down
48 changes: 48 additions & 0 deletions docs/HOMEBREW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Homebrew

Install RiskKernel as a prebuilt binary on macOS or Linux via Homebrew:

```bash
brew install prashar32/riskkernel/riskkernel
# or:
brew tap prashar32/riskkernel
brew install riskkernel
```

This pulls the signed release binary for your OS/arch (no compile). Upgrade with
`brew upgrade riskkernel`.

> **Status:** the publishing automation is in the repo, but `brew install` works only
> once the tap repo and token are set up (one-time maintainer step, below). Until
> then, install via `go install`, `docker run`, or the release binaries — see the
> [README quickstart](../README.md#quickstart-60-seconds).

## How it works

On every version tag, the [`Publish Homebrew formula`](../.github/workflows/homebrew-publish.yml)
workflow downloads the release's `checksums.txt`, renders the formula with
[`scripts/gen-homebrew-formula.sh`](../scripts/gen-homebrew-formula.sh) (a binary
formula with a per-OS/arch `url` + `sha256` taken from the GoReleaser archives), and
commits it to the tap repo as `Formula/riskkernel.rb`. It mirrors how the Python and
TypeScript SDKs publish on a tag.

## Maintainer setup (one-time, to activate)

The workflow is **inert until two things exist** — without them it logs a notice and
exits 0, so it never blocks a release:

1. **Create the tap repo.** A *public* repo named **`homebrew-riskkernel`** under the
same owner (`prashar32`). Homebrew requires the `homebrew-` name prefix; it maps to
the tap `prashar32/riskkernel`. It can start empty — the workflow writes
`Formula/riskkernel.rb`.
2. **Add the token secret.** On the `prashar32/riskkernel` repo, add an Actions secret
**`HOMEBREW_TAP_TOKEN`**: a fine-grained personal access token scoped to the
`homebrew-riskkernel` repo with **Contents: read and write**.

Then publish the current release: re-run the **Publish Homebrew formula** workflow
(Actions → *Run workflow*), or just cut the next release — it runs automatically on
each `v*` tag.

> On the eventual org transfer (`prashar32` → a `riskkernel` org), move the tap repo
> and re-point the `TAP_REPO` in the workflow, the same way the PyPI/npm trusted
> publishers move.
66 changes: 66 additions & 0 deletions scripts/gen-homebrew-formula.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Generates the Homebrew formula for riskkernel from a release's checksums.txt.
# Kept as a standalone script so it can be run/verified locally, not just in CI.
#
# Usage: scripts/gen-homebrew-formula.sh <version-without-v> <path/to/checksums.txt>
# Prints the formula (Formula/riskkernel.rb) to stdout.
set -euo pipefail

version="${1:?usage: gen-homebrew-formula.sh <version> <checksums.txt>}"
checksums="${2:?usage: gen-homebrew-formula.sh <version> <checksums.txt>}"
base="https://github.com/prashar32/riskkernel/releases/download/v${version}"

# sha256 of a named release asset, from goreleaser's "<sha> <file>" checksums.txt.
sha() {
local got
got="$(awk -v f="$1" '$2 == f {print $1}' "$checksums")"
if [ -z "$got" ]; then
echo "gen-homebrew-formula: no checksum for $1 in $checksums" >&2
exit 1
fi
printf '%s' "$got"
}

darwin_arm="$(sha "riskkernel_${version}_darwin_arm64.tar.gz")"
darwin_amd="$(sha "riskkernel_${version}_darwin_amd64.tar.gz")"
linux_arm="$(sha "riskkernel_${version}_linux_arm64.tar.gz")"
linux_amd="$(sha "riskkernel_${version}_linux_amd64.tar.gz")"

cat <<EOF
class Riskkernel < Formula
desc "Self-hosted reliability runtime for AI agents (budgets, approvals, crash-resume)"
homepage "https://github.com/prashar32/riskkernel"
version "${version}"
license "Apache-2.0"

on_macos do
on_arm do
url "${base}/riskkernel_${version}_darwin_arm64.tar.gz"
sha256 "${darwin_arm}"
end
on_intel do
url "${base}/riskkernel_${version}_darwin_amd64.tar.gz"
sha256 "${darwin_amd}"
end
end

on_linux do
on_arm do
url "${base}/riskkernel_${version}_linux_arm64.tar.gz"
sha256 "${linux_arm}"
end
on_intel do
url "${base}/riskkernel_${version}_linux_amd64.tar.gz"
sha256 "${linux_amd}"
end
end

def install
bin.install "riskkernel"
end

test do
assert_match "riskkernel", shell_output("#{bin}/riskkernel version")
end
end
EOF