CI/CD runner protocol — implementation
git-shark does not ship its own runner. It implements the server side of the Forgejo/Gitea
runner.v1 protocol so stock forgejo-runner / act_runner binaries work unchanged. This document
covers how the server side is built and what is / isn't done.
Component map
| Component | File | Role |
|---|---|---|
| Proto definitions | src/main/proto/{ping,runner}/v1/*.proto |
MIT-licensed copies of gitea.com/gitea/actions-proto-def, with java_package/java_multiple_files options added. |
| Generated messages | de.workaround.ci.proto.* (build output) |
Protobuf message classes generated by protobuf-maven-plugin at generate-sources. |
| Connect endpoint | ci/ConnectRunnerResource.java |
JAX-RS resource serving the Connect unary RPCs under /api/actions. |
| Registration/presence | ci/RunnerRegistrationService.java |
Token issue, runner register/declare/authenticate, list/delete. |
| Entities | model/CiRunner.java, model/CiRunnerRegistrationToken.java |
Persisted state (migration V19). |
| Admin UI | ci/AdminRunnerResource.java + templates/AdminRunnerResource/ |
Token generation, runner list, deletion. |
| Admin gate | account/AdminAccess.java |
Config-driven instance-admin check. |
Key decisions (and why)
- Reuse the Forgejo/Gitea runner, don't write our own. The wire protocol is small, open, and MIT-licensed; the runner ecosystem and GHA-compatible workflow format come for free. The real cost is server-side workflow semantics (trigger/DAG/matrix), not the transport — see the issue for the full rationale.
- Connect unary over plain JAX-RS, not
quarkus-grpc. Connect unary RPC is just an HTTPPOSTwith a serialized-protobuf body and a serialized-protobuf200body. We only need the generated message classes; serving them from ordinary JAX-RS resources avoids pulling in a gRPC server (extra listener, HTTP/2) we don't use. Henceprotobuf-maven-plugin(messages only) rather than the gRPC codegen. Errors use the Connect JSON error shape ({"code","message"}) with the matching HTTP status. - Two secret types, hashed at rest. A reusable, instance-scoped registration token (admin
issues it; runner presents it once in the
Registerbody) and a permanent per-runner secret (minted inRegister, sent to the runner exactly once, presented inx-runner-tokenthereafter). Only SHA-256 hashes are stored — same model asAccessToken. - Instance-scope, config-based admin. No admin role in the schema yet;
AdminAccessreadsgitshark.admin.handles. Deliberately minimal so phase 1 doesn't drag a roles migration with it. uuiddistinct from the DB primary key. The runner echoesuuidin headers; keeping it separate from the internalUUID idmeans the runner never learns our primary key.
What works today
protobuf-maven-plugingenerates theping.v1+runner.v1message classes at build time.Pinghealth check.Register: validates the registration token, creates aci_runner, returns the per-runner secret once. Registration tokens are reusable.Declare: authenticates byuuid+ secret, refreshes version/labels/last_seen, returns the runner.- Admin UI: generate/delete registration tokens, list/delete runners; gated by
AdminAccessand the/admin/*authenticated policy. - Tests:
RunnerRegistrationServiceTest(service),ConnectRunnerResourceTest(protobuf-over-HTTP round-trip for Ping/Register/Declare + auth failures),AdminAccessTest(admin gate).
What still needs to be implemented
- Run loop:
FetchTask(long-poll withtasks_version),UpdateTask,UpdateLog(offset /ack_indexresume). Not served yet. - Tables:
action_run,action_task,action_log(or log-blob storage). - Workflow pipeline: parse
.forgejo/workflows/*.ymlat the pushed head, evaluateon:triggers (push only for the MVP), expand a single job into a Task payload. Noneeds/matrixyet. - Run UI: per-repository run list + run detail with live per-step status and logs.
- Task state machine: timeout / zombie handling when a runner vanishes mid-task.
- Real-runner integration test: protocol round-trip against an actual
forgejo-runnercontainer (the current endpoint test uses a hand-built protobuf client, not the binary). - Later phases: secrets/variables delivery, label-based matching, concurrency/cancellation,
artifacts (
ACTIONS_RESULTS_URL), repo/org-scoped and ephemeral runners, commit/MR status.
References
- Protos: https://gitea.com/gitea/actions-proto-def (MIT)
- Gitea Actions design (server/runner split): https://docs.gitea.com/usage/actions/design
- Forgejo ↔ Gitea shared-protocol confirmation: https://code.forgejo.org/forgejo/runner/issues/525
# CI/CD runner protocol — implementation
git-shark does **not** ship its own runner. It implements the server side of the Forgejo/Gitea
`runner.v1` protocol so stock `forgejo-runner` / `act_runner` binaries work unchanged. This document
covers how the server side is built and what is / isn't done.
## Component map
| Component | File | Role |
|---|---|---|
| Proto definitions | `src/main/proto/{ping,runner}/v1/*.proto` | MIT-licensed copies of `gitea.com/gitea/actions-proto-def`, with `java_package`/`java_multiple_files` options added. |
| Generated messages | `de.workaround.ci.proto.*` (build output) | Protobuf message classes generated by `protobuf-maven-plugin` at `generate-sources`. |
| Connect endpoint | `ci/ConnectRunnerResource.java` | JAX-RS resource serving the Connect unary RPCs under `/api/actions`. |
| Registration/presence | `ci/RunnerRegistrationService.java` | Token issue, runner register/declare/authenticate, list/delete. |
| Entities | `model/CiRunner.java`, `model/CiRunnerRegistrationToken.java` | Persisted state (migration `V19`). |
| Admin UI | `ci/AdminRunnerResource.java` + `templates/AdminRunnerResource/` | Token generation, runner list, deletion. |
| Admin gate | `account/AdminAccess.java` | Config-driven instance-admin check. |
## Key decisions (and why)
- **Reuse the Forgejo/Gitea runner, don't write our own.** The wire protocol is small, open, and
MIT-licensed; the runner ecosystem and GHA-compatible workflow format come for free. The real cost
is server-side workflow semantics (trigger/DAG/matrix), not the transport — see the issue for the
full rationale.
- **Connect unary over plain JAX-RS, not `quarkus-grpc`.** Connect unary RPC is just an HTTP `POST`
with a serialized-protobuf body and a serialized-protobuf `200` body. We only need the generated
*message* classes; serving them from ordinary JAX-RS resources avoids pulling in a gRPC server
(extra listener, HTTP/2) we don't use. Hence `protobuf-maven-plugin` (messages only) rather than
the gRPC codegen. Errors use the Connect JSON error shape (`{"code","message"}`) with the matching
HTTP status.
- **Two secret types, hashed at rest.** A reusable, instance-scoped *registration token* (admin
issues it; runner presents it once in the `Register` body) and a permanent per-runner *secret*
(minted in `Register`, sent to the runner exactly once, presented in `x-runner-token` thereafter).
Only SHA-256 hashes are stored — same model as `AccessToken`.
- **Instance-scope, config-based admin.** No admin role in the schema yet; `AdminAccess` reads
`gitshark.admin.handles`. Deliberately minimal so phase 1 doesn't drag a roles migration with it.
- **`uuid` distinct from the DB primary key.** The runner echoes `uuid` in headers; keeping it
separate from the internal `UUID id` means the runner never learns our primary key.
## What works today
- `protobuf-maven-plugin` generates the `ping.v1` + `runner.v1` message classes at build time.
- `Ping` health check.
- `Register`: validates the registration token, creates a `ci_runner`, returns the per-runner secret
once. Registration tokens are reusable.
- `Declare`: authenticates by `uuid` + secret, refreshes version/labels/`last_seen`, returns the
runner.
- Admin UI: generate/delete registration tokens, list/delete runners; gated by `AdminAccess` and the
`/admin/*` authenticated policy.
- Tests: `RunnerRegistrationServiceTest` (service), `ConnectRunnerResourceTest` (protobuf-over-HTTP
round-trip for Ping/Register/Declare + auth failures), `AdminAccessTest` (admin gate).
## What still needs to be implemented
- **Run loop:** `FetchTask` (long-poll with `tasks_version`), `UpdateTask`, `UpdateLog` (offset /
`ack_index` resume). Not served yet.
- **Tables:** `action_run`, `action_task`, `action_log` (or log-blob storage).
- **Workflow pipeline:** parse `.forgejo/workflows/*.yml` at the pushed head, evaluate `on:` triggers
(push only for the MVP), expand a single job into a Task payload. No `needs`/`matrix` yet.
- **Run UI:** per-repository run list + run detail with live per-step status and logs.
- **Task state machine:** timeout / zombie handling when a runner vanishes mid-task.
- **Real-runner integration test:** protocol round-trip against an actual `forgejo-runner` container
(the current endpoint test uses a hand-built protobuf client, not the binary).
- **Later phases:** secrets/variables delivery, label-based matching, concurrency/cancellation,
artifacts (`ACTIONS_RESULTS_URL`), repo/org-scoped and ephemeral runners, commit/MR status.
## References
- Protos: <https://gitea.com/gitea/actions-proto-def> (MIT)
- Gitea Actions design (server/runner split): <https://docs.gitea.com/usage/actions/design>
- Forgejo ↔ Gitea shared-protocol confirmation: <https://code.forgejo.org/forgejo/runner/issues/525>