gitshark

Clone repository

git clone https://gitshark.ha1nz.de/git/workaround/Gitshark.git
git clone ssh://git@gitshark.ha1nz.de:22/workaround/Gitshark.git

← Issues

CI/CD: implement Forgejo/Gitea runner protocol (runner.v1) so stock runners work against git-shark #2

Summary

As an admin, I can set up CI/CD runners in git-shark: register runner instances, and have workflows in repositories executed on push, with live logs and results visible in the UI.

This is an epic. Strategy decision up front: do not write our own runner. Reuse the existing Forgejo Runner / Gitea act_runner by implementing their server-side protocol in git-shark. Stock runner binaries then work against git-shark unchanged.

Why the Forgejo/Gitea runner path (research summary)

  • Wire protocol is small and open. Runners talk Connect RPC (buf) over plain HTTP — no extra port, reuses the normal HTTP listener. One service runner.v1.RunnerService with 5 methods (Register, Declare, FetchTask, UpdateTask, UpdateLog) plus a PingService health check. Proto definitions are MIT-licensed (gitea.com/gitea/actions-proto-def) — we can generate Java stubs directly from the .proto files. Forgejo confirmed it still shares Gitea's proto contract (forgejo/runner#525), so both forgejo-runner and act_runner are compatible clients.
  • Workflows are GitHub-Actions-compatible YAML in .forgejo/workflows/ / .gitea/workflows/ — familiar format, existing ecosystem of actions.
  • GitLab Runner (fallback) is strictly worse for us: its server side must fully compile .gitlab-ci.yml (including include, extends, rules, YAML anchors, !reference, DAG, matrix) into pre-resolved job rows, and several endpoints (trace PATCH, status PUT) are only documented in the runner's source, not as a stable public contract.
  • Writing our own containerized runner: worst case only; not needed given the above.
  • The real cost is server-side, not the protocol: the runner intentionally receives a pre-resolved single-job payload and does no trigger/DAG/matrix work itself. Gitea's server does this with the Go act library; there is no Java port, so git-shark must implement workflow parsing, trigger evaluation, needs resolution and matrix expansion itself. This is the main implementation gap and the reason for phasing.

Architecture (target state)

push → detect .forgejo/workflows/*.yml at new head
     → evaluate `on:` triggers → create run + job rows (DAG, matrix-expanded)
     → runner long-polls FetchTask → gets Task { expanded single-job YAML,
       github context, secrets, vars, needs-outputs }
     → runner executes steps locally (its bundled act engine)
     → UpdateLog streams log rows (offset + ack_index resume protocol)
     → UpdateTask reports step/task state → run UI updates

Key protocol facts to honor:

  • FetchTask carries tasks_version so idle polling is cheap (no DB scan when nothing changed).
  • UpdateLog: runner sends rows from index, server acks durable offset (ack_index); reconnect-tolerant.
  • UpdateTask: step states carry log_index/log_length pointers into the log stream — server maps steps to log ranges via these, no log parsing.
  • Secrets/variables are delivered inline in FetchTaskResponse (plaintext over TLS — same trust model as GitHub self-hosted runners).

Phases

Phase 1 — MVP: runner registration + basic run loop

  • Connect RPC endpoint in Quarkus serving runner.v1 (generate Java stubs from the MIT-licensed protos; Connect protocol with binary protobuf codec over HTTP).
  • New tables: runner (uuid, token hash, name, labels, status, last_seen), action_run, action_task (job), action_log (or log blob storage), plus registration tokens.
  • Admin UI: generate registration token, list runners with status (idle/active/offline from Declare/FetchTask heartbeats), delete runner. Instance-scope only for v1 (repo/org scopes later).
  • Trigger: on: push only. Single-job workflows, no needs, no matrix. Parse workflow YAML from .forgejo/workflows/ at the pushed commit.
  • Run UI per repository: run list, run detail with per-step status + logs.
  • Task state machine incl. timeout/zombie handling (runner disappears mid-task → fail after deadline).

Phase 2 — real workflow semantics

  • Trigger evaluation: branch/tag/path filters, more events (tag push, merge-request events mapped to pull_request).
  • needs DAG resolution + passing TaskNeed outputs; matrix expansion (one Task per cell).
  • Secrets & variables: repo-level CRUD UI (owner-only), delivered via Task.Secrets/Task.Vars.
  • Concurrency/cancellation: cancel run from UI, re-run, cancel superseded runs on force-push.
  • Label-based task-to-runner matching.

Phase 3 — ecosystem compatibility

  • Artifacts: implement the GitHub Actions "Results" artifact service (Twirp, github.actions.results.api.v1.ArtifactServiceCreateArtifact/FinalizeArtifact/ListArtifacts/…) so actions/upload-artifact@v4 works; expose via ACTIONS_RESULTS_URL. Artifact download from run UI.
  • Cache: none server-side initially — act_runner ships its own cache server (ACTIONS_CACHE_URL, runner-local or shared external_server); document that in admin docs. (Gitea's server-side cache v2 is itself still an open proposal, gitea#33393.)
  • Repo/org-scoped runners; ephemeral runners (--ephemeral, server must enforce single-job-then-delete).
  • Commit/MR status integration (run result shown on commits and merge requests).

Out of scope

  • Writing our own runner binary.
  • GitLab runner compatibility.
  • Windows/macOS runner concerns (runner's problem, not ours).
  • Full GitHub Actions parity (reusable workflows, environments, deployments, OIDC token issuance).

Security notes

  • Registration tokens and runner secrets: store hashed, admin-only UI.
  • Secrets go to any runner that picks up the task — document clearly that runner hosts are trusted infrastructure; no fork-PR workflows with secrets in v1 (skip secret delivery for tasks triggered by non-writers when MR triggers arrive in Phase 2).
  • Workflow YAML is untrusted input: hard limits on size, job count, matrix size, log volume per task.

Acceptance criteria (Phase 1 = definition of done for this issue; 2/3 split into follow-ups)

  • [ ] Admin can create a registration token in the admin UI; stock forgejo-runner register against git-shark succeeds.
  • [ ] Registered runners appear in admin UI with status; Declare updates version/labels.
  • [ ] Push with .forgejo/workflows/*.yml (on: push, single job) creates a run; runner fetches, executes, streams logs; UI shows live per-step status and logs; final state (success/failure) correct.
  • [ ] Log resume after runner reconnect works (ack_index honored).
  • [ ] Task fails cleanly when runner vanishes (timeout).
  • [ ] Failing tests first: protocol round-trip against real forgejo-runner in an integration test (container), plus unit tests for trigger eval and task state machine.
  • [ ] Docs: docs/admins/ (runner setup, endpoints, new tables, reverse-proxy notes for Connect RPC), docs/users/ (writing workflows), docs/maintainers/ (protocol implementation decisions, phase gap list), root README.md feature list.

References

  • Proto: https://gitea.com/gitea/actions-proto-def (MIT), Go stubs incl. Connect handlers: https://gitea.com/gitea/actions-proto-go
  • 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
  • Forgejo runner registration flows: https://forgejo.org/docs/latest/admin/actions/registration/
  • Prior art (client-side protocol reimplementation): https://github.com/ChristopherHX/gitea-actions-runner

Migrated from https://github.com/workaround-org/git-shark/issues/13 Migrated from https://gitshark.ha1nz.de/repos/miggi/GitShark/issues/2 (original author: miggi, created 2026-07-09)

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog