GitHub App Adoption

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

  1. Navigate to the GitHub App settings page
  2. Click Install and select the target organization
  3. Choose All repositories or select specific repos
  4. 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 authentication
  • arc-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, and tinyland-dind in arc-runners
  • Optional additive shared capability labels from extra_runner_sets, such as the current heavy Nix lane tinyland-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-nix lane is still an 8Gi memory-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 runnerScaleSetName for registration identity
  • runner scale set names must be unique within a runner group
  • secondary owner overlays on the same cluster can use owner-distinct runnerScaleSetName values while exposing shared workflow labels through scaleSetLabels
  • 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, and tinyland-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

  1. Install GloriousFlywheel on the target GitHub account/org (Settings → Applications → Install).

  2. 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
  3. 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 as tinyland-nix-heavy or tinyland-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-heavy pods, but it will not resize tinyland-nix itself 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 token with API rate limit exceeded for user ID ..., the fix is app auth or install scope, not more runner capacity
  4. Preflight — from the overlay checkout, verify credentials, registration and workflow status before any state change:

    export GF_CORE_PATH=../GloriousFlywheel-infra-overlays
    just enrollment-preflight

    A 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’s next actions output as the enrollment queue for the owner overlay.

  5. Apply — after preflight blockers are resolved and the plan has no unexpected destroys, the new scale set appears in arc-runners alongside the existing ones:

    ENV=dev just tofu-plan arc-runners
    ENV=dev just tofu-apply arc-runners
  6. 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

GloriousFlywheel