Skip to content

Automate GitHub App and runner-group bootstrap with Bash #27

Description

@Nickfost

Problem

A fleet bootstrap can require an operator or automation agent to manually assemble a GitHub App manifest flow, copy a temporary conversion code between applications, exchange it, store the PEM, discover identifiers, install the App, and configure runner-group access.

An operator or automation agent should execute a reviewed script. It should not reconstruct security-sensitive GitHub setup from prose.

Goal

Provide a repository-owned Bash bootstrap command such as:

sudo ./scripts/bootstrap-github.sh \
  --organization example-org \
  --instance example-ci-01 \
  --runner-group example-ci-experimental \
  --allow-repository example-org/example-repo

The script should automate everything possible and pause only for GitHub's unavoidable organization-owner browser approvals.

All committed examples must use fictional organization, repository, host, group, App, network, and credential values.

Required flow

  1. Validate dependencies, destination permissions, organization, instance identity, and requested repository allowlist.
  2. Generate a cryptographically random, single-use state.
  3. Start a short-lived callback bound safely to loopback or an explicitly configured trusted interface.
  4. Generate and serve the GitHub App manifest registration page.
  5. Open the registration page when possible, or print one non-secret local URL for an operator or agent.
  6. Verify the returned state.
  7. Exchange the temporary manifest code directly without printing or transporting it through chat.
  8. Write the PEM directly to the approved host-local path with root ownership and mode 0600.
  9. Record only the required non-secret Client/App ID and installation ID in the host configuration.
  10. Direct the operator or agent through the unavoidable App-installation approval and poll until installation is visible.
  11. Create or validate the runner group and selected-repository access through the GitHub API.
  12. Fail if a public repository, unexpected organization, broader repository access, or default/broad runner group would be used.
  13. Destroy temporary callback state, conversion responses, and other sensitive temporary files.
  14. Produce a redacted final report.
  15. Hand off directly to the idempotent host installer.

Security requirements

  • Never print, log, echo, return, or commit the manifest conversion code, PEM, client secret, webhook secret, JWT, or installation token.
  • Never pass credentials in command-line arguments.
  • Use secure temporary files, restrictive umask, traps, and cleanup on success, failure, interruption, and timeout.
  • Verify exact redirect state and enforce a short timeout.
  • Bind callback services to the narrowest practical interface.
  • Require an explicit repository allowlist; never silently select all repositories.
  • Reject public repositories for privileged runner groups.
  • Require confirmation before changing or replacing an existing App or runner group.
  • Make each host identity independently revocable.
  • Do not depend on chat, clipboard, email, or issue comments for credential transport.
  • Pin or verify external tools and API behavior used by the script.
  • Support a dry-run/check mode that performs no GitHub mutations.
  • Keep real organization names, repository names, hosts, addresses, and internal infrastructure out of public examples and test fixtures.

Operational requirements

  • Linux/Bash only.
  • Safe to rerun after partial completion.
  • Detect and reuse the intended existing App/group only after verifying ownership, permissions, and identity.
  • Resume cleanly after browser approval delays.
  • Clearly distinguish non-secret operator URLs from secret callback/query values.
  • Provide rollback guidance for an accidentally created App or runner group.
  • Work when driven by a human or an automation agent.

Integration

The host installer from #21 should:

  • detect whether valid host-local GitHub credentials and runner-group prerequisites already exist;
  • optionally invoke this bootstrap through an explicit flag; or
  • stop with the exact bootstrap command required.

The generic host installer must remain usable with externally provisioned credentials and must not require GitHub bootstrap on every run.

Acceptance

A busy operator delegates the process to an agent, performs only GitHub's required authenticated approval clicks, and receives a fully configured host without copying any credential, PEM, or temporary code between applications.

The public issue, documentation, tests, and examples demonstrate the process exclusively with fictional values such as example-org/example-repo.

Target-host and phone-first requirement

The bootstrap must run on the target VM or bare-metal Linux Docker host. OpenClaw or another agent is optional automation, not a required intermediary.

From a phone and terminal, an operator should be able to:

  1. run the bootstrap command on the target host;
  2. open one phone-friendly local registration URL;
  3. approve creation and installation on GitHub;
  4. return to the terminal while the target host completes code exchange, PEM storage, installation detection, runner-group configuration, controller installation, and verification.

Any callback server must be temporary, bound only to an explicitly selected LAN/private interface, protected by random state and a short timeout, and removed after completion. The temporary code and resulting credentials must remain entirely on the target host. The workflow must not require copying values through chat, clipboard, email, an issue, an agent, or a second computer.

Where GitHub requires more than one authenticated approval, the target-host page should guide the operator through the smallest possible sequence of safe buttons/links. A QR code may be offered for convenience but must encode only a non-secret local registration URL.

The same command must support safe reruns after success or partial failure.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions