Release and packaging

How JACO releases are built, signed, and published. The pipeline runs in .github/workflows/release.yml; the packaging recipe is nfpm.yaml; the local preview path is make package / make release.

Artifact set per release

For each tag vX.Y.Z, the pipeline produces, per linux/<arch> in {amd64, arm64}:

  • jaco_<X.Y.Z>_<arch>.deb
  • jaco-<X.Y.Z>-1.<arch>.rpm
  • jaco_<X.Y.Z>_<arch>.apk
  • jaco-v<X.Y.Z>-linux-<arch>.tar.gz

Plus a single SHA256SUMS listing every artifact, and (once signing is wired) SHA256SUMS.minisig.

The current pipeline runs unsigned; the verification surface in jaco self-upgrade already requires the minisig, so signing is the gating step before self-upgrade is operationally complete. See "Verification key rotation" below.

What's inside each artifact

.deb / .rpm / .apk

nfpm-built from nfpm.yaml. Lays down:

  • /usr/local/bin/jaco and /usr/local/bin/jacod (mode 0755).
  • /lib/systemd/system/jaco.service.
  • /etc/jaco/jacod.yaml (type: config|noreplace — operator edits survive upgrade).

Postinstall, preremove, and postremove hooks under build/packaging/ handle daemon-reload and clean removal. Per-format dependencies:

  • .debdocker.io | docker-ce | docker-engine
  • .rpm/usr/bin/docker
  • .apkdocker

Alpine uses OpenRC; the systemd unit ships in the package but the postinstall reload is gated on systemctl presence so the install succeeds on musl systems even though the unit sits unused.

Generic tarball

Built by the release workflow (linux/<arch>) and by the local build/release.sh (linux + darwin, both archs). Contains:

  • jaco, jacod (static, CGO_ENABLED=0, -trimpath -ldflags "-s -w -X main.version=<tag>").
  • jaco.service, jacod.yaml, LICENSE, README.md, INSTALL.md.

Local preview

# build .deb/.rpm/.apk locally — defaults to amd64, version from
# `git describe`. Override:
make package
make package PACKAGE_ARCH=arm64 PACKAGE_VERSION=0.1.0
 
# cross-build linux + darwin × amd64 + arm64 tarballs into dist/.
# Set MINISIGN_KEY=... to also sign dist/SHA256SUMS.
make release

make package requires nfpm on PATH (install with go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.46.3).

Cutting a release

  1. Bump version where needed in code (none today — version is baked in via -ldflags "-X main.version=<tag>").
  2. Update CHANGELOG.md (if/when one exists).
  3. Confirm make ci-test vet lint is clean.
  4. Tag and push: git tag v0.2.0 && git push --tags.
  5. The release workflow triggers on tags matching v*. It builds binaries, packages, and the tarball per arch; assembles SHA256SUMS; uploads everything to a draft GitHub release for the tag.
  6. Once signing is wired, the workflow also produces SHA256SUMS.minisig.
  7. Publish the draft.

Signing (planned)

jaco self-upgrade verifies releases by:

  1. minisign signature over SHA256SUMS against the embedded public key (internal/packaging/release-pubkey.txt).
  2. SHA-256 of the tarball against the corresponding line in SHA256SUMS.

Both verifications must pass before either binary is touched on disk. Any mismatch surfaces as Error{code: upgrade_verification_failed} and the upgrade aborts.

The release workflow currently produces artifacts unsigned; a GPG-signing job is included commented-out, awaiting the project's minisign signing key being uploaded to the MINISIGN_SIGNING_KEY secret. Until signing is live, jaco self-upgrade will refuse to proceed against the unsigned releases — provision new clusters from the packages or generic tarball, not via self-upgrade.

Key rotation

If the signing key needs to rotate (compromise, schedule):

  1. Generate a new minisign keypair offline.
  2. Publish a new JACO release whose internal/packaging/release-pubkey.txt is the new key, signed with the old key (operators are running old code that verifies against the old key).
  3. Operators upgrade to that release; their daemons now verify future releases with the new key.
  4. The next release after that is signed with the new key.

What goes where between versions

  • gRPC field additions are backward-compatible. Operators may run an N+1 CLI against an N daemon within the same major version.
  • Raft FSM apply must remain compatible across adjacent versions. New command variants land additively under the Command{} proto's oneof.
  • jacod.yaml schema is closed — adding a key requires a loader update plus a defaulting rule so existing configs continue to parse.
  • Audit-event types are append-only on AuditEventType. Renames break backward compatibility for audit consumers.

See also