GitHub App Adoption
GloriousFlywheel ARC uses a GitHub App to authenticate with GitHub. This guide explains how to install the app on your organization and configure runner access.
What is the GitHub App for?
The GloriousFlywheel ARC integration uses a GitHub App (ID 2953466) to enable
self-hosted GitHub Actions runners. It listens for workflow_job webhook
events and scales runner pods on demand.
Implementation Overlay Boundary
GitHub App credentials are owner-specific deployment facts. Keep them in the implementation overlay for the owner or tenant being enrolled, not in the core GloriousFlywheel product stack.
Examples:
- a future Tinyland organization overlay can install the app on
tinyland-inc - a future Jess personal-account overlay can install the app for personal repos and point at the same backend substrate when isolation is explicit
- another operator can use the same core modules with their own app install, tfvars, backend config, and cache namespace choices
The overlay may carry private githubConfigUrl values and secret names. The
workflow-facing contract still uses shared capability labels.
Install on a New Organization
- Navigate to the GitHub App settings page
- Click Install and select the target organization
- Choose All repositories or select specific repos
- Approve the installation
Required Permissions
| Permission | Scope | Access |
|---|---|---|
| Self-hosted runners | Organization | Read & Write |
| Metadata | Repository | Read-only |
| Actions | Repository | Read-only |
These permissions are sufficient for ARC runner registration and
workflow_job-driven scale sets.
Private Consumer And Input Proof Permission
The tranche and GF REAPI Cell proof workflows can also use the same GitHub App
secret pair to check out private consumer repositories or mint
repository-scoped tokens for private external inputs. Those proof workflows
should still run on shared GloriousFlywheel runner lanes; the GitHub App token
path is separate from ARC runner registration. If an operator sets
require_consumer_app_token=true, the App must also grant repository
Contents: Read-only on the target installation and the installation update
must be approved for that organization. Private input handoffs such as
tinyland_schemas_private_handoff=true require the same permission on the
input repository.
Without that permission, actions/create-github-app-token fails while minting
the repository-scoped checkout or input token with:
The permissions requested are not granted to this installation.
Classify that as GitHub App permission drift, not as Bazel, target-class, or REAPI evidence. For consumer checkout, it is checkout-authority debt. For a private archive/distdir handoff, it is external-input authority debt. Fix the App permission and organization installation approval, then rerun the hosted proof dispatch.
If the permission update cannot be completed immediately, TIN-1127 allows a
proof-only alternate checkout authority. Set
consumer_checkout_authority=repo-scoped-deploy-key or
consumer_checkout_authority=owner-scoped-secret instead of
require_consumer_app_token=true. For deploy keys, configure only the fixed
per-repo secret needed for the consumer repo:
GF_REAPI_CONSUMER_CHECKOUT_SSH_KEY_TINYLAND_DEV or
GF_REAPI_CONSUMER_CHECKOUT_SSH_KEY_MASSAGEITHACA. For token checkout,
configure only the fixed owner secret needed for the consumer repo:
GF_REAPI_CONSUMER_CHECKOUT_TOKEN_TINYLAND_INC or
GF_REAPI_CONSUMER_CHECKOUT_TOKEN_JESSSULLIVAN. The value must be a
repository-scoped read credential. Do not use a broad PAT, do not pass tokens
through workflow inputs, and do not count a successful checkout as RBE evidence.
Webhook Events
The app subscribes to workflow_job events. These trigger runner
pod creation when a job matches a self-hosted runner label.
Kubernetes Secret Setup
ARC authenticates using a Kubernetes secret containing the GitHub App credentials. This secret must exist in both namespaces:
arc-systems— used by the ARC controller for API authenticationarc-runners— used by runner scale sets for registration
Use the helper so the secret shape stays consistent:
for ns in arc-systems arc-runners; do
scripts/rotate-arc-auth.sh app \
--secret-name github-app-secret \
--namespace "$ns" \
--app-id 2953466 \
--installation-id <INSTALLATION_ID> \
--pem-file <PATH_TO_PEM_FILE>
done
The helper writes Kubernetes secrets only. It does not create GitHub Actions
repository secrets such as GF_CORE_DEPLOY_KEY or GF_CORE_READ_TOKEN.
Runner Group Configuration
The default runner group must allow public repositories if you want self-hosted runners available to public repos:
gh api -X PATCH /orgs/<ORG>/actions/runner-groups/1 \
-f allows_public_repositories=true
Without this, workflows in public repos will fail to match self-hosted runner labels.
Deploy the ARC Stack
export KUBECONFIG=~/.kube/kubeconfig-honey.yaml
export KUBE_CONTEXT=honey
cp tofu/stacks/arc-runners/terraform.tfvars.example tofu/stacks/arc-runners/dev.tfvars
ENV=dev just tofu-preflight arc-runners
ENV=dev just tofu-init arc-runners
ENV=dev just tofu-plan arc-runners
ENV=dev just tofu-apply arc-runners
This deploys:
- ARC controller in
arc-systems - Shared GitHub Actions labels
tinyland-nix,tinyland-docker, andtinyland-dindinarc-runners - Optional additive shared capability labels from
extra_runner_sets, such as the current heavy Nix lanetinyland-nix-heavy
For the current Tinyland rollout, honey is the only physical cluster target.
The Civo cluster has been decommissioned (April 2026).
Composite Actions
GloriousFlywheel provides composite actions that auto-configure cache endpoints on self-hosted runners:
| Action | Description |
|---|---|
setup-flywheel |
Detect runner environment and configure cache/runtime hints |
nix-job |
Bootstrap Nix explicitly and run a Nix job with cache hints |
docker-job |
Standard CI job with Bazel cache |
Usage
jobs:
build:
runs-on: tinyland-nix
steps:
- uses: actions/checkout@v6
- uses: tinyland-inc/GloriousFlywheel/.github/actions/nix-job@main
env:
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN || '' }}
with:
attic-public-key: ${{ vars.ATTIC_PUBLIC_KEY || '' }}
command: nix build .#default
push-cache: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && secrets.ATTIC_TOKEN != '' && 'true' || 'false' }}
setup-flywheel configures cache/runtime hints on self-hosted runners and
attaches the Attic substituter to NIX_CONFIG when the public key is present.
It does not install Nix by itself. On runners that already provide Nix, it
repairs the daemon/build-user runtime before later raw nix commands. Use
nix-job or run DeterminateSystems/determinate-nix-action@v3 first when the
runner image does not already include Nix.
Adoption examples should keep pull requests read-only and only enable Attic
publication on trusted default-branch pushes when ATTIC_TOKEN is present.
Repository proof workflows and large publication surfaces remain separately
guarded until a named write path is restored with evidence.
Current scaling boundary:
- ARC can scale the number of runner pods up and down
- ARC does not automatically increase the CPU or memory limit of one runner pod
- the baseline
tinyland-nixlane is still an8Gimemory-limit runner
Workflow Examples
Simple Nix Build
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: tinyland-nix
steps:
- uses: actions/checkout@v6
- uses: DeterminateSystems/determinate-nix-action@v3
- uses: tinyland-inc/GloriousFlywheel/.github/actions/setup-flywheel@main
- run: nix build
Docker Build
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: tinyland-docker
steps:
- uses: actions/checkout@v6
- run: make test
build-image:
runs-on: tinyland-dind
needs: test
steps:
- uses: actions/checkout@v6
- run: docker build -t myapp .
Shared Capability Classes
How githubConfigUrl Scoping Works
| URL pattern | Scope |
|---|---|
https://github.com/ORG |
All repos in the org |
https://github.com/OWNER/REPO |
Single repository only |
Important GitHub boundary:
- GitHub self-hosted runners are exposed at repository, organization, or enterprise scope
- ARC still requires a unique
runnerScaleSetNamefor registration identity - runner scale set names must be unique within a runner group
- secondary owner overlays on the same cluster can use owner-distinct
runnerScaleSetNamevalues while exposing shared workflow labels throughscaleSetLabels - there is no personal-account-wide runner-group surface equivalent to an org runner group
- a Jess-owned repo URL can still be a required compatibility control-plane anchor today, but that does not prove pooled shared-lane authority and does not justify repo-shaped workload labels
- even when repo-scoped ARC plumbing is required, the workflow-facing contract
should still be shared capability labels such as
tinyland-nix,tinyland-docker, andtinyland-dind - if a personal-account repo cannot truthfully reach shared
tinyland-*labels today, keep it blocked or explicit as compatibility debt rather than minting a new repo-shaped lane - moving or mirroring the workload under an enrolled organization or proving an enterprise-scoped runner surface is the clean shared-lane exit; installing another repo-scoped runner set is not
If a personal-account repo URL is unavoidable as an ARC registration anchor, put it in that owner’s implementation overlay. Do not commit it to the core GloriousFlywheel stack, and do not expose the anchor as a product lane name.
Steps
-
Install GloriousFlywheel on the target GitHub account/org (Settings → Applications → Install).
-
Create a K8s secret for the new installation:
for ns in arc-systems arc-runners; do scripts/rotate-arc-auth.sh app \ --secret-name github-app-secret-shared \ --namespace "$ns" \ --app-id 2953466 \ --installation-id <NEW_INSTALLATION_ID> \ --pem-file <PATH_TO_PEM_FILE> done -
Add or adjust only shared capability-class lanes in your tfvars.
By default, GloriousFlywheel should expose shared capability-class lanes such as
tinyland-nix,tinyland-docker,tinyland-dind, and explicitly bounded additive capability lanes such astinyland-nix-heavyortinyland-nix-kvm.If a repo cannot currently reach the shared lane because of GitHub App installation scope or account visibility, treat that as enrollment and control-plane debt to fix. Do not mint a repo-shaped runner label as the normal answer.
Keep additive lanes bounded to a real capability difference:
extra_runner_sets = { tinyland-nix-heavy = { github_config_url = "https://github.com/tinyland-inc" github_config_secret = "github-app-secret" runner_label = "tinyland-nix-heavy" runner_labels = ["self-hosted", "nix", "linux"] runner_type = "nix" max_runners = 2 cpu_limit = "8" memory_limit = "16Gi" } }ARC will scale the number of
tinyland-nix-heavypods, but it will not resizetinyland-nixitself on demand.If the existing GitHub App or secret is scoped too narrowly for the repo you are enrolling, fix the installation scope or owner boundary first instead of adding a repo-shaped fallback lane. For a personal-account repo, that may mean moving or mirroring the workload under an enrolled organization, establishing an enterprise-level runner surface, or leaving the repo explicitly blocked from counted shared-runner authority.
Important boundary:
- prefer GitHub App auth over PAT-backed compatibility bridges
- treat PAT-backed secrets as temporary debt, not the normal control plane
- if listener logs show
failed to get runner registration tokenwithAPI rate limit exceeded for user ID ..., the fix is app auth or install scope, not more runner capacity
-
Preflight — from the overlay checkout, verify credentials, registration and workflow status before any state change:
export GF_CORE_PATH=../GloriousFlywheel-infra-overlays just enrollment-preflightA missing core-read deploy key or token, missing GitHub App secret, absent live
AutoscalingRunnerSet, or queued validation run is enrollment debt. Fix the owner/auth path rather than inventing a new label. Use the tool’snext actionsoutput as the enrollment queue for the owner overlay. -
Apply — after preflight blockers are resolved and the plan has no unexpected destroys, the new scale set appears in
arc-runnersalongside the existing ones:ENV=dev just tofu-plan arc-runners ENV=dev just tofu-apply arc-runners -
Use the label in a workflow:
jobs: build: runs-on: tinyland-nix steps: - uses: actions/checkout@v6 - uses: DeterminateSystems/determinate-nix-action@v3 - uses: tinyland-inc/GloriousFlywheel/.github/actions/setup-flywheel@main - run: nix build
See Also
- GitHub Actions Runners — runner labels, cache integration, architecture
- Downstream Migration Checklist — canonical downstream consumer pattern
- Cross-Forge CI — GitLab CI vs GitHub Actions comparison
- Runner Selection Guide — choosing the right runner type