Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cmd/fireactions/printables.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type printablePool struct {
}

func (p *printablePool) Cols() []string {
return []string{"Name", "Current", "Desired", "Organization", "Group ID", "Labels", "Image", "State"}
return []string{"Name", "Current", "Desired", "Organization", "Repository", "Group ID", "Labels", "Image", "State"}
}

func (p *printablePool) ColsMap() map[string]string {
Expand All @@ -23,6 +23,7 @@ func (p *printablePool) ColsMap() map[string]string {
"Current": "Current",
"Desired": "Desired",
"Organization": "Organization",
"Repository": "Repository",
"Group ID": "Group ID",
"Labels": "Labels",
"Image": "Image",
Expand All @@ -42,6 +43,7 @@ func (p *printablePool) KV() []map[string]interface{} {
"Current": pool.CurrentReplicas,
"Desired": pool.DesiredReplicas,
"Organization": pool.Organization,
"Repository": pool.Repository,
"Group ID": pool.GroupId,
"Labels": strings.Join(pool.Labels, ", "),
"Image": pool.Image,
Expand Down
28 changes: 25 additions & 3 deletions docs/help/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,12 @@ command that can remove all runners that are currently offline.
Requirements:

- The tools `jq`, `xargs` and the GitHub CLI
- The GitHub CLI is authenticated as Organization Administrator for the affected Fireactions Pool.
- The GitHub CLI is authenticated with admin access to wherever the affected pool registers its runners:
- **Organization scoped pools** (`runner.organization`): as an Organization Administrator of that organization.
- **Repository scoped pools** (`runner.repository`): as a user with admin access to that repository. Organization
Administrator access doesn't apply here, and isn't available at all for repositories owned by a personal account.

⚠️ **Warning**: This command will permanently delete runners. Ensure your organization depends on fireactions and that
⚠️ **Warning**: This command will permanently delete runners. Ensure the account depends on fireactions and that
you have tested this in a non-production environment first. To preview which runners will be deleted without actually
deleting them please omit the `xargs` step in the shell commands.

Expand All @@ -109,8 +112,27 @@ To do the same with fish shell, execute the following:

```fish
set -x GH_PAGER
set -x ORG <ORG>
set -x ORG "<ORG>"
gh api --paginate /orgs/$ORG/actions/runners | jq '.runners[] | select(.status=="offline") | .id' | xargs -I {} gh api --method DELETE /orgs/$ORG/actions/runners/{}
```

Please replace `<ORG>` with the name of your GitHub Account or Organization.

For repository scoped pools (including pools of personal accounts), the runners live on the repository instead, so use
the repository endpoint. With bash:

```bash
export GH_PAGER=
export REPO="<OWNER>/<REPOSITORY>"
gh api --paginate /repos/$REPO/actions/runners | jq '.runners[] | select(.status=="offline") | .id' | xargs -I {} gh api --method DELETE /repos/$REPO/actions/runners/{}
Comment thread
kholisrag marked this conversation as resolved.
```

With fish shell:

```fish
set -x GH_PAGER
set -x REPO "<OWNER>/<REPOSITORY>"
gh api --paginate /repos/$REPO/actions/runners | jq '.runners[] | select(.status=="offline") | .id' | xargs -I {} gh api --method DELETE /repos/$REPO/actions/runners/{}
Comment thread
kholisrag marked this conversation as resolved.
```

Please replace `<OWNER>/<REPOSITORY>` with the repository the pool registers its runners with.
29 changes: 25 additions & 4 deletions docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ metrics:
address: 127.0.0.1:8081

#
# GitHub configuration.
# GitHub configuration. See the GitHub App guide for the permissions the App
# needs: ../user-guide/github-app.md
#
github:
#
Expand Down Expand Up @@ -89,15 +90,35 @@ pools:
#
# GitHub runner group ID. 1 is the default group.
#
# Required: true
# Required: true when `organization` is set. Runner groups don't exist for
# personal accounts, so this is optional (and defaults to 1) when
# `repository` is set.
group_id: 1
#
# Organization name.
# Organization name. Runners are registered with the organization and are
# available to all of its repositories.
#
# Required: true
# Exactly one of `organization` or `repository` is required.
#
organization: hostinger
#
# Repository, in <owner>/<repository> format. Runners are registered with
# this single repository. This is the only option for personal (user)
# accounts, which can't have organization runners.
#
# Exactly one of `organization` or `repository` is required.
#
# repository: octocat/hello-world
#
# GitHub App installation ID. By default Fireactions looks the installation
# up from the configured organization or repository, which requires no
# extra configuration. Set this to skip the lookup, e.g. when the App can't
# read the installation itself.
#
# Required: false, Default: 0 (look up automatically)
#
# installation_id: 12345678
#
# Labels to apply to the GitHub runner.
#
# Required: true
Expand Down
37 changes: 37 additions & 0 deletions docs/user-guide/concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,40 @@ pools:
```

This will create a pool named `example` with 5 replicas for the GitHub runners. The runners will have the labels `self-hosted` and `fireactions`, and will use the specified Firecracker configuration.

## Runner scope

A pool registers its runners either with an organization or with a single repository. Exactly one of `runner.organization` or `runner.repository` must be set.

### Organization scoped

```yaml
runner:
organization: hostinger
group_id: 1
```

Runners are registered with the organization and can be used by any of its repositories, subject to the runner group's repository access settings. `group_id` is required, `1` is the default runner group.

### Repository scoped

```yaml
runner:
repository: octocat/hello-world
```

Runners are registered with a single repository. This is the only option for repositories owned by a personal (user) account, since GitHub doesn't offer organization runners or runner groups outside of organizations. `group_id` is optional and defaults to `1`.

Repository scoped pools work for organization owned repositories too, which is useful when you want a pool dedicated to one repository.

### GitHub App permissions

The two scopes use different GitHub APIs and therefore need different GitHub App permissions:

| Runner scope | Permission |
|--------------------|-----------------------------------------------------|
| Organization | Organization `Self-hosted runners`: Read and write |
| Repository | Repository `Administration`: Read and write |

See the [GitHub App](github-app.md) guide for how to create and install the App, and for the `runner.installation_id`
option.
4 changes: 2 additions & 2 deletions docs/user-guide/first-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ After installing and configuring Fireactions, verify your setup by running a tes

## Verify Runners Are Registered

Check your GitHub organization's Actions settings to confirm runners are registered:
Check your GitHub Actions settings to confirm runners are registered:

1. Navigate to your GitHub organization settings
1. Navigate to your GitHub organization settings, or, for repository scoped pools, to your repository settings
2. Go to **Actions** → **Runners**
3. Verify that runners are listed as **Idle** and ready to receive jobs

Expand Down
147 changes: 147 additions & 0 deletions docs/user-guide/github-app.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# GitHub App

Fireactions authenticates with GitHub as a [GitHub App](https://docs.github.com/en/apps/creating-github-apps). The App is
the only credential Fireactions needs: it creates just-in-time (JIT) runner registrations before starting a Firecracker
VM, and deletes the runner from GitHub after the VM exits.

The App is configured in the `github` section of the configuration file:

```yaml
github:
app_id: 12345
app_private_key: |
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
```

Which permissions the App needs depends on the [runner scope](concepts.md#runner-scope) of your pools.

## Permissions

### Organization scoped pools

For pools that set `runner.organization`, grant the following **organization permission**:

| Permission | Access |
|---------------------|------------------|
| Self-hosted runners | Read and write |

This is the permission that covers `POST /orgs/{org}/actions/runners/generate-jitconfig` and
`DELETE /orgs/{org}/actions/runners/{runner_id}`. The organization `Administration` permission does **not** grant access
to these endpoints, so granting it instead won't work.

No repository permissions are needed: runners are registered with the organization itself, not with any of its
repositories.

### Repository scoped pools

For pools that set `runner.repository`, grant the following **repository permissions**:

| Permission | Access |
|----------------|-----------------------------------|
| Administration | Read and write |
| Metadata | Read-only (granted automatically) |

`Administration` covers `POST /repos/{owner}/{repo}/actions/runners/generate-jitconfig` and
`DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}`. GitHub grants `Metadata: Read-only` automatically as soon as
any repository permission is selected.

Repository scoped pools are the only option for personal (user) accounts, which can't have organization runners. They
work for organization owned repositories too.

!!! note
A single App can serve both kinds of pools. Grant the organization `Self-hosted runners` permission and the
repository `Administration` permission if you run organization scoped and repository scoped pools side by side.

### Not required

Fireactions does not need any of the following, and you can leave them unset:

- **Webhooks and webhook events.** Fireactions keeps each pool at its configured number of replicas on its own and does
not consume `workflow_job` or any other event, so the App needs no webhook URL and no event subscriptions.
- **The `Actions` permission.** Fireactions never reads or writes workflow runs, jobs or artifacts.
- **Account (user) permissions.**

## Creating and installing the App

The App can be owned by an organization or by a personal account. Ownership only decides who administers the App; what
matters for Fireactions is the account the App is *installed* on.

1. Create the App:
- **Organization owned:** `https://github.com/organizations/<ORG>/settings/apps/new`
- **Personal account owned:** `https://github.com/settings/apps/new`
2. Set a name and a homepage URL. Uncheck **Active** under **Webhook** — Fireactions doesn't use webhooks.
3. Under **Permissions**, grant the permissions for your runner scope from the tables above.
4. Create the App, then note the **App ID** and generate a **private key**. The downloaded `.pem` file is what goes into
`github.app_private_key`.
5. Install the App:
- **On an organization** (for organization scoped pools): **Install App** → choose the organization.
- **On a personal account** (for repository scoped pools): **Install App** → choose your account, then
**Only select repositories** and pick the repositories your pools register runners with.

!!! warning
Changing an App's permissions after installation requires accepting the new permissions on the installation before
they take effect. If Fireactions starts failing with `403` errors after a permission change, check the installation
settings page for a pending request.

## Installation ID

Fireactions needs the ID of the App installation on the account owning the runners. It looks the ID up automatically
using the App's own JWT — `GET /orgs/{org}/installation` for organization scoped pools and
`GET /repos/{owner}/{repo}/installation` for repository scoped pools. Neither call needs an installation permission, so
in most setups nothing else is required.

Set `runner.installation_id` to skip the lookup:

```yaml
runner:
repository: octocat/hello-world
installation_id: 12345678
```

You can find the ID in the URL of the installation's settings page:

- Organization: **Settings** → **GitHub Apps** → **Configure**, the URL ends in
`/organizations/<ORG>/settings/installations/<INSTALLATION_ID>`
- Personal account: **Settings** → **Applications** → **Configure**, the URL ends in
`/settings/installations/<INSTALLATION_ID>`

Or via the API. This endpoint only accepts a JWT signed with the App's private key — a normal `gh auth login`
token won't work, so mint the JWT first and pass it explicitly:

```bash
APP_ID=<APP_ID>
KEY=<PATH_TO_PRIVATE_KEY.pem>

header=$(printf '{"alg":"RS256","typ":"JWT"}' | openssl base64 -A | tr '+/' '-_' | tr -d '=')
now=$(date +%s)
payload=$(printf '{"iat":%d,"exp":%d,"iss":"%s"}' "$((now - 60))" "$((now + 540))" "$APP_ID" \
| openssl base64 -A | tr '+/' '-_' | tr -d '=')
sig=$(printf '%s.%s' "$header" "$payload" \
| openssl dgst -sha256 -sign "$KEY" -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')
JWT="$header.$payload.$sig"

curl -sS -H "Authorization: Bearer $JWT" -H "Accept: application/vnd.github+json" \
https://api.github.com/repos/<OWNER>/<REPOSITORY>/installation | jq .id
```

The same JWT works with `gh` if you'd rather use it, since `-H` overrides the token it would otherwise send:

```bash
gh api /repos/<OWNER>/<REPOSITORY>/installation -H "Authorization: Bearer $JWT" --jq .id
```

## API calls Fireactions makes

| Endpoint | Authenticated as | Permission |
|-------------------------------------------------------------|------------------|-------------------------------------------|
| `GET /orgs/{org}/installation` | App (JWT) | None |
| `GET /repos/{owner}/{repo}/installation` | App (JWT) | None |
| `POST /orgs/{org}/actions/runners/generate-jitconfig` | Installation | Organization `Self-hosted runners: write` |
| `DELETE /orgs/{org}/actions/runners/{runner_id}` | Installation | Organization `Self-hosted runners: write` |
| `POST /repos/{owner}/{repo}/actions/runners/generate-jitconfig` | Installation | Repository `Administration: write` |
| `DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}` | Installation | Repository `Administration: write` |

The organization endpoints are only used by pools with `runner.organization` set, the repository endpoints only by pools
with `runner.repository` set.
16 changes: 13 additions & 3 deletions docs/user-guide/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ Before you begin, ensure you have:
- **KVM virtualization support** - Firecracker requires hardware virtualization
- **GitHub App credentials**:
- App ID
- App must be installed on your target organization
- Private key (PEM format)
- See [Creating GitHub Apps](https://docs.github.com/en/apps/creating-github-apps) for setup instructions
- App must be installed on your target organization or personal account
- App needs the organization `Self-hosted runners: Read and write` permission for organization scoped pools, or the repository `Administration: Read and write` permission for repository scoped pools
- See the [GitHub App](github-app.md) guide for setup instructions
- **Dedicated block device** for Containerd storage (e.g., `/dev/nvme1n1`, `/dev/sdb`)
- This will be used exclusively for container image storage via LVM
- Minimum 50GB recommended, though this depends on your image sizes
Expand Down Expand Up @@ -412,7 +413,16 @@ pools:
image: ghcr.io/hostinger/fireactions-images/ubuntu22.04:latest
image_pull_policy: IfNotPresent # or Always to pull on every run
group_id: 1 # Runner group ID in GitHub (1 = default)
organization: YOUR_GITHUB_ORGANIZATION # or use 'repository: owner/repo'
# Set EXACTLY ONE of `organization` or `repository`. Setting both is
# rejected at startup; to switch scope, delete the line you're not using.
#
# Organization scoped - runners are shared by the whole organization:
organization: YOUR_GITHUB_ORGANIZATION
#
# Repository scoped - the only option for personal accounts. To use this,
# delete the `organization` line above and uncomment the line below
# (`group_id` is optional here and defaults to 1):
# repository: YOUR_GITHUB_USERNAME/YOUR_REPOSITORY
labels:
- self-hosted
- fireactions
Expand Down
2 changes: 2 additions & 0 deletions docs/user-guide/metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ The following metrics are available, excluding the default Prometheus metrics:
| `fireactions_scale_duration_seconds` | Histogram | Time taken to complete a scale operation | `pool`, `organization`, `direction` |


The `organization` label holds the GitHub account that owns the pool's runners: the organization name for organization scoped pools, or the repository owner (which may be a personal account) for repository scoped pools.

Example Grafana dashboard for vizualisation of Fireactions metrics:

![Grafana Dashboard](../img/grafana-dashboard.png)
4 changes: 2 additions & 2 deletions docs/user-guide/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ Fireactions is still in the early stages of development, we are waiting for feed
There are a few requirements to run Fireactions:

- Linux machine with KVM support. We recommend using a machine with at least 2 CPU cores and 4GB of RAM.
- GitHub organisation account (currently only organisation accounts are supported)
- GitHub App with permissions to manage self-hosted runners. See the [installation guide](installation.md) for setup details.
- GitHub organisation or personal account. Organisation accounts can register runners with the organisation or with a single repository, personal accounts with a single repository. See [runner scope](concepts.md#runner-scope).
- GitHub App with permissions to manage self-hosted runners, installed on your organization or personal account. See the [GitHub App guide](github-app.md) for the required permissions and setup details.
- [Containerd v1.7.0 or newer](https://github.com/containerd/containerd)
- [Firecracker v1.4.1 or newer](https://github.com/firecracker-microvm/firecracker)
- [CNI Plugins v1.6.0 or newer](https://github.com/containernetworking/plugins) (with `firewall` and `bridge` plugins)
Expand Down
Loading
Loading