Releases and CI
Status: built (2026-10-15); the workflows have not yet run on GitHub.
1. Goals
- One command cuts a release:
make release BUMP=minor. GitHub Actions does the rest: tests, every package, smoke tests, the GitHub release and the Homebrew tap. - CI runs what you run. Each workflow step is a make target or a script under
packaging/, so any CI failure can be reproduced with the same command on your machine. - Every change is tested on Linux and macOS, on every push to
mainand every pull request. - Nothing half-done. A release that fails before its tag leaves nothing behind. One that fails in CI publishes nothing, and the fix is a new patch release.
Not goals (for now): an apt repository (so apt upgrade would find new versions), signed packages, Docker images on a registry, Windows.
2. Workflows
| Workflow | Runs on | Does |
|---|---|---|
Test (.github/workflows/test.yml) | push to main, pull requests, and from Release | cargo test --locked on Ubuntu 24.04 and macOS 15; the web UI compiles for wasm32 |
Release (.github/workflows/release.yml) | a v* tag; or by hand, as a dry run | §4 |
Toolchain: the Rust and Trunk versions come from docker/Dockerfile (RUST_VERSION, TRUNK_VERSION), through .github/actions/rust. The Docker builds use the same versions, so local and CI builds match.
3. Versions
-
Semver.
make release BUMP=patch|minor|major(defaultpatch), orVERSION=x.y.z. The first release is0.2.0(make release BUMP=minor):0.1.0has been the unreleased development version. -
One version for the workspace. Every crate has
version.workspace = true, so a release sets[workspace.package] versionalone.c6_core::VERSION, which workers send to the head and which database upgrades record, is that version. -
Tags: annotated,
v0.2.0. The Release workflow refuses a tag that doesn't match the workspace version. -
Package versions:
- at a release tag:
0.2.0(deb and brew alike); - between releases:
0.2.0+git<commit time>.<commit>(deb) and0.2.0.<commit time>(brew).
Both sort after
0.2.0and before0.2.1, so upgrades go the right way. The Makefile picks the right one by itself:RELEASE_TAGis set whenHEADcarriesv<crate version>. - at a release tag:
4. Cutting a release
make release [BUMP=… | VERSION=…] runs packaging/release/release.sh on your machine:
-
Checks:
- on
main, with a clean tree, atorigin/main; - the version is newer than the current one;
- its tag doesn't exist.
- on
-
The release commit:
- sets the version, and refreshes
Cargo.lockoffline; - puts a new section at the top of
CHANGELOG.md(§5); - commits
Release 0.2.0and tagsv0.2.0.
A failure along the way puts everything back.
- sets the version, and refreshes
-
Push: asks first (
YES=1skips the question), thengit push --atomic origin main v0.2.0. Not pushed, the commit and tag stay local;git tag -d v0.2.0 && git reset --hard HEAD~1undoes them.
The tag starts the Release workflow:
| Job | Runner | Does |
|---|---|---|
| check | ubuntu-24.04 | the tag matches the workspace version |
| test | Test workflow | Linux and macOS suites |
| linux | ubuntu-24.04, ubuntu-24.04-arm | make docker-deb-test: the .debs and carbon6-<v>-linux-<arch>.tar.gz, built on Ubuntu 22.04 natively per architecture, then installed under systemd, run, upgraded and removed |
| macos | macos-15 (Apple Silicon), macos-15-intel | make brew-test: bottles and binary tarballs, then installed from a scratch tap, the head started with brew services, a worker and the memex attached, stopped with SIGTERM |
| publish | ubuntu-24.04, after all the others | packaging/release/publish.sh (§6) |
Dry run: started by hand (Actions → Release → Run workflow, or gh workflow run release.yml), the workflow builds and tests everything and keeps the artifacts on the run. publish is skipped. Do this once before the first release.
5. Changelog
CHANGELOG.md, newest first. Each release adds a section:## 0.2.0 (2026-10-15), then one bullet per commit since the previous tag, its one-line message as written.- Release commits are left out of the list.
- The first release lists every commit since the start of the repository.
- The same section is the GitHub release's notes.
- Preview:
make release-notesprints the next section without changing anything.
6. Publishing
publish.sh <version> <dir> runs in the Release workflow, on the artifacts of the other jobs:
- The formulas, written by
packaging/brew/formulas.sh:- one per binary, covering both architectures (
on_arm/on_intel); - with a bottle for each;
- downloading from
https://github.com/carbon6-dev/carbon6/releases/download/v<version>/.
- one per binary, covering both architectures (
SHA256SUMSof every file, and the notes: the version's section ofCHANGELOG.md.- The GitHub release
v<version>, with the files below. If the release already exists (a re-run), missing files are uploaded. - The tap: the formulas are committed to
carbon6-dev/homebrew-carbon6, sobrew install carbon6-dev/carbon6/c6hworks andbrew upgradefinds new releases.- This needs the repository secret
TAP_TOKEN: a fine-grained token with Contents: read and write on the tap repository only. - Without the secret, the formulas are only attached to the release, and the run says so.
- This needs the repository secret
The files on a release:
| File | What it is |
|---|---|
c6h_<v>_amd64.deb, _arm64.deb (and c6w, c6m, c6) | Ubuntu packages with systemd units, for 22.04 and later |
carbon6-<v>-linux-amd64.tar.gz, -arm64 | the four binaries, for Linux without packages |
c6h-<v>.arm64_big_sur.bottle.tar.gz, .big_sur. (and the others) | Homebrew bottles, Apple Silicon and Intel, for macOS 11 and later |
c6h-<v>-darwin-arm64.tar.gz, -x86_64 (and the others) | each binary for macOS without Homebrew; also the formulas' source |
c6h.rb and the other formulas | the formulas, as pushed to the tap |
SHA256SUMS | checksums of all of the above |
7. Make targets
| Target | Does |
|---|---|
make release [BUMP=… | VERSION=…] | §4: checks, release commit, tag, push |
make release-notes | prints the next changelog section |
make docker-deb-test [ARCH=…] | the Linux job: builds and tries the .debs under systemd |
make brew-test | the macOS job: builds and tries the bottles. It installs into this Mac's Homebrew, so it refuses where c6h etc. are installed or :7700 is in use, and removes all it installed |
make brew-dist | bottles and formulas (with file:// URLs) into dist/brew/, without a tap |
8. Setting it up (once)
- Dry run: run the Release workflow by hand on
main(§4) and check that every job passes. - Tap repository: create a public, empty
carbon6-dev/homebrew-carbon6. - Token: make a fine-grained token for it with Contents: read and write, and add it to
carbon6-dev/carbon6as the Actions secretTAP_TOKEN. - Release:
make release BUMP=minorcuts0.2.0.
9. Open questions
- Upgrade check across releases. A release could keep a small
c6h.dbandmemex.db(packaging/release/fixtures/<version>/), and the Release workflow could runc6h upgrade --checkandc6m upgrade --checkfrom the previous release's copies. That catches a migration that breaks upgrades before it ships. - The commit in
--versionbetween releases. Today a build after0.2.0reports0.2.0. A build script could add+git<commit>. - An apt repository. A signed one, on GitHub Pages, would let
apt upgradepick up releases. It needs a signing key in the repository's secrets. - Docker images.
carbon6/c6handcarbon6/c6wcould be pushed toghcr.ioby the Release workflow.