From 1505d4eaf98184eba33ec451a2318faaf7d35f31 Mon Sep 17 00:00:00 2001 From: Riccardo Tacconi Date: Wed, 29 Apr 2026 16:52:19 +0100 Subject: [PATCH 1/4] docs(release): Make+Nix release flow with gh publish Add scripts/release.sh (build ISO and kcore-kctl, dist tarball/checksums, gh release). Add Makefile release-* targets; fix ISO discovery for Nix output filename. Document process in docs/release.md; template RELEASE_NOTES; link from README. --- .gitignore | 4 ++ Makefile | 26 +++++++++-- README.md | 2 + RELEASE_NOTES.template.md | 14 ++++++ docs/README.md | 1 + docs/file-structure.md | 2 + docs/release.md | 84 ++++++++++++++++++++++++++++++++++ scripts/release.sh | 94 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 224 insertions(+), 3 deletions(-) create mode 100644 RELEASE_NOTES.template.md create mode 100644 docs/release.md create mode 100755 scripts/release.sh diff --git a/.gitignore b/.gitignore index 790fccf..6f85a0e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,8 @@ /target /result /result-* +/result-kctl +/dist +# Local release notes (copy from RELEASE_NOTES.template.md before make release-publish) +/RELEASE_NOTES.md *.iso diff --git a/Makefile b/Makefile index 7e12925..6200cbb 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all build check fmt clippy audit lint-nix test test-all test-rust test-nix test-vm test-tla test-tla-trace test-replication-soak coverage test-controller test-node-agent test-kctl test-rust-filter loc iso iso-remote kctl clean install-hooks kani help +.PHONY: all build check fmt clippy audit lint-nix test test-all test-rust test-nix test-vm test-tla test-tla-trace test-replication-soak coverage test-controller test-node-agent test-kctl test-rust-filter loc iso iso-remote kctl clean install-hooks kani help release-build release-dist release-publish release VERSION := $(shell cat VERSION) V ?= v$(VERSION) @@ -92,7 +92,7 @@ iso: @echo "" @ls -lh result-iso/iso/*.iso @echo "" - @echo "ISO built: result-iso/iso/kcoreos-$(VERSION)-x86_64-linux.iso" + @echo "ISO built under result-iso/iso/ (release-dist copies it to kcoreos-$(VERSION)-x86_64-linux.iso)" iso-remote: @echo "Building kcore ISO $(V) on remote Linux server..." @@ -101,6 +101,22 @@ iso-remote: kctl: cargo build --release -p kcore-kctl +# Release (operator machine: Linux x86_64, Nix + flakes, gh CLI authenticated). +# Flow: bump VERSION → merge PR → tag v$(VERSION) → push tag → edit RELEASE_NOTES.md → make release-build release-dist → make release-publish +release-build: + bash ./scripts/release.sh build + +release-dist: + bash ./scripts/release.sh dist + +release-publish: + bash ./scripts/release.sh publish + +release: release-build release-dist + @echo "" + @echo "Artifacts are under dist/. Next: ensure git tag v$(VERSION) exists on origin," + @echo "fill RELEASE_NOTES.md (from RELEASE_NOTES.template.md), then: make release-publish" + install-hooks: @for hook in scripts/hooks/*; do \ name=$$(basename "$$hook"); \ @@ -110,7 +126,7 @@ install-hooks: clean: cargo clean - rm -rf result result-iso dist + rm -rf result result-iso result-kctl dist help: @echo "kcore $(V)" @@ -140,6 +156,10 @@ help: @echo " iso Build NixOS ISO (Linux only)" @echo " iso-remote Build NixOS ISO on remote Linux server (from macOS)" @echo " kctl Build kctl CLI only" + @echo " release-build Nix-build ISO + kcore-kctl (result-iso, result-kctl)" + @echo " release-dist Tarball + ISO under dist/ + SHA256SUMS" + @echo " release-publish Create GitHub Release from tag (needs gh, RELEASE_NOTES.md)" + @echo " release release-build + release-dist (then publish manually)" @echo " install-hooks Install git pre-commit/pre-push hooks" @echo " clean Remove build artifacts" @echo " help Show this help" diff --git a/README.md b/README.md index 0573c81..6d4ba0f 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,8 @@ cargo fmt --check cargo audit ``` +**Releases** (version bump, Nix build, GitHub Release assets): [docs/release.md](docs/release.md). + ## Known Gaps / Next Priorities 1. Add certificate lifecycle management (rotation, expiry handling, revocation strategy). diff --git a/RELEASE_NOTES.template.md b/RELEASE_NOTES.template.md new file mode 100644 index 0000000..bb95c28 --- /dev/null +++ b/RELEASE_NOTES.template.md @@ -0,0 +1,14 @@ +# kcore X.Y.Z + +## Highlights + +- + +## Upgrade / install + +- **ISO**: flash `kcoreos-X.Y.Z-x86_64-linux.iso` (verify `SHA256SUMS`). +- **kctl**: extract `kcore-kctl-X.Y.Z-linux-x86_64.tar.gz` and run `./kcore-kctl --help` (glibc Linux x86_64, built via this repo’s Nix flake). + +## Checksums + +See `SHA256SUMS` attached to the GitHub Release. diff --git a/docs/README.md b/docs/README.md index 7072529..2c0de41 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,6 +12,7 @@ | Day-2 filesystem operations | [day-2-filesystem-operations.md](./day-2-filesystem-operations.md) | | CRDT / HA replication | [ha-crdt-replication.md](./ha-crdt-replication.md) | | File layout | [file-structure.md](./file-structure.md) | +| Releases (Make + Nix + `gh`) | [release.md](./release.md) | | Formal methods | [formal-methods-and-verification.md](./formal-methods-and-verification.md) | | Heartbeat | [heartbeat.md](./heartbeat.md) | | kctl workflows | [kctl-commands-and-workflows.md](./kctl-commands-and-workflows.md) | diff --git a/docs/file-structure.md b/docs/file-structure.md index 579a565..bc23aaa 100644 --- a/docs/file-structure.md +++ b/docs/file-structure.md @@ -250,6 +250,7 @@ For each file: purpose + where it is used in runtime/operator flows. - `specs/tla/CrossDcReplication.tla` — multi-DC replication convergence model with link failures. - `specs/tla/CrossDcReplication.cfg` — TLC constants/properties for cross-DC replication model. - `scripts/build-iso-remote.sh` — remote build automation script; orchestrates ISO build steps on a remote Linux builder host. +- `scripts/release.sh` — non-interactive release helper: Nix-build ISO and `kcore-kctl`, package `dist/` + `SHA256SUMS`, `gh release create` (see `docs/release.md`). ### Documentation @@ -268,3 +269,4 @@ For each file: purpose + where it is used in runtime/operator flows. - `docs/mtls-bootstrap-and-auth.md` — certificate lifecycle guide; bootstrapping, cert roles, and auth usage in runtime calls. - `docs/formal-methods-and-verification.md` — verification strategy notes; current checks and future formalization directions. - `docs/file-structure.md` — repository map and deep catalog; explains file responsibilities and runtime/operator relevance. +- `docs/release.md` — release checklist: bump `VERSION`, tag, Nix build, GitHub Release assets via Make/`gh`. diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 0000000..381610e --- /dev/null +++ b/docs/release.md @@ -0,0 +1,84 @@ +# Release process (Make + Nix + GitHub Releases) + +Releases are **operator-driven**: build artifacts locally with Nix, publish to GitHub with `gh`. There is no release workflow in GitHub Actions; CI on `main` remains the quality gate only. + +## Version sources (policy) + +| Source | Role | +|--------|------| +| [`VERSION`](../VERSION) (single line, e.g. `0.2.0`) | **Product / packaging version**: Nix `kcoreVersion`, ISO filename `kcoreos-$(VERSION)-x86_64-linux.iso`, Git tag `v$(VERSION)`, release assets. **Bump this for every release.** | +| `crates/*/Cargo.toml` `version = "…"` | Rust crate semver (currently `0.1.0` for workspace members). **Not automatically tied to `VERSION`.** Optional: bump crate versions in the same PR as `VERSION` if you want `kcore-kctl --version` to match the product version everywhere. | + +## Preconditions + +- **Host**: Linux **x86_64** with Nix and flakes working (`nix build .#kcore-kctl`). +- **GitHub CLI**: `gh` installed and authenticated (`gh auth login`) with `contents: write` on the repo. Optional: set `GH_REPO=owner/repo` if not using the default remote. +- **Tag**: The annotated tag `v$(cat VERSION)` must exist **on the remote** before `make release-publish` (the script uses `gh release create --verify-tag`). + +## Steps + +1. **Bump version** + Edit [`VERSION`](../VERSION) to `X.Y.Z` (and optionally align `crates/kctl/Cargo.toml` and other crates if you follow the policy above). Open a PR, get CI green, merge to `main`. + +2. **Tag the release commit** (on `main` after merge): + + ```bash + git fetch origin main && git checkout main && git pull + git tag -a "v$(tr -d '\n' < VERSION)" -m "kcore $(tr -d '\n' < VERSION)" + git push origin "v$(tr -d '\n' < VERSION)" + ``` + +3. **Quality gate (recommended)** + `make check` and/or `make test-all` per [rust-quality-checks](../.cursor/rules/rust-quality-checks.mdc). + +4. **Build artifacts (Nix)** + At the same commit as the tag (or clean tree on `main` at that commit): + + ```bash + make release-build + ``` + + This runs [`scripts/release.sh`](../scripts/release.sh) `build`: ISO → `result-iso/`, `kcore-kctl` → `result-kctl/`. + +5. **Package `dist/`** + Produces the GitHub upload set: + + ```bash + make release-dist + ``` + + - `dist/kcore-kctl-$(VERSION)-linux-x86_64.tar.gz` (binary at archive root: `kcore-kctl`) + - `dist/kcoreos-$(VERSION)-x86_64-linux.iso` (release asset name; copied from the single ISO produced under `result-iso/iso/`) + - `dist/SHA256SUMS` for both files + + Or in one step after a successful build: `make release` (build + dist; does not publish). + +6. **Release notes** + Copy the template and edit: + + ```bash + cp RELEASE_NOTES.template.md RELEASE_NOTES.md + # edit RELEASE_NOTES.md (not committed; see .gitignore) + ``` + + Publish uses `RELEASE_NOTES` env if you need another path: `RELEASE_NOTES=path/to/notes.md make release-publish`. + +7. **Publish the GitHub Release** + + ```bash + make release-publish + ``` + + This runs `gh release create v$(VERSION) --verify-tag` and uploads the tarball, ISO, and `SHA256SUMS`. + +## Artifact notes + +- **kctl** in the tarball is the **Nix-built** `kcore-kctl` from `.#kcore-kctl` (same lineage as the ISO), not a raw `cargo build`. +- **Platform**: **linux x86_64** (glibc via Nix). No musl/static build in this flow. +- **Large files**: ISOs are ~1–2 GiB; GitHub per-file limit is 2 GiB. Stay under that or split hosting for huge artifacts. + +## Troubleshooting + +- **`gh release create` fails on `--verify-tag`**: push the tag first: `git push origin vX.Y.Z`. +- **Wrong ISO name**: Nix may place the built ISO under a NixOS-derived name in `result-iso/iso/`; the dist step discovers the single `*.iso` there and copies it to the release asset name `kcoreos-$(VERSION)-x86_64-linux.iso`. +- **Token in automation**: set `GH_TOKEN` in the environment for non-interactive `gh` (e.g. CI in the future); this doc targets local operator use. diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..4d41172 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# Build release artifacts (Nix ISO + kcore-kctl), package dist/, publish GitHub Release. +# Usage: +# ./scripts/release.sh build # nix build ISO + kcore-kctl -> result-iso, result-kctl +# ./scripts/release.sh dist # dist/*.tar.gz, ISO copy, dist/SHA256SUMS +# ./scripts/release.sh publish # gh release create (needs tag on remote, RELEASE_NOTES.md) +# Environment: +# RELEASE_NOTES Path to release notes file (default: RELEASE_NOTES.md) +# GH_REPO owner/repo override for gh (optional; defaults to git remote) +set -euo pipefail + +ROOT="$(cd "$(dirname "${0}")/.." && pwd)" +cd "${ROOT}" + +VERSION="$(tr -d '\n' < VERSION)" +ISO_NAME="kcoreos-${VERSION}-x86_64-linux.iso" +KCTL_ARCHIVE="kcore-kctl-${VERSION}-linux-x86_64.tar.gz" + +die() { + echo "release.sh: $*" >&2 + exit 1 +} + +require_cmd() { + command -v "${1}" >/dev/null 2>&1 || die "missing required command: ${1}" +} + +cmd_build() { + require_cmd nix + echo "==> Building ISO (${ISO_NAME})..." + nix build ".#nixosConfigurations.kcore-iso.config.system.build.isoImage" -o result-iso + echo "==> Building kcore-kctl..." + nix build ".#kcore-kctl" -o result-kctl + echo "==> Build outputs:" + ls -lh result-iso/iso/*.iso + ls -lh result-kctl/bin/kcore-kctl +} + +cmd_dist() { + require_cmd tar + require_cmd sha256sum + [[ -f result-kctl/bin/kcore-kctl ]] || die "run '${0} build' first (missing result-kctl/bin/kcore-kctl)" + shopt -s nullglob + iso_candidates=(result-iso/iso/*.iso) + shopt -u nullglob + [[ "${#iso_candidates[@]}" -eq 1 ]] || die "expected exactly one ISO under result-iso/iso/; run '${0} build' first" + ISO_SRC="${iso_candidates[0]}" + + mkdir -p dist + echo "==> Packaging ${KCTL_ARCHIVE}..." + tar -C result-kctl/bin -czf "dist/${KCTL_ARCHIVE}" kcore-kctl + echo "==> Copying $(basename "${ISO_SRC}") to dist/${ISO_NAME}..." + cp -f "${ISO_SRC}" "dist/${ISO_NAME}" + echo "==> Writing dist/SHA256SUMS..." + ( + cd dist + sha256sum "${ISO_NAME}" "${KCTL_ARCHIVE}" >SHA256SUMS + ) + echo "==> dist layout:" + ls -lh dist/ + cat dist/SHA256SUMS +} + +cmd_publish() { + require_cmd gh + NOTES="${RELEASE_NOTES:-RELEASE_NOTES.md}" + [[ -f "${NOTES}" ]] || die "missing ${NOTES} - copy RELEASE_NOTES.template.md to RELEASE_NOTES.md and edit" + [[ -f "dist/${KCTL_ARCHIVE}" ]] || die "run '${0} dist' first" + [[ -f "dist/${ISO_NAME}" ]] || die "run '${0} dist' first" + [[ -f dist/SHA256SUMS ]] || die "run '${0} dist' first" + + TAG="v${VERSION}" + echo "==> Creating GitHub release ${TAG} (verify-tag)..." + gh release create "${TAG}" \ + --verify-tag \ + --title "kcore ${VERSION}" \ + --notes-file "${NOTES}" \ + "dist/${KCTL_ARCHIVE}" \ + "dist/${ISO_NAME}" \ + dist/SHA256SUMS + echo "==> Done: gh release view ${TAG}" +} + +usage() { + echo "Usage: ${0} {build|dist|publish}" + exit 1 +} + +case "${1:-}" in + build) cmd_build ;; + dist) cmd_dist ;; + publish) cmd_publish ;; + *) usage ;; +esac From f4d51d8da21674a2cb6e3e09710fbb997f5022b4 Mon Sep 17 00:00:00 2001 From: Riccardo Tacconi Date: Wed, 29 Apr 2026 17:04:15 +0100 Subject: [PATCH 2/4] fix(release): address CodeRabbit PR16 (markdownlint, gh via nix develop, file tree) --- docs/file-structure.md | 4 +++- docs/release.md | 4 ++-- scripts/release.sh | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/file-structure.md b/docs/file-structure.md index bc23aaa..504897a 100644 --- a/docs/file-structure.md +++ b/docs/file-structure.md @@ -102,7 +102,8 @@ kcore-rust/ │ └── CrossDcReplication.cfg TLC configuration for cross-DC replication model │ ├── scripts/ -│ └── build-iso-remote.sh SSH helper to build the kcore ISO on a remote Linux host +│ ├── build-iso-remote.sh SSH helper to build the kcore ISO on a remote Linux host +│ └── release.sh release helper: Nix-build ISO and kcore-kctl, package dist/ + SHA256SUMS, gh publish (see docs/release.md) │ └── docs/ ├── Architecture.md high-level flow diagrams (Mermaid) and component responsibilities @@ -119,6 +120,7 @@ kcore-rust/ ├── nix-vm-config-generation.md when/how Nix VM configs are generated and applied ├── mtls-bootstrap-and-auth.md certificate creation, node bootstrap, runtime mTLS ├── formal-methods-and-verification.md notes on formal verification approaches + ├── release.md release checklist: VERSION bump, tag, Nix build, GitHub assets via Make/gh └── file-structure.md this file ``` diff --git a/docs/release.md b/docs/release.md index 381610e..867ada9 100644 --- a/docs/release.md +++ b/docs/release.md @@ -48,7 +48,7 @@ Releases are **operator-driven**: build artifacts locally with Nix, publish to G ``` - `dist/kcore-kctl-$(VERSION)-linux-x86_64.tar.gz` (binary at archive root: `kcore-kctl`) - - `dist/kcoreos-$(VERSION)-x86_64-linux.iso` (release asset name; copied from the single ISO produced under `result-iso/iso/`) + - `dist/kcoreos-$(VERSION)-x86_64-linux.iso` (release asset name; copied from the single ISO produced under `result-iso/iso/`) - `dist/SHA256SUMS` for both files Or in one step after a successful build: `make release` (build + dist; does not publish). @@ -69,7 +69,7 @@ Releases are **operator-driven**: build artifacts locally with Nix, publish to G make release-publish ``` - This runs `gh release create v$(VERSION) --verify-tag` and uploads the tarball, ISO, and `SHA256SUMS`. + This runs `nix develop --command gh release create v$(VERSION) --verify-tag` (GitHub CLI from the dev shell) and uploads the tarball, ISO, and `SHA256SUMS`. ## Artifact notes diff --git a/scripts/release.sh b/scripts/release.sh index 4d41172..ba4a040 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -62,7 +62,7 @@ cmd_dist() { } cmd_publish() { - require_cmd gh + require_cmd nix NOTES="${RELEASE_NOTES:-RELEASE_NOTES.md}" [[ -f "${NOTES}" ]] || die "missing ${NOTES} - copy RELEASE_NOTES.template.md to RELEASE_NOTES.md and edit" [[ -f "dist/${KCTL_ARCHIVE}" ]] || die "run '${0} dist' first" @@ -71,14 +71,14 @@ cmd_publish() { TAG="v${VERSION}" echo "==> Creating GitHub release ${TAG} (verify-tag)..." - gh release create "${TAG}" \ + nix develop --command gh release create "${TAG}" \ --verify-tag \ --title "kcore ${VERSION}" \ --notes-file "${NOTES}" \ "dist/${KCTL_ARCHIVE}" \ "dist/${ISO_NAME}" \ dist/SHA256SUMS - echo "==> Done: gh release view ${TAG}" + echo "==> Done: nix develop --command gh release view ${TAG}" } usage() { From be9ff79fac945bb63ca8b8353efc0ef9108d5109 Mon Sep 17 00:00:00 2001 From: Riccardo Tacconi Date: Wed, 29 Apr 2026 17:11:46 +0100 Subject: [PATCH 3/4] fix(ci): quote kani-shard job name for valid YAML --- .github/workflows/formal-checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/formal-checks.yml b/.github/workflows/formal-checks.yml index 61796ae..c40ad4d 100644 --- a/.github/workflows/formal-checks.yml +++ b/.github/workflows/formal-checks.yml @@ -76,7 +76,7 @@ jobs: # toolchain is identical), so the first shard to finish install # warms the cache for all the others on subsequent runs. kani-shard: - name: kani-shard / ${{ matrix.crate }} :: ${{ matrix.harness }} + name: "kani-shard / ${{ matrix.crate }} :: ${{ matrix.harness }}" runs-on: ubuntu-latest timeout-minutes: 15 strategy: From b5d4328b604c3b518a74b0b566f0a59239134434 Mon Sep 17 00:00:00 2001 From: Riccardo Tacconi Date: Wed, 29 Apr 2026 17:40:43 +0100 Subject: [PATCH 4/4] fix(ci): raise kani-shard timeouts (job was capped below install+proof) --- .github/workflows/formal-checks.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/formal-checks.yml b/.github/workflows/formal-checks.yml index c40ad4d..971075f 100644 --- a/.github/workflows/formal-checks.yml +++ b/.github/workflows/formal-checks.yml @@ -78,7 +78,10 @@ jobs: kani-shard: name: "kani-shard / ${{ matrix.crate }} :: ${{ matrix.harness }}" runs-on: ubuntu-latest - timeout-minutes: 15 + # Must exceed install + proof step ceilings plus checkout/cache/rust-cache overhead. + # Cold Kani install + CBMC setup can approach 15 min on busy runners; install+proof + # ceilings plus checkout/cache need headroom (avoid cancelling mid-proof). + timeout-minutes: 35 strategy: # `fail-fast: false` lets all harnesses report results even # if one regresses; otherwise GHA cancels the rest and we lose @@ -186,7 +189,7 @@ jobs: # action's last step runs `cargo-kani` at the workspace root with # no `-p` flag, which on this multi-crate workspace tries to # verify every member and hangs (cancelled at ~9-10 min, well - # before our 15-min step budget). Direct install + explicit + # before the job timeout). Direct install + explicit # `-p kcore-sanitize --harness` below gives predictable timing. # # No protobuf compiler is needed: `kcore-sanitize` is a leaf @@ -197,7 +200,7 @@ jobs: # every time it runs even when the layout is already in place. - name: Install kani-verifier id: kani-install - timeout-minutes: 10 + timeout-minutes: 18 run: | set -euo pipefail if ! command -v cargo-kani >/dev/null 2>&1; then @@ -253,7 +256,7 @@ jobs: # match. `--exact` would be stricter but does not exist in # kani-verifier 0.67.0. - name: Run bounded Kani proof - timeout-minutes: 8 + timeout-minutes: 12 run: | set -euo pipefail cargo-kani --version