Set up the GitHub App
Every commit cairn makes travels through one GitHub App, the thing your adapter names in
backend: githubApp({ owner, repo, branch, appId, installationId }). You create the App on
GitHub, install it on your content repository, and give your Worker its private key. After that,
saving an entry in /admin writes a real GitHub commit. The commit’s author is the signed-in
editor; the committer is the App.
Before you begin
- A GitHub repository to hold the site’s content, the one your adapter’s
ownerandreponame. - Owner access on the account or organization that owns that repository. Creating and installing a GitHub App both require it.
- An adapter that already declares a
backend, even with placeholder values. Define an adapter and schema builds one from nothing if you don’t have it yet. - A Cloudflare Worker to hold the private key as a secret. You don’t need to deploy it yet.
Create the app
- Go to Settings > Developer settings > GitHub Apps > New GitHub App.
- Give it a name. Every commit the App makes shows this name with a
[bot]suffix as the committer, so pick one an editor would recognize in a commit log, not a placeholder. - Under Repository permissions, set Contents to Read and write. The commit path reads files, lists branches, and writes commits, and nothing else it does needs a broader grant.
- Under Webhook, clear the Active checkbox. Cairn never receives a webhook call, so leaving it active only gives GitHub an endpoint to retry against.
Generate the private key
Every install needs a key the App signs its requests with.
- On the App’s own settings page, scroll to Private keys and choose Generate a private key.
- GitHub downloads a
.pemfile. Its first line reads-----BEGIN RSA PRIVATE KEY-----, which names the format: PKCS#1. Keep this file out of git; you’ll convert it to the one string your Worker needs in Store the private key, below.
Install the app on your repo
- From the App’s settings page, choose Install App.
- Pick the account or organization that owns your content repository, and scope the install to that one repository rather than every repository on the account.
- After installing, open the installation’s settings page and read the number at the end of its
URL. That’s the installation ID; your adapter’s
installationIdtakes it as a string.
Configure the identity in your adapter
Two of the App’s identifiers aren’t secrets at all. appId is the number on the App’s own
settings page, and installationId is the one you just read off the installation’s URL. Both are
compile-time config, not runtime bindings, because the adapter builds its backend at module scope,
before a request and its bindings exist:
import { githubApp } from '@glw907/cairn-cms';
const backend = githubApp({
owner: 'your-org',
repo: 'your-site',
branch: 'main',
appId: '123456',
installationId: '987654',
});
The private key is the one member of this identity that never appears in source. It lives as a Worker secret, read at request time to mint a short-lived installation token.
Store the private key
The Worker needs the .pem from earlier as one base64 string on a single line:
base64 -w0 your-key.pem
Push the result to the Worker as GITHUB_APP_PRIVATE_KEY_B64:
npx wrangler secret put GITHUB_APP_PRIVATE_KEY_B64
That’s the only GitHub App value the Worker’s bindings carry. The CairnPlatformBindings
type names this secret alongside the site’s
other required bindings, so a forgotten one fails app.d.ts at compile time.
You’ll notice a format mismatch. GitHub issued the key as PKCS#1, and Web Crypto’s
importKey('pkcs8', ...) only takes PKCS#8. The engine converts the key in process before every
sign, so you store the PKCS#1 PEM’s base64 exactly as downloaded.
Verify
cairn-doctor runs the same steps a save runs. It parses and signs with the key, exchanges the
signature for an installation token, and uses that token to read your repository.
npx cairn-doctor --repo your-org/your-site
Run it with GITHUB_APP_ID, GITHUB_APP_INSTALLATION_ID, and GITHUB_APP_PRIVATE_KEY_B64 in the
environment. A missing one skips the check rather than failing it, and the skip names what’s
still absent. The doctor reference documents every stage of
this check and what a failure at each stage means.
A green doctor run proves the credentials work. It doesn’t prove that an editor’s save reaches
GitHub the way you expect, so check that too. Sign in to /admin, edit an entry, and save. Open
the resulting commit on GitHub. It carries two identities. GitHub shows the signed-in editor as
the author, by name and email, and the App, with [bot] appended, as the committer. The engine
sends the editor as the commit’s author and omits the committer entirely, so GitHub attributes the
commit to whichever identity signed the request. That’s the App.
What’s next
GitHub App private-key rotation covers rotating this key without
a save-path outage. The security model explains why the App
holds write access at all and what a save is and isn’t allowed to do with it. And the core
reference documents githubApp’s full signature alongside the
rest of the adapter’s construction surface.