github-app-forge
⚠ STATUS: shelved (2026-04-27).
The GitHub App Manifest flow this tool wraps has too much schema-validator friction (rejects
nullfor optional Option fields, then rejects "url wasn't supplied" when present-but-form-encoded, with no clean signal from GitHub about which) for the savings to be worth it. The canonical path for first-pass GitHub App registration is now manual UI (~5 min, once a year per app) — see the runbook in any pleme-io cluster'sclusters/<cluster>/infrastructure/arc/RUNBOOK.md.This repo is preserved for two reasons:
- The Rust scaffolding (manifest serde types + JWT signer + CredentialSink trait + composite-action structure) is reusable if/when GitHub improves the Manifest API or a similar flow lands elsewhere.
- If rotation cadence ever justifies revisiting (e.g. monthly App key rotation across N clusters), the
rotatecommand is already authored.Do not depend on this tool for new pleme-io workflows. Use a pre-existing org PAT (e.g. an
_CI_GITHUB_TOKENorg secret) for ARC runner registration until/unless the situation changes.
Declarative GitHub App lifecycle management for pleme-io. Replaces ~10 minutes of UI form-filling per app with a typed YAML manifest and one browser confirmation.
Why
GitHub doesn't expose a pure REST API for creating GitHub Apps (anti-supply-chain — one app can't silently provision another). The only programmatic path is the Manifest flow, which still requires exactly one browser click on a confirmation page. Everything before and after that click — manifest authoring, credential capture, sink writing, repo installation, rotation — IS automatable.
github-app-forge collapses the manual steps to:
- Author a typed YAML manifest (one-time per app)
- Run
github-app-forge create manifests/<app>.yaml - Click "Create from manifest" in the browser tab that opens
- Click "Install" in the second tab that opens (with target repos pre-selected)
- Tool captures App ID, private key, installation ID + writes them to the configured sink (SOPS, file, future: Akeyless)
Everything else — repo additions later, credential rotation, value-stub generation for HelmReleases — is fully API-driven via the App's own JWT.
Install
Or via home-manager:
imports = [ inputs.github-app-forge.homeManagerModules.default ];
programs.github-app-forge = {
enable = true;
package = inputs.github-app-forge.packages.${pkgs.system}.default;
};
Usage
# Validate a manifest without network calls
# Create the app + install + write credentials
# Add a repo to an existing installation (fully API-driven)
# Emit a HelmRelease values stub
Sinks
| Kind | Use case |
|---|---|
stdout (default) |
Dry-run / debugging |
file |
Plaintext YAML — testing only |
sops |
Canonical sink for pleme-io GitOps clusters: renders K8s Secret YAML matching pleme-arc-controller's expected shape, runs sops --encrypt --in-place |
akeyless |
Stub — future Akeyless integration via akeyless-nix |
Manifest schema
See src/manifest.rs for the typed Rust source of truth. The YAML mirrors GitHub's Manifest API schema for the fields under github:, plus github-app-forge-specific outer keys (name, install_target, owner, install_repos, sink).
Security
- Private keys never leave the local machine without going through the configured sink
- SOPS sink runs
sops --encrypt --in-placeso the unencrypted PEM is written to disk only momentarily before encryption (atomic rename via sops) - App JWT generation happens locally; tokens are never persisted
- Installation tokens issued for repo-add operations are short-lived (15min) and not stored
License
MIT