Skip to main content

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

ComponentFileRole
Proto definitionssrc/main/proto/{ping,runner}/v1/*.protoMIT-licensed copies of gitea.com/gitea/actions-proto-def, with java_package/java_multiple_files options added.
Generated messagesde.workaround.ci.proto.* (build output)Protobuf message classes generated by protobuf-maven-plugin at generate-sources.
Connect endpointci/ConnectRunnerResource.javaJAX-RS resource serving the Connect unary RPCs under /api/actions.
Registration/presenceci/RunnerRegistrationService.javaToken issue, runner register/declare/authenticate, list/delete.
Entitiesmodel/CiRunner.java, model/CiRunnerRegistrationToken.javaPersisted state (migration V19).
Admin UIci/AdminRunnerResource.java + templates/AdminRunnerResource/Token generation, runner list, deletion.
Admin gateaccount/AdminAccess.javaConfig-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