mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 03:38:23 +00:00
bf25eb2fc7removed the sync-main job because the fork had no token that may push .github/workflows/**: GITHUB_TOKEN cannot be granted the workflows permission, and the bot App is installed on infinito-nexus/core only. Since then a fork's main only moves when its owner syncs it by hand, and the fork's main has fallen 368 commits behind. The job is back with a token of its own: CI_SYNC_MAIN_TOKEN, a fine-grained personal access token limited to the fork, with Contents and Workflows write. main_from_repository.sh resolves the source through baseline_remote.sh, so the normalization and the off switch exist once: in infinito-nexus/core itself and with CI_SYNC_MAIN_SOURCE_REPOSITORY disabled the job ends green without reading the secret. Everywhere else a missing secret fails the job before any fetch or push and names the page that explains how to create it. The token reaches git as an HTTP header through GIT_CONFIG_* variables, never through argv or .git/config; checkout runs without persisted credentials so its own header does not compete. The job runs beside run-policy, not before it: CI keeps fetching its diff baseline from the source repository read-only, so a failed sync turns the run red without holding CI back. fix/ci-mirror-token tried the bot App instead (019fb572a7, 756b180f1d). That needs the App installed on every fork and granted Workflows write, which secrets.md rules out for the App, so the token here is a per-fork PAT. Checked: the script skips as infinito-nexus/core and with the sync disabled, and fails before fetching as a fork without the secret. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
125 lines
9.9 KiB
Markdown
125 lines
9.9 KiB
Markdown
# CI Secrets 🔐
|
|
|
|
This page lists every repository secret that GitHub Actions workflows in this repository consume, and documents the failure mode when a secret is missing. For repository **variables** see [configuration.md](configuration.md). For the workflow catalog see [workflows.md](workflows.md).
|
|
|
|
Repository secrets MUST be set under **Settings → Secrets and variables → Actions → Secrets**.
|
|
|
|
## Secrets 📋
|
|
|
|
| Secret | Workflow | Purpose | Required when |
|
|
|---|---|---|---|
|
|
| `BOT_APP_CLIENT_ID` | [cron-update.yml](../../../../../.github/workflows/cron-update.yml) | GitHub App Client ID (OAuth-style `Iv…` identifier shown on the App's General page) used to mint a short-lived installation token for the update PR. | `CI_ENABLE_AUTO_UPDATES == 'true'` |
|
|
| `BOT_APP_PRIVATE_KEY` | [cron-update.yml](../../../../../.github/workflows/cron-update.yml) | PEM-encoded private key of the same GitHub App. Used to sign the JWT that exchanges for the installation token. | `CI_ENABLE_AUTO_UPDATES == 'true'` |
|
|
| `API_OPENAI_KEY` | [call-test-deploy.yml](../../../../../.github/workflows/call-test-deploy.yml) | Publishes the `openai/*` models on the LiteLLM gateway and lets the CLI test exercise the route. | Never. Unset leaves the route unpublished. |
|
|
| `API_ANTHROPIC_KEY` | [call-test-deploy.yml](../../../../../.github/workflows/call-test-deploy.yml) | Publishes the `anthropic/*` models. | Never. |
|
|
| `API_OPENROUTER_KEY` | [call-test-deploy.yml](../../../../../.github/workflows/call-test-deploy.yml) | Publishes `openrouter/auto`. | Never. |
|
|
| `DOCKERHUB_USERNAME` | [cron-images-mirror-all.yml](../../../../../.github/workflows/cron-images-mirror-all.yml), [call-images-mirror-missing.yml](../../../../../.github/workflows/call-images-mirror-missing.yml) | Authenticates the mirror job against Docker Hub, which raises the anonymous pull-rate limit the mirror would otherwise hit. | Never. Both workflows declare the secret `required: false` and gate the login step on both values being non-empty. |
|
|
| `DOCKERHUB_TOKEN` | [cron-images-mirror-all.yml](../../../../../.github/workflows/cron-images-mirror-all.yml), [call-images-mirror-missing.yml](../../../../../.github/workflows/call-images-mirror-missing.yml) | The access token paired with `DOCKERHUB_USERNAME`. | Never, and only together with the username. |
|
|
| `CI_SYNC_MAIN_TOKEN` | [entry-push-latest.yml](../../../../../.github/workflows/entry-push-latest.yml) | Fine-grained token the `🔄 Sync main` job pushes the source repository's `main` with. | In every repository whose `CI_SYNC_MAIN_SOURCE_REPOSITORY` names another repository, which a fork does by default. |
|
|
|
|
## `CI_SYNC_MAIN_TOKEN` 🔄
|
|
|
|
### How to create the token 🛠️
|
|
|
|
1. Open **Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token** on the account that owns the fork.
|
|
2. Set **Resource owner** to that account and **Repository access** to **Only select repositories**, selecting the fork alone.
|
|
3. Grant these repository permissions and nothing else:
|
|
- `Contents`: **Read and write** (force-push of `main`).
|
|
- `Workflows`: **Read and write** (the pushed `main` carries `.github/workflows/**`).
|
|
- `Metadata`: **Read** (default, cannot be removed).
|
|
4. Choose an expiration date and note it; an expired token fails the job like a missing one.
|
|
5. In the fork, add it under **Settings → Secrets and variables → Actions → Secrets → New repository secret** as `CI_SYNC_MAIN_TOKEN`.
|
|
|
|
### Failure mode when the secret is missing 💥
|
|
|
|
| Repository | Behaviour |
|
|
|---|---|
|
|
| `infinito-nexus/core`, or a repository with the sync disabled through `CI_SYNC_MAIN_SOURCE_REPOSITORY` | The job prints that the sync is skipped and succeeds. The secret is not read. |
|
|
| Any other repository | [main_from_repository.sh](../../../../../scripts/github/sync/main_from_repository.sh) fails before any fetch or push and names the secret. The run is marked failed; the other jobs of the run are unaffected. |
|
|
|
|
## `API_OPENAI_KEY`, `API_ANTHROPIC_KEY`, `API_OPENROUTER_KEY` 🤖
|
|
|
|
These are optional and fail soft: unset means the provider's route is never
|
|
published, and the deployment falls back to whatever local backend the round
|
|
deploys. The workflow reads each as `${{ secrets.X || vars.X }}`, so the same
|
|
name works as a repository variable; see
|
|
[configuration.md](configuration.md#provider-keys-variable-or-secret-) for why
|
|
the secret is the better half of that pair.
|
|
|
|
Set them the same way as the App credentials: **Settings → Secrets and variables
|
|
→ Actions → Secrets → New repository secret**, one per provider, the value being
|
|
the provider's key verbatim.
|
|
|
|
The shape each key must have is declared per role rather than here, in
|
|
[roles/svc-ai-litellm/meta/secrets.yml](../../../../../roles/svc-ai-litellm/meta/secrets.yml)
|
|
with a `type:` and a `regex:`. The pattern rejects a truncated or
|
|
whitespace-carrying paste and deliberately accepts any vendor prefix, so a
|
|
provider changing its issuance format cannot turn a valid key into a failed
|
|
inventory run.
|
|
|
|
## `BOT_APP_CLIENT_ID` and `BOT_APP_PRIVATE_KEY` 🤖
|
|
|
|
### Why these secrets exist 🎯
|
|
|
|
Actions performed with the workflow-provided `GITHUB_TOKEN` do not start further workflow runs. When [cron-update.yml](../../../../../.github/workflows/cron-update.yml) opens an update PR with `GITHUB_TOKEN`, the `pull_request` and `pull_request_target` events that drive [entry-pr-change-orchestrate.yml](../../../../../.github/workflows/entry-pr-change-orchestrate.yml) do not fire on that PR, so the CI orchestrator never runs against it.
|
|
|
|
Opening the PR through a GitHub App installation token bypasses this restriction. The App counts as a distinct actor, so PR-lifecycle events fire normally and the full CI pipeline runs against every update PR.
|
|
|
|
### Why a GitHub App and not a PAT 🏷️
|
|
|
|
A GitHub App installation token SHOULD be used because:
|
|
|
|
- The token lifetime is bounded to one hour, and a new token is minted on every workflow run.
|
|
- Permissions are scoped at the App installation level, independent of any user account.
|
|
- No personal account footprint appears in audit logs.
|
|
|
|
A classic personal access token MAY be used as a fallback for unrelated automation, but it MUST NOT be used here because user-bound credentials expand the blast radius and require manual rotation.
|
|
|
|
### How to generate the App 🛠️
|
|
|
|
1. Create a new GitHub App under **Organization settings → Developer settings → GitHub Apps → New GitHub App**.
|
|
2. Disable the webhook (uncheck **Active** under **Webhook**). The App MUST NOT receive events.
|
|
3. Grant the following repository permissions and nothing else:
|
|
- `Contents`: **Read and write** (the update script force-pushes to `update/*` branches).
|
|
- `Pull requests`: **Read and write** (`gh pr create` / `gh pr edit`).
|
|
- `Metadata`: **Read** (default, cannot be removed).
|
|
4. Restrict installation to the owning account only.
|
|
5. Generate a private key and download the PEM file.
|
|
6. Install the App on the `core` repository only. The App MUST NOT be installed on unrelated repositories.
|
|
|
|
### How to hand the credentials to CI 📥
|
|
|
|
1. Open the repository on GitHub.
|
|
2. Go to **Settings → Secrets and variables → Actions**.
|
|
3. Stay on the **Secrets** tab.
|
|
4. Click **New repository secret** and add `BOT_APP_CLIENT_ID` with the App's Client ID (shown on the App's General page under **About**, in the `Iv…` format — *not* the numeric App ID).
|
|
5. Click **New repository secret** and add `BOT_APP_PRIVATE_KEY` with the complete PEM content, including the `-----BEGIN PRIVATE KEY-----` and `-----END PRIVATE KEY-----` lines.
|
|
|
|
### How the workflow consumes the secrets ⚙️
|
|
|
|
Each PR-opening job in [cron-update.yml](../../../../../.github/workflows/cron-update.yml) runs an `actions/create-github-app-token@v2` step gated on `steps.diff.outputs.changed == 'true'`, then forwards the minted token as `GH_TOKEN` and the resolved `APP_SLUG` to the subsequent PR step. The job's `permissions:` block keeps the workflow-provided `GITHUB_TOKEN` read-only; write access is provided exclusively by the App token, scoped to the App's repository permissions.
|
|
|
|
### Commit identity propagation 🪪
|
|
|
|
[pr.sh](../../../../../scripts/github/update/open/pr.sh) reads `APP_SLUG` from the environment and resolves the App's bot user via `gh api /users/<slug>[bot]`. The resolved login and numeric user ID are written into `git config user.name` and `git config user.email` in the `<id>+<slug>[bot]@users.noreply.github.com` form that GitHub accepts for verified commits. When `APP_SLUG` is unset, the script falls back to the `github-actions[bot]` identity. That branch is reached only when the script is invoked outside the workflow, for example a local smoke test where the caller has set `GH_TOKEN` to something other than an App token.
|
|
|
|
### Failure mode when secrets are missing 💥
|
|
|
|
A missing secret resolves to an empty string in workflow expressions. The workflow run starts and proceeds normally until the token-minting step:
|
|
|
|
| Stage | Behaviour when both secrets are missing |
|
|
|---|---|
|
|
| Workflow parse | Run starts. GitHub emits the warning `The following secrets are referenced but not defined: BOT_APP_CLIENT_ID, BOT_APP_PRIVATE_KEY`. |
|
|
| `Generate app token` step | `actions/create-github-app-token` declares both inputs as required and fails with `Error: Input required and not supplied: client-id`. |
|
|
| `Open PR if … changed` step | Skipped. Its implicit `if:` condition is `success() && (steps.diff.outputs.changed == 'true')`, and `success()` is false after the token step failed. |
|
|
| `pr.sh` | Not executed. No commit, no push, no PR. |
|
|
| Outcome | The run is marked failed. GitHub notifies repository admins. No PR is created with a wrong identity. |
|
|
|
|
The behaviour is fail-loud by design. Missing or mistyped secret names MUST surface as a failed workflow run rather than as a PR with an unintended author.
|
|
|
|
## Related 🔗
|
|
|
|
- [configuration.md](configuration.md): repository variables that gate the same workflows.
|
|
- [workflows.md](workflows.md): full workflow catalog.
|
|
- [pipeline.md](../../../artefact/git/pipeline.md): how the orchestrator routes PR runs.
|