gitmote / docs / evolution / ci-runner.md · history · branches
6910 bytes · raw
Status: largely realized — see architecture/ci.md. CI (run
.gitmote/workflowson push,actself-hosted on Scaleway Jobs, logs, status UI, encrypted secrets) has landed. Container image builds work on a daemon-backed local/VPS runner behind theGITMOTE_CI_ALLOW_BUILDSopt-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.
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.
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 |
Same move as "don't reimplement git, shell out to it." Candidates:
gitmote supplies the forge around the engine, not the executor.
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.)
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.
act (runs .github/workflows YAML, ~80% Actions-compatible)
vs Woodpecker (Drone-lineage, its own .woodpecker.yml format, no Actions
compat). Not a neutral toss-up given the mirror: act keeps one CI
definition that runs both self-hosted and on the GitHub mirror, so the
break-glass redeploy path uses the same workflow — Actions-compat is
load-bearing here, not a preference. Woodpecker's simplicity means two
CI definitions (.woodpecker.yml for gitmote, .github/workflows/ for the
mirror) that drift, quietly weakening the "GitHub as clean redeploy" guarantee.
Pick Woodpecker only if the mirror running identical CI genuinely doesn't matter.