Skip to content

feat: phase 8 - packaging, releasing, and CI - #1

Merged
bougyman merged 10 commits into
mainfrom
phase-8
Aug 8, 2026
Merged

feat: phase 8 - packaging, releasing, and CI#1
bougyman merged 10 commits into
mainfrom
phase-8

Conversation

@bougyman

@bougyman bougyman commented Aug 7, 2026

Copy link
Copy Markdown
Member

Summary

Phase 8: makes this thing actually releasable. Cross-platform standalone
binaries via Burrito (one binary is both the interactive CLI and, with
LINEAR_CLI_DAEMON=true, the daemon), a container image for the daemon,
and release-please-driven versioning - matching the conventions already
used by linear-cli (the Ruby sibling) and the user's other FOSS
projects. Full design writeup and verification notes in
documents/phase-8-plan.adoc.

Changes

  • docs: add the phase 8 plan doc (decisions, verification notes, sequencing)
  • fix(ci): stop leaking git remote credentials into container image labels - ci/build_image.sh was embedding the raw (possibly credential-bearing) git remote get-url origin output directly into the org.opencontainers.image.url label; rebuilt from the already-parsed owner/repo path instead
  • feat(release): track the version via release-please - .version.txt + .release-please-config.json/-manifest.json, app/mix.exs's version field wired as a generic extra-file via x-release-please-start-version/-end markers
  • feat(release): add the real Burrito multi-target release config - macOS Apple Silicon, Linux x86_64, Windows x86_64, one binary for both the CLI and the daemon
  • feat: add oci/Containerfile wrapping the Linux x86_64 Burrito binary (not a separate Elixir build stage) - lives in oci/, not the repo root, since there may be more than one variant later
  • ci: add a validate workflow - mix test, mix format --check-formatted, and the existing (previously unwired) ci/conventional_commits.sh
  • ci: add the release-please + Burrito/container publish pipeline - on push to main, gated on the validate workflow, release-please-action opens/merges release PRs; when it cuts a release, a second workflow builds every Burrito target plus the container image, attaches binaries to the GitHub release, and pushes the image to ghcr.io

Verified, not assumed

  • Burrito 1.6.0 actually requires Zig 0.16.0 - its own README says 0.15.2. Found by hitting the version-check error directly.
  • exqlite's NIF (via ecto_sqlite3) survives cross-compilation. Built a real Burrito release targeting linux_x86_64 from this macOS arm64 host; the build log showed Burrito cross-compiling it for real (CC/LD invocation, not a mismatched precompiled fetch: Going to recompile NIF for cross-build: exqlite -> x86_64-linux ... Successfully re-built exqlite for x86_64-linux!). Ran the produced binary inside a podman container (--platform linux/amd64, QEMU-emulated) with LINEAR_CLI_DAEMON=true - it booted, stayed alive, and created a real ~/.linear_cli/oban.db on disk.
  • custom_erts: (used briefly while chasing a not-yet-catalogued local OTP patch version) silently skips Burrito's musl-runtime-embedding step for Linux targets - verified by reading deps/burrito/lib/steps/fetch/fetch_musl.ex. Not used in the real pipeline, which pins a specific, catalogued OTP version (29.0.3) instead.
  • The credential-leak fix above was caught by actually inspecting the labels on a locally-built image, not by code review alone. The one locally-built image that had the leaked token has been deleted.
  • Every third-party GitHub Action version/input referenced in the workflows (googleapis/release-please-action, erlef/setup-beam, mlugg/setup-zig, actions/checkout, actions/cache) was checked against its real action.yml/tags via the GitHub API, not guessed.

Tests

  • mix test: 174 passed (1 doctest, 173 tests), against the pinned OTP 29.0.3 / Elixir 1.20.3 toolchain used in CI
  • mix format --check-formatted: clean
  • All three workflow YAML files parsed successfully (ruby -ryaml)
  • .release-please-config.json / .release-please-manifest.json validated as JSON
  • Built the real (not exploratory) linear_cli Burrito release for the linux_x86_64 target locally and ran it
  • Built the container image locally with ci/build_image.sh + podman (--platform linux/amd64, since this dev machine is arm64) and ran it, confirming LINEAR_CLI_DAEMON=true boots correctly inside the actual image
  • Confirmed ci/build_image.sh correctly discovers oci/Containerfile via its existing search order, with no script changes needed
  • The release-please PR/GitHub-release flow itself and the full CI pipeline can only be verified by a real push to main - RELEASE_PLEASE_TOKEN is confirmed already present as an org secret, so this should Just Work once merged

One binary is both the interactive CLI and (with LINEAR_CLI_DAEMON=true)
the daemon - macOS Apple Silicon, Linux x86_64, Windows x86_64. exqlite's
NIF cross-compilation verified empirically (built and ran the
linux_x86_64 target under podman/QEMU, confirmed it created a real
SQLite db on disk) rather than assumed - see documents/phase-8-plan.adoc.
macOS Intel intentionally not targeted.
## Changes

- Wraps the already-built Linux x86_64 Burrito binary rather than a separate Elixir build stage - one build artifact, reused
- Lives in oci/ (not the repo root) since we may want more than one variant later
- ci/build_image.sh already searches oci/Containerfile as a fallback location, so no script changes were needed

## Tests

- Built locally with ci/build_image.sh + podman (--platform linux/amd64, this dev machine is arm64)
- Ran the built image with LINEAR_CLI_DAEMON=true, confirmed it stays up rather than crashing
## Changes

- mix test + mix format --check-formatted, against a pinned OTP 29.0.3 / Elixir 1.20.3 toolchain
- Wires up the existing (previously unwired) ci/conventional_commits.sh as its own job
- Triggered on pull_request and workflow_call, so release.yaml can gate on it

## Tests

- Validated YAML syntax (ruby -ryaml)
- mix test (174 passed) and mix format --check-formatted run clean locally against the same toolchain pin
## Changes

- release.yaml: on push to main, gated on the ci.yaml validate workflow, runs googleapis/release-please-action@v5 against .release-please-config.json/.release-please-manifest.json
- When release-please creates a release, calls burrito-release.yaml with the new tag
- burrito-release.yaml: builds all 3 Burrito targets in one job (erlef/setup-beam pinned to OTP 29.0.3/Elixir 1.20.3, mlugg/setup-zig pinned to 0.16.0), uploads the binaries to the GitHub release via gh release upload
- burrito-release.yaml also builds the linux_x86_64 target again for the container job (parallel, not serialized - no artifact sharing needed) and publishes it to ghcr.io via the existing ci/build_image.sh + ci/publish.sh

## Tests

- Validated YAML syntax (ruby -ryaml) for both files
- Action versions/inputs (release-please-action, setup-beam, setup-zig, actions/checkout, actions/cache) verified against their real action.yml/tags via the GitHub API, not guessed
- The actual release-please PR/GitHub release flow can only be verified by a real push to main once RELEASE_PLEASE_TOKEN is set up (confirmed already present as an org secret)
Copilot AI lite review requested due to automatic review settings August 7, 2026 18:28

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements “Phase 8” packaging/releasing/CI so the project can ship cross-platform standalone binaries (Burrito), publish a daemon container image to GHCR, and manage versions/releases via release-please, with CI validation gating releases.

Changes:

  • Add Burrito release configuration (multi-target) and wire release-please version tracking into app/mix.exs + new repo-root release metadata files.
  • Add CI workflows: validation (mix test, format, conventional commits) plus release orchestration and artifact publishing (binaries + container image).
  • Harden container build metadata by avoiding embedding raw git remote URLs into OCI image labels, and add a daemon container Containerfile that wraps the Burrito Linux binary.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
oci/Containerfile Defines the daemon container image by copying in the prebuilt Linux Burrito binary.
documents/phase-8-plan.adoc Adds the Phase 8 design/verification write-up.
ci/build_image.sh Prevents credential leakage by deriving the OCI URL label from parsed owner/repo instead of the raw remote URL.
app/mix.exs Adds release-please version markers, Burrito release targets, and the Burrito dependency.
app/mix.lock Locks Burrito and its transitive deps.
app/.gitignore Ignores Burrito output directory (burrito_out/).
AGENTS.md Adds Phase 8 plan to the agent plan index.
.version.txt Introduces the version file tracked by release-please.
.release-please-manifest.json Adds the release-please manifest mapping for the root package.
.release-please-config.json Configures release-please (simple release type, changelog, extra-file mix.exs updating).
.github/workflows/ci.yaml Adds CI validation workflow (tests, format, conventional commits).
.github/workflows/release.yaml Adds release-please workflow gated on CI.
.github/workflows/burrito-release.yaml Adds publishing workflow to build/upload Burrito targets and build/push GHCR image.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/workflows/burrito-release.yaml
Comment thread ci/build_image.sh Outdated
Comment thread ci/build_image.sh Outdated
pull_request's default checkout ref is GitHub's synthetic
refs/pull/N/merge test-merge commit (subject "Merge <sha> into <sha>"),
not the PR branch tip - that commit was failing conventional-commit
validation on every PR, and neither rebasing the branch nor loosening
the validator would fix it (GitHub regenerates that ref fresh on every
run, regardless of branch history). Check out
github.event.pull_request.head.sha instead, falling back to
github.sha for non-PR triggers.
git init --bare's HEAD symref follows the runner's ambient
init.defaultBranch config (falls back to git's legacy "master" if
unset). The test only ever creates/pushes "main", so on a runner
without init.defaultBranch=main set (e.g. GitHub Actions, unlike this
machine), origin's HEAD dangled at a "master" that never exists, and
git ls-remote --symref reported nothing - failing default_branch/0's
test in CI while passing locally. Set origin's HEAD explicitly instead
of relying on ambient config, matching what a real git host does when
you configure a repo's default branch.

Verified by reproducing the failure locally with
GIT_CONFIG_GLOBAL=/dev/null before this fix, and confirming all 8
git_test.exs tests pass under those same config-free conditions after it.
- burrito-release.yaml: install Zig 0.16.0 in the `container` job too -
  it runs the same `mix release`/Burrito build as the `burrito` job,
  which already has this step; without it the build fails
- build_image.sh: org.opencontainers.image.source now points to
  $image_url/tree/$revision instead of a string embedding
  $USER@$HOSTNAME - closes an info leak and satisfies the OCI spec's
  requirement that this label be a URL
- build_image.sh: drop the conflicting org.opencontainers.image.version
  --label (set to the raw tag) - oci/Containerfile already sets it
  correctly from $APP_VERSION via the build-arg; the CLI-level label
  was silently overriding that with the wrong value
@bougyman
bougyman merged commit 905c238 into main Aug 8, 2026
2 checks passed
@bougyman
bougyman deleted the phase-8 branch August 8, 2026 15:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants