gitmote

gitmote / docs / evolution / ci-runner.md  ·  history  ·  branches

6910 bytes · raw

CI runner (speculative)

Status: largely realized — see architecture/ci.md. CI (run .gitmote/workflows on push, act self-hosted on Scaleway Jobs, logs, status UI, encrypted secrets) has landed. Container image builds work on a daemon-backed local/VPS runner behind the GITMOTE_CI_ALLOW_BUILDS opt-in (off by default; it grants CI host-daemon/root access — trusted repos only), so a VPS-hosted gitmote can build its own image. Still not built: the full self-deploy loop below (build → push → redeploy with a latest-wins guard). The Scaleway substrate still can't build (no daemon / no user namespaces), so cloud deployment stays on GitHub Actions. This note is kept for the original reasoning; don't cite it as a spec.

The idea

Once gitmote hosts repos, the natural graduation is CI: run workflows on push, the way GitHub Actions does — in a limited but real form. The payoff isn't feature parity; it's inverting the source of truth. S3 holds the truth, gitmote + a serverless runner execute your code, and GitHub becomes a plain git push mirror — a second remote, not the origin of anything. Self-hosting stops being a downgrade.

The loop then closes on itself: a push runs CI and redeploys gitmote — gitmote hosting, building, and shipping its own next version. That used to be the scary case (an instance replacing itself mid-write is the two-writer hazard), but the leased writer (reader-writer-split, shipped) makes it safe by construction: the new image boots as a follower, the old releases the lease on SIGTERM, the new promotes — the ouroboros never has two writers. The GitHub mirror then earns a second job: break-glass. If a bad push ever wedges the self-deploy loop, GitHub is an independent clean copy to redeploy from by hand — a backup that happens to also be a mirror. That recovery path is why the mirror is load-bearing, not a nicety.

Why it fits the existing design

CI doesn't bolt on; it reuses seams already built:

CI need Reused gitmote primitive
Trigger (on: push) The s3lite ref CAS commit — the one durable "a ref advanced" moment (architecture → push flow). "After a successful CAS, enqueue a run."
Run / job state An s3lite runs / jobs table — single-writer is fine at this scale
Logs & artifacts Append-only blobs in S3; content-before-pointer applies unchanged
Runner → server report The parent's authenticated API — same discipline as the push-hook channel; runners never touch s3lite directly
Status UI Another forge-metadata view

Don't rebuild the runner — drive an existing engine

Same move as "don't reimplement git, shell out to it." Candidates:

gitmote supplies the forge around the engine, not the executor.

Runner substrate: Scaleway Serverless Jobs

Almost purpose-built: a finite task that scales to zero — the same "wake, work, idle" shape as gitmote itself, and with a real Docker daemon + CPU the Container can't offer. A job spins up, clones from gitmote (it speaks git), runs the engine, reports back, dies. (Serverless Containers stay for the always-warm writer; long-running batch work is exactly what Jobs are for — already proven in a sibling project.)

Precedent

Forgejo / Gitea Actions is a small self-hosted forge running an Actions-compatible runner (act_runner, built on act's engine). "Small forge + reused engine + Actions compat" ships today — this isn't hypothetical.

Trigger & deploy discipline

Honest caveats

Open threads