|
root / docs / superpowers / specs / 2026-06-02-repoman-ci-release-design.md
2026-06-02-repoman-ci-release-design.md markdown 190 lines 7.1 KB

repoman CI + binary release — design

Date: 2026-06-02
Status: Approved (design), pending implementation
Author: Chris Tusa chris.tusa@leafscale.com

Goal

Build, test, and release repoman binaries automatically via Isurus CI, using
the prebuilt reef-lang toolchain container as the build environment. On every
push/PR: build + test. On a version tag: also package a binary bundle and publish
it as an Isurus release asset.

Context

  • repoman is a reef application (compiles to a single build/repoman binary via
    reefc), not the reef compiler. It also ships profiles/*.yml and
    recipes/{docker,claude}/* that make install lays under
    /usr/local/share/repoman.
  • Isurus CI config is .isurus-ci.yml at the repo root, hg-native (hg push
    triggers; hg tag vX && hg push → tag event). Steps run in docker: images;
    container-registry: authenticates private pulls; a top-level release: block
    attaches assets: globs to the release on tag using a releases:write token.
  • The build image registry.sharkos.one/reef/reef-builder:latest already contains
    a pre-installed reef compiler (reefc on PATH, runtime + clang/lld baked in;
    reefc doctor passes). No opam env setup is needed — that is only for building
    the compiler from OCaml source, which repoman does not do.
  • Reference pipelines: ~/repos/reef-lang/.isurus-ci.yml (canonical Isurus
    example) and ~/repos/zygaena-project/coral/.woodpecker.yml (a reef app release,
    pre-Isurus). This design follows reef-lang's structure.

Decisions

  • Release contents: binary bundle only (no source tarball).
  • Installer: a standalone install.sh inside the bundle — end users need no
    reefc and no Makefile to install.
  • Arch: linux-amd64 only (the reef-builder image and repoman's Incus-host
    target are x86-64). arm64 deferred.
  • Build image tag: :latest (floats with the toolchain, matching reef-lang),
    not a pinned :0.6.5.
  • Triggers: build + test on [push, pull_request, tag]; package + release on
    [tag] only. Single when: filter map, no top-level branch: gate (a branch
    gate is incompatible with PR CI — per reef-lang's CLAUDE.md note).
  • Version source of truth: the version field in reef.toml (currently
    0.6.0). Packaging derives the version from it; the release fires off $CI_TAG.

Components

1. .isurus-ci.yml (repo root, new)

container-registry:
  - url: registry.sharkos.one
    secret: SHARKOS_REGISTRY_CREDS

when:
  event: [push, pull_request, tag]

steps:
  - name: build
    docker: { image: registry.sharkos.one/reef/reef-builder:latest }
    commands:
      - reefc doctor
      - make all
      - ./build/repoman --version
    timeout: 30m

  - name: test
    docker: { image: registry.sharkos.one/reef/reef-builder:latest }
    commands:
      - make test
    timeout: 30m

  - name: package
    docker: { image: registry.sharkos.one/reef/reef-builder:latest }
    when: { event: [tag] }
    commands:
      - ./scripts/package-binary.sh
      - ls -la dist/
    timeout: 30m

release:
  tag: "$CI_TAG"
  title: "repoman $CI_TAG"
  assets:
    - dist/*.tar.gz
    - dist/*.sha256
  secret: ISURUS_RELEASE_TOKEN
  when:
    event: [tag]

Notes:

  • make all lives only in build. Steps share the cloned workspace and run
    sequentially (pipeline halts on first failure), so package only runs after
    build/test pass. Each target is also self-sufficient: make test compiles
    and runs the test files directly via reefc run (no dependency on
    build/repoman), and package-binary.sh calls make install, whose phony
    build prerequisite rebuilds the binary. No repeated make all.
  • reefc doctor in build gives an early, legible failure if the toolchain image
    is broken.

2. scripts/package-binary.sh (new, executable)

Produces dist/repoman-<version>-linux-amd64.tar.gz and a matching .sha256.

Behavior:

  1. Derive VERSION from reef.toml (grep '^version' | sed), allow override via
    $1.
  2. NAME=repoman-${VERSION}-linux-amd64, DIST=${REPOMAN_DIST:-dist}.
  3. Stage the real install tree by reusing the Makefile:
    make install DESTDIR="$STAGE/$NAME" PREFIX=/usr/local
    $STAGE/$NAME/usr/local/{bin/repoman, share/repoman/...}.
  4. Add README.md, LICENSE, and a generated install.sh (see §3) to
    $STAGE/$NAME/.
  5. tar -czf "$DIST/$NAME.tar.gz" -C "$STAGE" "$NAME".
  6. sha256sum "$DIST/$NAME.tar.gz" > "$DIST/$NAME.tar.gz.sha256".
  7. set -eu; clean up $STAGE on exit.

Rationale for reusing make install: the tarball's tree stays byte-identical to a
real install, so there is one definition of the install layout (DRY) — when a new
recipe/profile is added, the bundle picks it up automatically.

3. Bundled install.sh (inside the tarball)

Relocatable installer; needs no reefc, no make. Run from the unpacked bundle dir:

#!/bin/sh
set -eu
PREFIX="${PREFIX:-/usr/local}"
here="$(cd "$(dirname "$0")" && pwd)"
# Copy the staged usr/local tree into PREFIX.
cp -a "$here/usr/local/." "$PREFIX/"
echo "repoman installed under $PREFIX"
"$PREFIX/bin/repoman" --version
  • Honors PREFIX (default /usr/local); preserves perms (cp -a) so the
    launcher recipe stays 0755.
  • Mirrors what make install does, minus the build dependency.

4. .hgignore (edit)

Add dist/ so packaging output is not accidentally committed.

Data flow

hg push (branch)            -> build + test
hg push (PR)                -> build + test
hg tag vX.Y.Z && hg push    -> build + test + package -> dist/*.tar.gz(+.sha256)
                                                       -> release: attach assets to vX.Y.Z

Prerequisites (operator-side, outside this repo)

Two CI secrets must exist for the repoman repo (or its org) in Isurus
CI/CD > Secrets:

  • SHARKOS_REGISTRY_CREDS — Harbor robot creds JSON
    {"username":"robot$ci","password":"<token>"} to pull the builder image.
  • ISURUS_RELEASE_TOKEN — token with releases:write scope.

reef-lang already uses both names; if they are org-scoped, repoman inherits them.

Testing / verification

  • Local dry runs (no CI needed):
    • make all && make test inside the builder image (or locally) stays green.
    • ./scripts/package-binary.sh produces a tarball; unpack to a temp dir, run its
      install.sh with PREFIX=$(mktemp -d), and confirm bin/repoman --version
      prints the bundle version and the claude launcher lands at 0755.
  • CI smoke: push a branch → build+test pipeline goes green. Then a throwaway
    tag (or the next real release tag) exercises package + release end to end.

Out of scope

  • Source tarball, multi-arch (arm64), signing/notarization, publishing to any
    index beyond Isurus releases, and container-image publishing for repoman itself.

Files touched

File Change
.isurus-ci.yml new — pipeline + release block
scripts/package-binary.sh new, 0755 — binary bundle + sha256
.hgignore add dist/
Makefile (if needed) confirm install works with DESTDIR/PREFIX — already verified
README.md brief "Releases / CI" note (optional)