Portable Elixir and OTP CI/CD control plane
  • Elixir 99.3%
  • Shell 0.3%
  • Dockerfile 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Sean OMeara 576a4f8a65
All checks were successful
binjovi/ci Binjovi completed the frozen plan
chore(staging): remove old staging route
2026-08-25 15:15:46 +02:00
apps chore(staging): remove old staging route 2026-08-25 15:15:46 +02:00
config feat(staging): serve binjovi.dev 2026-08-25 14:56:17 +02:00
docs feat: gate production through staging availability 2026-08-25 11:43:37 +02:00
manifests chore(staging): remove old staging route 2026-08-25 15:15:46 +02:00
tests fix: close staging availability cutover gaps 2026-08-25 12:12:09 +02:00
.dockerignore feat: establish the Bytey control-plane floor 2026-08-19 13:30:35 +02:00
.formatter.exs feat: establish the Bytey control-plane floor 2026-08-19 13:30:35 +02:00
.gitignore feat: establish the Bytey control-plane floor 2026-08-19 13:30:35 +02:00
AGENTS.md feat: establish the Bytey control-plane floor 2026-08-19 13:30:35 +02:00
ci.Dockerfile Upgrade Bandit past HTTP/2 advisories 2026-08-24 12:10:48 +02:00
Dockerfile Include manifests in image check context 2026-08-24 16:54:46 +02:00
Makefile fix: close staging availability cutover gaps 2026-08-25 12:12:09 +02:00
mix.exs feat: establish the Bytey control-plane floor 2026-08-19 13:30:35 +02:00
mix.lock Upgrade Bandit past HTTP/2 advisories 2026-08-24 12:10:48 +02:00
README.md docs: record durable gate evidence 2026-08-25 13:43:32 +02:00

binjovi

Binjovi is a portable Elixir and OTP CI/CD control plane. Kubernetes is its first deployment target. Kubernetes is not its durable state model or its only execution engine.

The first pass uses sean/bytey to measure the minimum control-plane cost. Bytey has almost no product work, so it exposes coordination overhead.

Current slice

The current slice contains:

  • pure Build, Release, and Deployment models with immutable source identity;
  • a typed, shell-free Bytey execution plan;
  • a PostgreSQL authority with automatic migrations, provider-delivery deduplication, source deduplication, and compare-and-set transitions;
  • one short-lived :gen_statem coordinator for each active Build;
  • pod-scoped lease owners and fencing epochs for safe controller takeover;
  • signed Forgejo webhook wakeups for Bytey and for Binjovi, with a bounded five-minute recovery scan for missed deliveries;
  • canonical Forgejo pull-request Changes with frozen plan digests and the binjovi/ci commit status;
  • restricted Kubernetes Jobs that run Bytey through an AlmaLinux 10 buildctl client with no shell, token, or credential mount;
  • durable status delivery, pull-request head supersession, Job cancellation, and Job log folds;
  • an in-memory Bytey floor probe that measures the control contract without creating project Build records;
  • authenticated, idempotent release commands with durable stage, lease, fencing, retry, and external-effect evidence;
  • exact-head fast-forward integration, main promotion, stable patch tags, Forgejo Releases, and immutable artifact identity;
  • independent deploy commands with exact-revision target verification;
  • an operator dashboard with only Builds, Releases, and Deploys; Build folds contain Change evidence, execution attempts, logs, and release actions;
  • an OTP release and AlmaLinux 10 container image;
  • a self-hosted Shipwright image executor with deterministic BuildRun identity;
  • a machine-owned Forgejo deploy branch and exact-digest Argo CD delivery;
  • an isolated, read-only staging instance and a 60-second dual-stack availability gate before automatic production promotion;
  • three control-plane replicas on separate nodes, with a two-copy disruption floor during planned node rolls.

The Bytey floor probe stays in memory and does not create project activity. Bytey and Binjovi pull requests run the real CI path. An authenticated user can release an exact successful Build from the dashboard. Binjovi fails the command if its Change head or base became stale. For an exact current Bytey Build, it fast-forwards trunk and main, creates the next stable patch tag, and records the exact Forgejo Release URL. A separate deploy command updates the bytey-floorplan ConfigMap with resource-version compare-and-set. Binjovi records a Deploy only after exact readback.

Binjovi pull requests use Shipwright to isolate the image build. The first new release is v0.1.0; later releases increase the stable patch version. Each Release records the exact OCI digest. A separate deploy command first copies the exact revision and digest to deploy-staging. Argo CD applies the read-only instance at staging.binjovi.sean.farm. A restricted host-network Job then verifies the Service, the internal Gateway, public IPv4 reachability, public A and AAAA records, the UI, and the CI service spine. The gate needs five consecutive preflight passes and then zero failures for 60 seconds. The probe writes the exact summary to stdout and the Pod termination message, so a node replacement cannot remove the promotion evidence. Binjovi stores the result with the staging Deploy. Only then does policy copy the same Release to deploy for production. Production succeeds only after all three replicas report the exact source revision and image digest. A failed staging Deploy has an authenticated retry action in its dashboard fold. A retry deletes the old deterministic probe Job and repeats the same Release verification. The API rejects direct production retries.

Each self-hosted revision records its complete accepted-to-verified duration, with separate Build, Release, and Deploy timings in the dashboard.

Run

Install Elixir 1.20 with Erlang/OTP 29, then run:

mix deps.get
make check
make floor

Start the HTTP service:

mix run --no-halt
open http://127.0.0.1:4000/
curl http://127.0.0.1:4000/health/ready
curl http://127.0.0.1:4000/api/v1/floor
curl http://127.0.0.1:4000/api/v1/projects/bytey/activity
curl http://127.0.0.1:4000/api/v1/projects/binjovi/activity

The Kubernetes dashboard is https://binjovi.sean.farm. Select bytey to see real pull-request Builds with folded Forgejo Change evidence, Releases, and marker Deploys. Select binjovi to see each self-hosted image Build, OCI Release, and Argo CD Deploy. The dashboard refreshes durable PostgreSQL activity every five seconds.

Forgejo calls /hooks/forgejo through the cluster-local Service. The endpoint verifies the payload HMAC before it wakes a reconciler. The payload is only a hint. The reconciler reads canonical source state from Forgejo and durable phase state from PostgreSQL. A five-minute scan repairs a lost webhook. Normal source discovery therefore takes one signed delivery round trip, not a fixed polling interval. Configure the Forgejo hook with the pull_request event. A push event alone supports only the temporary self-host bootstrap path.

Each phase has a durable wall-clock boundary. Build time is acceptance through terminal Build state. Release time is release-command acceptance through immutable artifact observation. Deploy time is deploy-command acceptance through independent target verification. The accepted-to-verified total runs from Build acceptance through Deploy verification. It can include policy wait time between the independent phases. A controller restart does not reset a phase timer.

Build logs stream while containers run. At terminal state, Binjovi also captures any container log that does not yet have a durable MinIO chunk. A replacement controller adopts the same deterministic BuildRun after a lease expires. Fence epochs stop an expired controller from changing the recovered run. The self-host image gate runs the complete make check suite before it can publish an image. BuildRun submission observes that deterministic identity before each bounded transport retry.

Floor scope

mix binjovi.floor and /api/v1/floor measure this path:

validated Forgejo-shaped event
  -> atomic delivery and Build acceptance
  -> OTP coordinator start
  -> immutable Bytey plan creation
  -> fake executor start and completion events
  -> terminal Build state

The floor measurement does not include Forgejo synchronization, PostgreSQL, worker network time, repository checkout, BuildKit, or Bytey compilation. It remains a stable lower bound while the dashboard shows the durable production observation path.

See docs/architecture.md for the design boundaries and docs/pipeline-lifecycles.md for the three-section project model and the Build and Release state machines.