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
45 changes: 14 additions & 31 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,14 @@ jobs:
steps:
- name: 'Check out code'
uses: actions/checkout@v4
- name: 'Make release'
id: 'make-release'
# Drafting logic lives in scripts/deploy.sh so CI and manual releases stay
# in sync (single source of truth). See CONTRIBUTING.md.
- name: 'Draft the release'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -x
VERSION=v$(cat package/version)
gh release create "${VERSION}" \
--repo "${GITHUB_REPOSITORY}" \
--draft \
--title "${VERSION}" \
--target ${{ github.sha }}
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
OWNER_REPO: '${{ github.repository }}'
REV: '${{ github.sha }}'
run: ./scripts/deploy.sh release-draft

nix-cache:
name: 'Populate Nix Caches'
Expand Down Expand Up @@ -55,13 +50,8 @@ jobs:
if: failure()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -x
VERSION=v$(cat package/version)
gh release delete "${VERSION}" \
--repo "${GITHUB_REPOSITORY}" \
--yes \
--cleanup-tag || true
OWNER_REPO: '${{ github.repository }}'
run: ./scripts/deploy.sh release-abort

dockerhub:
name: 'Build and Publish Docker Image'
Expand All @@ -85,13 +75,8 @@ jobs:
if: failure()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -x
VERSION=v$(cat package/version)
gh release delete "${VERSION}" \
--repo "${GITHUB_REPOSITORY}" \
--yes \
--cleanup-tag || true
OWNER_REPO: '${{ github.repository }}'
run: ./scripts/deploy.sh release-abort

cut-release:
name: 'Cut Release'
Expand All @@ -104,12 +89,10 @@ jobs:
ref: ${{ github.sha }}
fetch-depth: 0

# Finalizing logic lives in scripts/deploy.sh so CI and manual releases
# stay in sync (single source of truth). See CONTRIBUTING.md.
- name: 'Finalize Release'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -x
VERSION=v$(cat package/version)
gh release edit "${VERSION}" \
--repo "${GITHUB_REPOSITORY}" \
--draft=false
OWNER_REPO: '${{ github.repository }}'
run: ./scripts/deploy.sh release-cut
29 changes: 17 additions & 12 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,14 @@ where `<version>` comes from `package/version`.

### Prerequisites

Both subcommands run from a checkout of the revision you are releasing, with the
The subcommands run from a checkout of the revision you are releasing, with the
following tools on `PATH`:

- `nix-cache` needs `nix` and `cachix`. It fetches `kup` from
`github:runtimeverification/kup` itself, so you do not install kup separately.
- `docker` needs `docker`.
- `release-draft`, `release-cut`, and `release-abort` need `gh`, authenticated
via `gh auth login` or a `GH_TOKEN`/`GITHUB_TOKEN` in the environment.

Each subcommand checks its tools up front and exits with a clear message if one
is missing, before starting the build.
Expand All @@ -102,14 +104,16 @@ further setup.
| ----------- | ------------------------------ |
| `nix-cache` | `CACHIX_PUBLIC_TOKEN`, `CACHIX_PRIVATE_KFB_TOKEN` |
| `docker` | `DOCKERHUB_PASSWORD` |
| `release-*` | none — `gh` supplies its own authentication |

`OWNER_REPO` and `REV` default to the current checkout's `origin` remote and
`HEAD`. Override them if you are publishing a revision other than the one checked
out. `DOCKERHUB_USERNAME`, `DOCKERHUB_NAMESPACE`, and `DOCKERHUB_REPO` default to
the release account and repository; override them only to publish elsewhere.

Both subcommands refuse to run if the working tree is not clean. CI builds from a
fresh checkout, so a manual build must too: uncommitted changes and untracked
The `nix-cache` and `docker` subcommands refuse to run if the working tree is not
clean. CI builds from a fresh checkout, so a manual build must too: uncommitted
changes and untracked
files would otherwise be baked into the published image or flake build without
matching the `REV` they are published under. Commit, stash, or clean the tree
first. `git status --porcelain` ignores `.gitignore`d paths, so runtime artifacts
Expand All @@ -118,26 +122,27 @@ deliberately want to publish an uncommitted state.

### Manual release while CI is down

The workflow also drafts, cleans up, and finalizes the GitHub release around the
deployment jobs. When you deploy by hand, run those `gh` steps yourself in this
order. `nix-cache` runs once per architecture (the workflow uses an `x86_64` and
an `ARM64` runner), so run it on a machine of each architecture that should be
`deploy.sh` wraps the GitHub-release steps too, so a manual release runs the same
subcommands the workflow does, in the same order. Run them from a checkout of the
`main` commit you are releasing: `release-draft` targets `REV` (the current
`HEAD` by default), and kup resolves komet-node versions against `main`, so a
release built from an unmerged commit will not be installable with `kup`.
`nix-cache` runs once per architecture (the workflow uses an `x86_64` and an
`ARM64` runner), so run it on a machine of each architecture that should be
cached.

```sh
VERSION=v$(cat package/version)

# 1. Draft the release.
gh release create "$VERSION" --draft --title "$VERSION" --target "$(git rev-parse HEAD)"
./scripts/deploy.sh release-draft

# 2. Deploy. Export the secrets first (see the table above).
# Run nix-cache once per architecture; docker once.
./scripts/deploy.sh nix-cache
./scripts/deploy.sh docker

# 3. Finalize the release once every deployment has succeeded.
gh release edit "$VERSION" --draft=false
./scripts/deploy.sh release-cut

# If a deployment fails, remove the draft instead:
# gh release delete "$VERSION" --yes --cleanup-tag
# ./scripts/deploy.sh release-abort
```
57 changes: 48 additions & 9 deletions scripts/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ set -euo pipefail
# unavailable. See CONTRIBUTING.md ("Deploying a release") for the runbook.
#
# Subcommands:
# nix-cache Build komet-node and push it to both Nix caches (the
# k-framework closure + the k-framework-binary kup binary).
# docker Build the Docker image, smoke-test it, and push to Docker Hub.
# all Run nix-cache then docker.
# nix-cache Build komet-node and push it to both Nix caches (the
# k-framework closure + the k-framework-binary kup binary).
# docker Build the Docker image, smoke-test it, and push to Docker Hub.
# all Run nix-cache then docker.
# release-draft Create the draft GitHub release for this version and revision.
# release-cut Publish (un-draft) the release once its artifacts are up.
# release-abort Delete the draft release and its tag (failure cleanup).
#
# Usage:
# scripts/deploy.sh <nix-cache|docker|all>
# scripts/deploy.sh <nix-cache|docker|all|release-draft|release-cut|release-abort>

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "${REPO_ROOT}"
Expand Down Expand Up @@ -156,15 +159,51 @@ deploy_docker() {
docker image push "${tag}"
}

# --- release lifecycle -----------------------------------------------------
# The GitHub release is drafted before the artifacts are published and finalized
# after, so it only becomes visible once its Docker image and cache pins exist.
# These wrap the `gh` calls the Release workflow makes, keeping the release
# lifecycle in the same single source of truth as the deploy steps. `gh`
# authenticates via its own login or GH_TOKEN/GITHUB_TOKEN; no secret is read
# here directly. The release is drafted against REV, which must be the commit you
# deployed -- and, for a real release, a commit on `main`, since kup resolves
# komet-node versions against `main`.
release_tag() { echo "v${KOMET_NODE_VERSION}"; }

deploy_release_draft() {
require_cmd gh git
local tag; tag="$(release_tag)"
echo ":: drafting release ${tag} targeting ${REV}"
gh release create "${tag}" --repo "${OWNER_REPO}" --draft --title "${tag}" --target "${REV}"
}

deploy_release_cut() {
require_cmd gh git
local tag; tag="$(release_tag)"
echo ":: publishing release ${tag}"
gh release edit "${tag}" --repo "${OWNER_REPO}" --draft=false
}

deploy_release_abort() {
require_cmd gh git
local tag; tag="$(release_tag)"
echo ":: deleting drafted release ${tag}"
# Best-effort: the cleanup path must never fail on its own account.
gh release delete "${tag}" --repo "${OWNER_REPO}" --yes --cleanup-tag || true
}

usage() {
sed -n '8,22p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
sed -n '8,25p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
}

main() {
case "${1:-}" in
nix-cache) deploy_nix_cache ;;
docker) deploy_docker ;;
all) deploy_nix_cache; deploy_docker ;;
nix-cache) deploy_nix_cache ;;
docker) deploy_docker ;;
all) deploy_nix_cache; deploy_docker ;;
release-draft) deploy_release_draft ;;
release-cut) deploy_release_cut ;;
release-abort) deploy_release_abort ;;
-h | --help | help | "")
usage
[ -n "${1:-}" ] # exit non-zero when no subcommand was given
Expand Down
Loading