+++
title = "Forgejo"
availability = "v2.18+"
maintainer = "Community"
category = "CI/CD"
description = "Scale applications based on pending jobs on Forgejo repository."
go_file = "forgejo_scaler"
+++

### Trigger Specification

This specification describes the `forgejo-runner` trigger for Forgejo Actions. It scales based on the amount of jobs pending in given runner labels.

```yaml
triggers:
  - type: forgejo-runner
    metadata:
      # Required: the name of the registered runner 
      name: "forgejo-runner-ubuntu"
      # Required: the url of the forgejo instance 
      address: "http://localhost:3000"
      # Optional: Scope of the jobs to check, global is the default one and will get all the jobs in the instance with the defined labels
      global: "true" 
      # Optional: User to set as a scope, global should be false, if no repo is set will get all the pending jobs for the user
      owner: "cobak"
      # Optional: Repository level scope
      repo: "my-repo"
      # Required: Will get the jobs with match with this labels
      labels: "ubuntu-latest"
```

**Parameter list:**

- `name` - Name of the registered runner.
- `address` - Url of the forgejo instance.
- `global` - Scope of the jobs to check. (Values: `true`, `false`, Default: `true`, Optional, Mutually exclusive with `owner` and `repo`)
- `owner` - User to set as a scope. (Optional, Mutually exclusive with `global` and `repo`)
- `repo` - User to set as a scope. (Optional, Mutually exclusive with `global` and `owner`)
- `labels` - Labels to match the job with.


### Authentication Parameters

- `token` - Required token to connect to forgejo instance.

### Options on how to set the scope (global, user, repo)

1. If you set owner and repo KEDA forgejo scaler will retrieve jobs from that repository:
   ```
   owner: "username"
   repo: "my_repo"
   ```
2. If you set the organization will retrieve the organization jobs:
   ```
   org: "my_org"
   ```
3. If you set global: true will retrieve all the jobs from the instance
   ```
   global: "true"
   ```
4. And if you don't set any of this optional fields will retrieve the jobs from the user who creates the token 

## Configuration 

### Registering runners and binding config with autoscalers

To match job endpoints with job execution, we need to pre-register a runner, save the generated JSON file as a ConfigMap, and share it with the autoscaler. review the different ways to register a runner [here](https://forgejo.org/docs/latest/admin/runner-installation/#standard-registration), select the desired scope (user, repository or global) and create the corresponding JSON file. 

Autoscaler definition and runner registration should match to work as expected.

Create a .runner file and a registration file with this commands, and set the output in a configmap.

```shell
docker run -v /var/run/docker.sock:/var/run/docker.sock  -v $PWD:/data --rm code.forgejo.org/forgejo/runner:6.3.1 forgejo-runner register --no-interactive --token xxxx --name runner --instance http://localhost:3000/ --labels ubuntu-20:docker://node:20-bookworm,docker:docker://ghcr.io/catthehacker/ubuntu:act-latest
docker run -v /var/run/docker.sock:/var/run/docker.sock  -v $PWD:/data --rm code.forgejo.org/forgejo/runner:6.3.1 forgejo-runner generate-config > registration.yaml 
```

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: runner-config
  namespace: runners
data:
  .runner: |
    {
        "WARNING": "This file is automatically generated by act-runner. Do not edit it manually unless you know what you are doing. Removing this file will cause act runner to re-register as a new runner.",
        "id": 3,
        "uuid": "be6b5260-e73f-4e21-af84-8e205dbaaacd",
        "name": "runner",
        "token": "<global-runner-token>",
        "address": "http://localhost:3000/",
        "labels": [
            "ubuntu-20:docker://node:20-bookworm",
            "docker:docker://ghcr.io/catthehacker/ubuntu:act-latest"
        ]
      }

---

apiVersion: v1
kind: ConfigMap
metadata:
  name: runner-registration
  namespace: runners
data:
  registration.yaml: |
    log:
      # The level of logging, can be trace, debug, info, warn, error, fatal
      level: info
      # The level of logging for jobs, can be trace, debug, info, earn, error, fatal
      job_level: info

    runner:
      # Where to store the registration result.
      file: /data/.runner
      # Execute how many tasks concurrently at the same time.
      capacity: 1
      # Extra environment variables to run jobs.
      envs:
        DOCKER_HOST: tcp://localhost:2376
        DOCKER_TLS_VERIFY: 1
        DOCKER_CERT_PATH: /certs/client
      # Extra environment variables to run jobs from a file.
      # It will be ignored if it's empty or the file doesn't exist.
      env_file: .env
      # The timeout for a job to be finished.
      # Please note that the Forgejo instance also has a timeout (3h by default) for the job.
      # So the job could be stopped by the Forgejo instance if it's timeout is shorter than this.
      timeout: 3h
      # The timeout for the runner to wait for running jobs to finish when
      # shutting down because a TERM or INT signal has been received.  Any
      # running jobs that haven't finished after this timeout will be
      # cancelled.
      # If unset or zero the jobs will be cancelled immediately.
      shutdown_timeout: 3h
      # Whether skip verifying the TLS certificate of the instance.
      insecure: false
      # The timeout for fetching the job from the Forgejo instance.
      fetch_timeout: 5s
      # The interval for fetching the job from the Forgejo instance.
      fetch_interval: 2s
      # The interval for reporting the job status and logs to the Forgejo instance.
      report_interval: 1s
      # The labels of a runner are used to determine which jobs the runner can run, and how to run them.
      # Like: ["macos-arm64:host", "ubuntu-latest:docker://node:20-bookworm", "ubuntu-22.04:docker://node:20-bookworm"]
      # If it's empty when registering, it will ask for inputting labels.
      # If it's empty when executing the `daemon`, it will use labels in the `.runner` file.
      labels:
        - ubuntu-20:docker://node:20-bookworm
        - docker:docker://ghcr.io/catthehacker/ubuntu:act-latest

    cache:
      # Enable cache server to use actions/cache.
      enabled: true
      # The directory to store the cache data.
      # If it's empty, the cache data will be stored in $HOME/.cache/actcache.
      dir: ""
      # The host of the cache server.
      # It's not for the address to listen, but the address to connect from job containers.
      # So 0.0.0.0 is a bad choice, leave it empty to detect automatically.
      host: ""
      # The port of the cache server.
      # 0 means to use a random available port.
      port: 0
      # The port of the cache proxy.
      # 0 means to use a random available port.
      proxy_port: 0
      # The external cache server URL. Valid only when enable is true.
      # If it's specified, it will be used to set the ACTIONS_CACHE_URL environment variable. The URL should generally end with "/".
      # Otherwise it will be set to the the URL of the internal cache server.
      external_server: ""
      # The shared cache secret. When communicating with a cache server, the runner uses this secret to verify the authenticity of the cache requests.
      # When using an external cache server it is required to set the same secret for the runner and the cache server.
      secret: ""
      # Overrides the ACTIONS_CACHE_URL passed to workflow containers. This should only be used if the runner host is not reachable from the
      # workflow containers, and requires further setup.
      actions_cache_url_override: ""

    container:
      # Specifies the network to which the container will connect.
      # Could be host, bridge or the name of a custom network.
      # If it's empty, create a network automatically.
      network: host
      # Whether to create networks with IPv6 enabled. Requires the Docker daemon to be set up accordingly.
      # Only takes effect if "network" is set to "".
      enable_ipv6: false
      # Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker).
      privileged: false
      # And other options to be used when the container is started (eg, --add-host=my.forgejo.url:host-gateway).
      options: -v /certs/client:/certs/client
      # The parent directory of a job's working directory.
      # If it's empty, /workspace will be used.
      workdir_parent:
      # Volumes (including bind mounts) can be mounted to containers. Glob syntax is supported, see https://github.com/gobwas/glob
      # You can specify multiple volumes. If the sequence is empty, no volumes can be mounted.
      # For example, if you only allow containers to mount the `data` volume and all the json files in `/src`, you should change the config to:
      # valid_volumes:
      #   - data
      #   - /src/*.json
      # If you want to allow any volume, please use the following configuration:
      # valid_volumes:
      #   - '**'
      valid_volumes:
        - /certs/client
      # overrides the docker client host with the specified one.
      # If "-" or "", an available docker host will automatically be found.
      # If "automount", an available docker host will automatically be found and mounted in the job container (e.g. /var/run/docker.sock).
      # Otherwise the specified docker host will be used and an error will be returned if it doesn't work.
      docker_host: "-"
      # Pull docker image(s) even if already present
      force_pull: false
      # Rebuild local docker image(s) even if already present
      force_rebuild: false

    host:
      # The parent directory of a job's working directory.
      # If it's empty, $HOME/.cache/act/ will be used.
      workdir_parent:

```

### Create a token on forgejo

The autoscaler will continuously fetch new waiting jobs from Forgejo, so it requires a properly configured token with the appropriate permissions. Navigate to **Forgejo > User settings > Applications > Access Tokens** and generate a new token. This token will be used by the autoscaler

### Configuring KEDA Autoscaler

Create a new custom resource with the required parameters and custom fields based on the scope chosen during runner registration. For example, set `global="true"` for global runners or specify `repo=xxx` and `owner=xxx` for repository level runners.

Choose the runner image you want to use (>6.1 of forgejo runner releases) or a custom one with `--one-job` command. 

Apply the new custom resource to your cluster.

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: forgejo-runner-secret
  namespace: runners
data:
  token: "BEARER_TOKEN"
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
  name: forgejo-runner-creds
  namespace: runners
spec:
  secretTargetRef:
    - parameter: token
      name: forgejo-runner-secret
      key: token
---
apiVersion: keda.sh/v1alpha1
kind: ScaledJob
metadata:
  labels:
    app: forgejo-runner
  name: upload-platform-forgejo-runner
  namespace: runners
spec:
  jobTargetRef:
    template:
      metadata:
        labels:
          app: forgejo-runner
      spec:
        volumes:
          - name: runner-data
            emptyDir: {}
          - name: runner-config
            configMap:
              name: upload-platform-runner-config
          - name: runner-registration
            configMap:
              name: upload-platform-runner-registration
          - name: docker-certs
            emptyDir: {}
        restartPolicy: Never
        shareProcessNamespace: true
        containers:
          - name: runner
            image: code.forgejo.org/forgejo/runner:6.3.1
            command:
              - sh
              - -c
              - |
                while ! nc -z localhost 2376 </dev/null; do 
                  echo 'waiting for docker daemon...'
                  sleep 5
                done

                cp /config/.runner /data/.runner

                exec forgejo-runner -c /registration/registration.yaml one-job
            securityContext:
              privileged: true
              runAsUser: 0
            env:
              - name: DOCKER_HOST
                value: tcp://localhost:2376
              - name: DOCKER_CERT_PATH
                value: /certs/client
              - name: DOCKER_TLS_VERIFY
                value: "1"
            volumeMounts:
              - name: docker-certs
                mountPath: /certs
              - name: runner-data
                mountPath: /data
              - name: runner-registration
                mountPath: /registration
                readOnly: false
              - name: runner-config
                mountPath: /config
                readOnly: false
          - name: daemon
            image: docker:27.4.1-dind
            command:
              - sh
              - -c
              - |
                dockerd-entrypoint.sh 2>&1 &

                while kill -0 7 2>/dev/null
                  do sleep 10 
                  echo "watching main job execution"
                done;

                sleep 10

                echo "main container exited, stopping dind"
                exit 0
            env:
              - name: DOCKER_TLS_CERTDIR
                value: /certs
              - name: DOCKER_HOST
                value: tcp://0.0.0.0:2376
            securityContext:
              privileged: true
              runAsUser: 0
            volumeMounts:
              - name: docker-certs
                mountPath: /certs
  minReplicaCount: 0
  maxReplicaCount: 20
  pollingInterval: 30
  triggers:
    - type: forgejo-runner
      metadata:
        name: "runner"
        address: "http://localhost:3000/"
        global: "true"
        labels: "ubuntu-20,docker"
      authenticationRef:
        name: forgejo-runner-creds
```
