# Self-hosted runners

Run [Actions](https://git.cafe/docs/ci/actions.md) jobs on machines you manage: Linux, macOS or
Windows computers, build servers, or hardware-in-the-loop rigs with boards
attached. A self-hosted runner is GitHub's official runner,
[`actions/runner`](https://github.com/actions/runner), unmodified: you
download it from GitHub, configure it against GitCafe instead of github.com,
and run it. GitCafe presents itself to the runner as a GitHub Enterprise
Server.

A runner belongs to an organization (personal accounts too) and serves the
repositories its [runner group](https://git.cafe/docs/ci/self-hosted-runners.md#runner-groups) allows, or it belongs to one
repository. Organization owners and admins manage organization runners;
repository admins manage a repository's runners.

## Add a runner

1. Open your organization's **Settings → Runners** and choose **New runner**
   (or, for a runner that serves one repository, the repository's
   **Settings → CI** and **Add runner**).
2. In the dialog, choose the machine's operating system and architecture.
3. Choose **Create registration token** and run the commands shown on the
   machine, as the user the runner should run as.

On Linux or macOS the commands are:

```sh
mkdir actions-runner && cd actions-runner
curl -o actions-runner-linux-x64-2.338.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.338.0/actions-runner-linux-x64-2.338.0.tar.gz
tar xzf ./actions-runner-linux-x64-2.338.0.tar.gz
./config.sh --url https://git.cafe/acme --token GCR...
./run.sh
```

macOS packages are named `actions-runner-osx-<arch>-<version>.tar.gz`. On
Windows, in PowerShell, download `actions-runner-win-x64-2.338.0.zip`,
extract it and run `./config.cmd` and `./run.cmd` with the same arguments.

`--url` is your organization's address on GitCafe, such as
`https://git.cafe/acme`, or a repository's, such as
`https://git.cafe/acme/firmware`, for a runner that serves only that
repository. `config.sh` asks for the runner's name, group, labels and work
folder; press Enter to accept the defaults.

From the CLI:

```sh
cafe runner token create --org acme --name rig-1   # prints the config.sh command
cafe runner token create --repo acme/firmware      # a repository runner
```

A registration token starts with `GCR` and works for one hour; it can
register any number of runners until then. It is shown once. Scripts that
create tokens with GitHub's REST API ("create a registration token for an
organization" or "for a repository") can do the same against GitCafe: use
`https://git.cafe/api/v3` as the API URL and a GitCafe API token of an
organization or repository admin.

The runner needs HTTPS access to your GitCafe server and, for the actions
your workflows use, to `api.github.com` and `codeload.github.com`. It keeps
the release you installed: GitCafe doesn't update runners.

## Labels

Each runner has these labels, which it reports itself:

- `self-hosted`;
- its operating system: `Linux`, `macOS` or `Windows`;
- its architecture: `X64` or `ARM64`;
- any labels you add with `--labels` when you configure it, or at the
  `config.sh` prompt, such as `--labels gpu,cuda-12`.

A job runs on a runner that has every label in its `runs-on`; letter case
doesn't matter. To change a runner's labels, configure it again with
`./config.sh --url … --token … --replace` and the new `--labels`.

**Settings → Runners** lists each runner with its status (**Idle**, **Busy**
running a job, or **Offline** when it hasn't polled GitCafe for two
minutes), its labels (the ones it added itself, then yours), group,
architecture, version and when it was last seen.

## Runner groups

Groups decide which repositories may use an organization's runners. Every
organization has a **Default** group that all its repositories may use; new
runners join it unless you choose another group with `--runnergroup NAME`
(or at the prompt).

In **Settings → Runners → Runner groups**, choose **New group** to create one
for all repositories or only selected ones, and a group's edit (pencil)
button to rename it or change its repositories. You can delete a group that has no runners;
**Default** can be neither renamed nor deleted.

```sh
cafe runner groups create --org acme --name "HIL rigs" --repos firmware,drivers
cafe runner groups update "HIL rigs" --org acme --visibility all
cafe runner groups list --org acme
cafe runner groups delete "HIL rigs" --org acme --yes
```

To move a runner to another group, configure it again with `--replace` and
`--runnergroup`. A repository's **Settings → CI** lists the groups and
runners it can use.

## Hardware-in-the-loop rigs

Give each rig labels that say what is attached to it, and target them in
`runs-on`:

```sh
./config.sh --url https://git.cafe/acme --token GCR... --name rig-stm32 --labels hil,stm32 --runnergroup "HIL rigs"
```

```yaml
jobs:
  flash-and-test:
    runs-on: [self-hosted, hil, stm32]
    concurrency: hil-stm32
    steps:
      - uses: actions/checkout@v4
      - run: ./flash.sh && ./hil-test.sh
```

A runner takes one job at a time. Use a `concurrency` group per rig when
several workflows target it, so a queued run waits instead of interleaving
with another, and a runner group to keep the rig to the repositories that
should use it.

## Run the runner as a service

`./run.sh` runs in the foreground until you stop it. To start the runner at
boot, install it as a service after configuring it:

- **Linux** (systemd): `sudo ./svc.sh install`, then `sudo ./svc.sh start`.
  `sudo ./svc.sh status` and `sudo ./svc.sh stop` show and stop it.
- **macOS** (launchd): `./svc.sh install`, then `./svc.sh start`, as the user
  the runner runs as; the service starts when that user logs in.
- **Windows**: answer **Y** when `config.cmd` asks whether to run the runner
  as a service, or pass `--runasservice`.

## Ephemeral and just-in-time runners

`./config.sh … --ephemeral` registers a runner that takes one job and then
unregisters itself, for machines you reset between jobs.

A just-in-time (JIT) runner is created by an API call instead of
`config.sh`, for scripts that start a fresh machine per job:

```sh
curl -X POST https://git.cafe/api/v3/orgs/acme/actions/runners/generate-jitconfig \
  -H "Authorization: Bearer $GITCAFE_TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "vm-42", "runner_group_id": 1, "labels": ["hil"]}'
```

The answer's `encoded_jit_config` starts the runner without configuring it:
`./run.sh --jitconfig <encoded_jit_config>`. It is a credential: pass it to
the machine privately. The runner is ephemeral. `runner_group_id` is the
group's number, which `cafe runner groups list` shows. For a repository
runner, use the repository's `generate-jitconfig` endpoint, as on GitHub. The
API token must belong to an admin of the organization or repository.

## Remove a runner

Choose the remove (trash) button next to the runner in **Settings → Runners**
(or the repository's **Settings → CI**), or:

```sh
cafe runner list --org acme
cafe runner remove <runner-id> --org acme --yes
```

Its credential stops working at once and a job it is running fails. Then
delete the runner folder on the machine, after `sudo ./svc.sh uninstall` (or
`./svc.sh uninstall` on macOS) if it runs as a service.

You can also remove a runner from the machine itself:
`./config.sh remove --token <token>` removes it from GitCafe and deletes its
configuration. Get the remove token from GitHub's REST API ("create a remove
token for an organization" or "for a repository") at
`https://git.cafe/api/v3`, with a GitCafe API token of an admin.

## Move a runner from GitHub

One runner installation is registered with one server: it can't serve
GitHub and GitCafe at the same time. To move a machine:

1. On the machine, stop the runner (`sudo ./svc.sh stop` and
   `sudo ./svc.sh uninstall` if it runs as a service).
2. Remove it from GitHub: `./config.sh remove --token <remove token>`, with a
   remove token from the runner's settings page on GitHub.
3. Configure the same installation against GitCafe with a GitCafe
   registration token, keeping its name and labels:
   `./config.sh --url https://git.cafe/acme --token GCR... --labels <same labels>`.
4. Start it again, or install the service again.

Your workflows' `runs-on` keeps working when the runner keeps its labels. To
try GitCafe without touching a GitHub runner, install a second copy of the
runner in another folder.

## Troubleshooting

- **The job waits for a runner.** Check the runner is **Idle** and has every
  label in the job's `runs-on`, and that its group lets the repository use it
  (the repository's **Settings → CI** lists what it can use).
- **`config.sh` fails with "Http response code: NotFound".** The token has
  expired, or `--url` names another organization or repository than the
  token: create a new token from the same page.
- **The runner shows Offline.** It hasn't polled GitCafe for two minutes:
  check it's running and can reach your GitCafe server.
- **Logs.** The runner writes its own logs to the `_diag` folder in its
  installation directory: `Runner_*.log` for registration and polling,
  `Worker_*.log` for each job.
