Audience: the SAP GitHub org admin who owns
https://github.com/sap-tutorials, and thetutorials-imsrepo maintainer who will set the Actions secrets and flip the activation variable. This doc is intentionally short and copy-pasteable.
For the why behind this change, see github-app-migration.md. The short version: replace a long-lived classic PAT (which expires under SAP rotation policy and is tied to one human) with a GitHub App that mints a fresh 1-hour token per workflow run.
- Today:
tutorials-imsrunsnpm run fetch-tutorialsin CI using theTUTORIALS_GITHUB_TOKENsecret (a classic PAT). When the PAT expires, the build breaks until someone with access regenerates and re-pastes it. - After: the workflow asks GitHub for a short-lived installation token at the start of each run. Nothing to rotate.
The workflow change is already merged (see .github/workflows/rebuild-content.yml):
- A
Generate GitHub App tokenstep gated on the repo variableUSE_GITHUB_APP == 'true'. - The
Fetch tutorialsstep pickssteps.app-token.outputs.token || secrets.TUTORIALS_GITHUB_TOKEN.
So:
- Until the org admin completes setup → the variable stays unset → step is skipped → PAT continues to be used.
- Once secrets + variable are in place → App token is used automatically.
- To roll back at any time → set the variable to anything other than
true(or delete it).
No code changes required in scripts/parsers/github.ts. Installation tokens are standard Bearer tokens.
These run once. Estimated time: 15 minutes.
- Go to https://github.com/organizations/sap-tutorials/settings/apps/new
- Fill in:
- GitHub App name:
sap-tutorials-builder - Homepage URL:
https://github.com/sap-tutorials/tutorials-ims(or wherever the repo lives) - Webhook → Active: uncheck (we don't consume events)
- Repository permissions:
Contents: Read-only (Phase 3 note: bump to Read and write only if you migrateTUTORIALS_POC_DISPATCH_TOKEN— needed forrepository_dispatch)Metadata: Read-only (auto-selected)Actions: Read and write (runtimeworkflow_dispatchfrom the CAP app — Phase 2)- everything else: No access
- Organization permissions: all No access
- Account permissions: all No access
- Where can this GitHub App be installed? Only on this account
- GitHub App name:
- Click Create GitHub App.
On the App's settings page, scroll to Private keys → Generate a private key. A .pem file downloads automatically. Save this file — you'll paste its contents into a repo secret in Part 2. The key cannot be recovered later; if lost, generate a new one and rotate.
At the top of the App's settings page: App ID: XXXXXXX. Copy this number.
- On the App settings page, left sidebar → Install App.
- Click Install next to
sap-tutorials. - Choose either:
- All repositories (simpler; covers public tutorials + private
*-Contributionrepos automatically as new ones are added), or - Only select repositories → pick every public tutorial repo plus every
*-Contributionprivate repo.
- All repositories (simpler; covers public tutorials + private
- Click Install.
- After installation, the URL contains
/installations/<INSTALLATION_ID>. Copy this number.
Send to whoever owns tutorials-ims Actions secrets:
- App ID (the number from 1.3)
- The full contents of the
.pemfile from 1.2 — paste between-----BEGIN RSA PRIVATE KEY-----and-----END RSA PRIVATE KEY-----inclusive - Installation ID (the number from 1.4)
Use a secure channel (e.g. SAP password vault, encrypted email, in-person). Treat the private key like any other credential — anyone with it can mint tokens with the App's permissions.
These also run once, after Part 1 is complete. Estimated time: 5 minutes.
Settings → Secrets and variables → Actions → New repository secret. Add:
| Name | Value |
|---|---|
TUTORIALS_APP_ID |
App ID from 1.3 |
TUTORIALS_APP_PRIVATE_KEY |
Full .pem contents from 1.2 (including BEGIN/END lines) |
TUTORIALS_APP_INSTALLATION_ID |
Installation ID from 1.4 (optional — see note) |
The
actions/create-github-app-token@v1action can resolve the installation ID fromapp-id + ownerautomatically. The current workflow does not passinstallation-id, so this secret is optional. Add it only if you later want to pin a specific installation.
Same page, Variables tab → New repository variable:
| Name | Value |
|---|---|
USE_GITHUB_APP |
true |
This is a non-secret variable on purpose — it's a feature flag, not a credential, and being non-secret means it shows up clearly in workflow logs.
- Go to Actions → Rebuild Content → Run workflow.
- Choose
dev, leaveslugempty. - Watch the run. You should see:
Generate GitHub App tokenstep: ✅ runs (was previously skipped)Fetch tutorialsstep: ✅ uses the App token (no observable difference in output)
- If the run succeeds end-to-end, the migration is complete.
Once you've confirmed at least one scheduled or dispatch-triggered run works without manual help:
- Delete the
TUTORIALS_GITHUB_TOKENrepo secret. - Revoke the underlying classic PAT in the source account's developer settings (so it can't be used elsewhere by accident).
Keep the
TUTORIALS_GITHUB_TOKENsecret for at least one full successful run cycle before deleting. It's the rollback path if the App turns out to have a permission gap.
The CAP app (tutorials-srv) mints its own installation token via
srv/lib/github-app-token.js for the rebuild dispatcher and the two fetch
crons. This needs the App secrets in the BTP Credential Store (not just
GitHub Actions) plus a runtime flag.
At /admin-ui/#secrets-display on the target env's approuter, set values for
the three registry rows (seeded by scripts/seed-secrets.cjs):
| Alias | Value |
|---|---|
TUTORIALS_APP_ID |
App ID from 1.3 |
TUTORIALS_APP_INSTALLATION_ID |
Installation ID from 1.4 |
TUTORIALS_APP_PRIVATE_KEY |
Full .pem contents from 1.2 (multi-line, incl. BEGIN/END) |
cf set-env tutorials-srv USE_GITHUB_APP true && cf restart tutorials-srvVerify: trigger an admin save (fires a debounced workflow_dispatch) and
confirm rebuild-content ran; check srv logs for [github-app-token] warnings
(none expected on success). Roll back with cf set-env tutorials-srv USE_GITHUB_APP false && cf restart tutorials-srv — the PAT path resumes.
Each *-Contribution repo fires repository_dispatch at tutorials-ims via
notify-qa.yml. To migrate off TUTORIALS_POC_DISPATCH_TOKEN:
- Ensure the App is installed on the Contribution repo and holds
Contents: write on
tutorials-ims(dispatch permission). - Add
TUTORIALS_APP_ID+TUTORIALS_APP_PRIVATE_KEYActions secrets to the Contribution repo. - Set repo variable
USE_GITHUB_APP=trueon the Contribution repo. - Push a tutorial change; confirm a
tutorial-qa-updateddispatch reachestutorials-ims. - After a clean run, delete
TUTORIALS_POC_DISPATCH_TOKENfrom that repo.
Roll out per-repo; each is independent.
If anything misbehaves after activation:
- Set
USE_GITHUB_APPrepo variable tofalse(or delete it). - Re-add
TUTORIALS_GITHUB_TOKENsecret if it was already deleted. - Re-run the workflow. It falls back to the PAT path automatically.
No code change needed.
| Symptom | Likely cause | Fix |
|---|---|---|
Resource not accessible by integration on a *-Contribution repo |
App not installed on that repo | Re-run install (1.4), pick "All repositories" or add the missing repo to the selected list |
Bad credentials on GraphQL |
Private key pasted incorrectly (missing BEGIN/END lines, line endings mangled) | Re-paste the .pem exactly as downloaded; GitHub's secret editor preserves newlines correctly when pasted whole |
app-token step is skipped even after activation |
USE_GITHUB_APP variable name typo, or value is not exactly true |
The check is vars.USE_GITHUB_APP == 'true' — must be lowercase string |
| Rate limit errors after migration | App installation rate limit (5000 req/hr) shared with other workflows | Unlikely with current build cadence; check if other workflows now use the same install |
github-app-migration.md— engineering rationale, current state analysis, comparison tableactions/create-github-app-token— the GitHub-published action used in the workflow- GitHub Apps documentation