Bagas Purwa S 776a3fbd7f Merge pull request #4 from bapung/fix/repo-scope-for-user
fix: handle case if owner is a user, take priority over org
2026-01-13 22:15:08 +08:00
2026-01-13 20:47:53 +08:00
2026-01-13 20:47:53 +08:00
2026-01-13 20:47:53 +08:00
2026-01-05 22:10:38 +08:00
2026-01-05 22:10:38 +08:00
2026-01-05 22:10:38 +08:00
2026-01-05 22:10:38 +08:00
2026-01-08 22:29:51 +08:00
2026-01-05 22:10:38 +08:00
2026-01-13 20:47:53 +08:00
2026-01-05 22:10:38 +08:00
2026-01-13 20:47:53 +08:00

Gitea Runner Operator

A Kubernetes Operator to manage ephemeral Gitea Act runners. This operator automatically spawns runner pods based on queued jobs, support global, org/user, repo level runner. Definetely-vibe-coded (don't worry i know what i am doing).

Features

  • Ephemeral Runners: Each job gets a fresh runner which is destroyed after execution.
  • Multiple Scopes: Support for global, org, user, and repo level runners.
  • Auto-Scaling: Automatically scales runners up to a configured maximum based on queued jobs.
  • Label Matching: matches Gitea job labels (e.g., ubuntu-latest) to runner capabilities.

Prerequisites

  • Kubernetes Cluster: v1.23+
  • Gitea: v1.25.0+ (with Actions enabled)

Installation (Helm Chart)

Incoming

Installation (Manual)

1. Deploy the Operator

You can deploy the operator using the provided manifests.

# Clone the repository
git clone https://github.com/bapung/gitea-runner-operator.git
cd gitea-runner-operator

# Install CRDs
make install

# Deploy the controller to the cluster
make deploy IMG=ghcr.io/bapung/gitea-runner-operator:latest

2. Create Credentials Secret

Create a secret containing the Gitea Registration Token and an API Auth Token.

  1. Registration Token: Get this from Gitea Admin -> Actions -> Runners -> Create new Runner (or Org/Repo settings).
  2. Auth Token: Generate a token in Gitea User Settings -> Applications. It needs read:repository, read:user permissions.
apiVersion: v1
kind: Secret
metadata:
  name: gitea-runner-secret
  namespace: gitea-runner-operator-system
type: Opaque
stringData:
  registrationToken: "<YOUR_REGISTRATION_TOKEN>"
  authToken: "<YOUR_API_TOKEN>"

Apply it:

kubectl apply -f secret.yaml

Configuration

The core resource is the RunnerGroup. Below are examples for different scopes.

1. Repository Scope

Spawns runners only for jobs in a specific repository.

apiVersion: gitea.bpg.pw/v1alpha1
kind: RunnerGroup
metadata:
  name: my-repo-runner
  namespace: gitea-runner-operator-system
spec:
  scope: repo
  org: myorg
  repo: myrepo
  giteaURL: https://gitea.example.com
  maxActiveRunners: 5
  labels:
    - "ubuntu-latest"
    - "custom-label"
  registrationToken:
    secretRef:
      name: gitea-runner-secret
      key: registrationToken
  authToken:
    secretRef:
      name: gitea-runner-secret
      key: authToken

2. Organization Scope

Spawns runners for any repository within the organization.

apiVersion: gitea.bpg.pw/v1alpha1
kind: RunnerGroup
metadata:
  name: my-org-runner
  namespace: gitea-runner-operator-system
spec:
  scope: org
  org: myorg
  # repo is omitted
  giteaURL: https://gitea.example.com
  maxActiveRunners: 10
  # ... (tokens)

3. User Scope

Spawns runners for any repository owned by the specified user.

apiVersion: gitea.bpg.pw/v1alpha1
kind: RunnerGroup
metadata:
  name: my-user-runner
  namespace: gitea-runner-operator-system
spec:
  scope: user
  user: myusername
  # org and repo are omitted
  giteaURL: https://gitea.example.com
  maxActiveRunners: 3
  # ... (tokens)

4. Global Scope

Spawns runners for any job in the Gitea instance (Admin level).

apiVersion: gitea.bpg.pw/v1alpha1
kind: RunnerGroup
metadata:
  name: global-runner
  namespace: gitea-runner-operator-system
spec:
  scope: global
  # org, user, and repo are omitted
  giteaURL: https://gitea.example.com
  maxActiveRunners: 20
  # ... (tokens)

How it works

  1. The Controller polls the Gitea API (using the authToken) to check for queued jobs matching the scope and labels.
  2. If a matching queued job is found, and the current active runner count is below maxActiveRunners, the Controller creates a Kubernetes Job.
  3. The Job pod starts an act_runner instance, registers itself using the registrationToken (as ephemeral), picks up the job, executes it, and then terminates.

Troubleshooting

Runners are not starting

  1. Check Controller Logs:

    kubectl logs -n gitea-runner-operator-system -l control-plane=controller-manager -f
    

    Look for errors regarding API authentication or connectivity.

  2. Check Permissions: Ensure the authToken has sufficient permissions (read:repository, etc.) to query actions.

  3. Check Labels: Enable debug logging in the controller to see label matching logic. If your Gitea job requires ubuntu-latest but your RunnerGroup defines centos, it won't match.

Docker Daemon Issues

This is a default rootless Job template from Gitea doc, it has issues with docker daemon. I still can't to get it working with docker command, other container works just fine if you put correct labels. Per Gemini: The default runner image uses dind-rootless. This requires the pod to run with privileged: true. Ensure your cluster policies (PSP/PSA) allow privileged pods in the operator namespace.

Roadmap / Wishlist

  • Helm Chart
  • Custom Runner Job Spec definition
  • Push mode using Webhook trigger

License

This project is licensed under the MIT License - see the LICENSE file for details.

Description
No description provided
Readme MIT 208 KiB
Languages
Go 82.5%
Makefile 15.7%
Dockerfile 1.2%
Shell 0.6%