# Repo bootstrap

> Give a GitHub repo a default-branch ruleset, CODEOWNERS and README templates, Actions variables, and Actions and Dependabot secrets in one step.

This page helps you set up a GitHub repo the way your organization wants it in one step. By the end, the repo
has a ruleset on its default branch, a `CODEOWNERS` file and a README, the Actions variables and secrets its
workflows need, and the Dependabot secrets its Dependabot updates need.

New repos start bare. A **bootstrap policy** describes what every repo should get, and By Dawn applies it when
you **bootstrap** a repo: a project admin does it from the project's [Repos](https://docs.bydawn.com/repos/) page, and a worker can run `saw repo bootstrap <repo>`. Bootstrap never overwrites a file, and running it twice changes
nothing, so it's safe to run on a repo you've already set up.

## What a policy holds

An org admin sets the organization's policy in **Settings › Organization › GitHub**, on the
**Repo bootstrap** card. A project admin can give one project its own in
**Settings › This project › GitHub**: the card says **Inherited from the org** until you choose
**Set a policy for this project**, and **Use the org policy** goes back. A project policy **replaces** the
organization's as a whole, so a project that sets one uses only its own. Everyone else sees the policy but can't
change it.

A policy is off until you turn on **Bootstrap repos** ("Lets project admins and agents bootstrap repos."). Then it
has four parts:

| Part | What By Dawn does with it |
| --- | --- |
| **Default-branch ruleset** | With **Add the ruleset** on, creates a ruleset named `SAW default branch` on the repo's default branch. It can **Require a pull request** with 0 to 6 **Required approvals**, **Dismiss stale approvals when new commits are pushed**, **Block force pushes**, **Block deleting the branch**, and require **Required status checks** by name. |
| **Files** | Adds `CODEOWNERS` and `README.md` from your templates, but only where the repo doesn't have them yet. Leave a template empty to skip that file. |
| **Actions variables** | Creates or updates repo-level Actions variables. They're plain text, readable by anyone with access to the repo. To set one variable outside a policy, an agent runs `saw variable set` (see [Actions variables](https://docs.bydawn.com/actions-variables/)). |
| **Secrets to push** | Copies By Dawn secrets into the repo's Actions secrets, its Dependabot secrets, or both, optionally under a different name on GitHub. A secret the repo already has is kept. |

Templates can use `{{repo}}` (the repo's name), `{{project}}` (the project's name) and `{{org}}` (your GitHub
organization's login). For example, `* @{{org}}/maintainers` becomes `* @acme/maintainers`.

### Actions or Dependabot

GitHub keeps a repo's secrets in separate stores, and each one is read by a different thing:

- **Actions** secrets are what your workflows read as `secrets.NAME`.
- **Dependabot** secrets are what Dependabot reads when it updates dependencies. That includes the credentials
  for a private package registry in the `registries:` block of `.github/dependabot.yml`.

Neither can see the other. If `.github/dependabot.yml` reads `${{secrets.PACKAGES_TOKEN}}` and the repo has only an
Actions secret of that name, Dependabot can't authenticate to your registry and every update run fails. So each
secret in a policy names where it goes: tick **Actions**, **Dependabot**, or both on its row. A secret going to both
is pushed twice under the same name, once to each store. Over the API this is the secret's `stores`, and a
secret without it goes to Actions only.

Only **organization and project secrets** can be pushed to a repo, because a personal secret belongs to one
person and shouldn't end up in everyone's CI. The secret picker only offers those, and By Dawn refuses to save a
policy that names any other (`repo_bootstrap_secret_scope_invalid`).

## Bootstrap a repo

On the project's [Repos](https://docs.bydawn.com/repos/) page, a project admin chooses **Bootstrap repo…** in the repo's **⋯** menu. It's
offered for GitHub repos. The dialog sums up what the policy will set up, for example "Sets up default-branch ruleset,
CODEOWNERS, 1 variable and 1 secret.", and whether the policy is the project's or the organization's. Choose
**Bootstrap now**, or **Bootstrap again** on a repo that has been bootstrapped before. If the project's policy is
off, the dialog says so and **Open settings** takes you to it.

The repo's row then shows a badge for its latest run:

| Badge | Meaning |
| --- | --- |
| **Bootstrapped** | Every item applied, already matched or was already there. |
| **Secrets pending** | At least one secret waits for a By Dawn agent to push it ([why](#why-secrets-are-pushed-by-an-agent)). This badge wins while it lasts, so check the dialog for any other item that didn't apply. |
| **Partly bootstrapped** | At least one item didn't apply: a permission is missing, GitHub's plan has no rulesets, or GitHub refused. The dialog says which. |
| **Bootstrap refused** | Nothing could run, for example because By Dawn's GitHub App can't reach the repo. The dialog says why. |

The dialog lists every item of the latest run, says whether a person or a By Dawn agent ran it, and gives each item
one of the results in [What a run does](#what-a-run-does).

### Over the API

Over the API, the organization's policy is one object:

```http
PUT https://workspace.dev.sawrun.com/api/v1/orgs/{orgId}/organization/repo-bootstrap-policy
Authorization: Bearer eyJ…
Content-Type: application/json

{
  "enabled": true,
  "ruleset": { "enabled": true, "requirePullRequest": true, "requiredApprovals": 1, "dismissStaleReviews": false,
               "blockForcePush": true, "blockDeletion": true, "requiredStatusChecks": ["build"] },
  "files": { "codeowners": "* @{{org}}/maintainers\n", "readme": "# {{repo}}\n\nPart of {{project}}.\n" },
  "variables": [ { "name": "DEPLOY_ENV", "value": "dev" } ],
  "secrets": [ { "name": "NPM_TOKEN", "scope": "org", "githubName": null, "stores": ["actions", "dependabot"] } ]
}
```

A project's policy lives at `…/projects/{projectId}/repo-bootstrap-policy`. Reading it returns the project's
own policy (`override`, or `null`), the one that applies (`effective`), and where that comes from
(`source`: `org` or `project`). `DELETE` goes back to the organization's.

Variable and secret names follow GitHub's rules: letters, digits and `_`, not starting with a digit or with
`GITHUB_`. A policy holds up to 50 of each. `stores` is `actions`, `dependabot` or both, with no repeats, and two
secrets can't push the same name to the same store.

## What a run does

By Dawn works through the policy in this order: **files, variables, secrets, then the ruleset**. The ruleset goes
last because a rule requiring pull requests would otherwise block By Dawn's own first commit.

Each item reports what happened. The dialog shows the label, and the API the value next to it:

| Result | API value | Meaning |
| --- | --- | --- |
| **Applied** | `applied` | By Dawn wrote it. |
| **Unchanged** | `unchanged` | It already matched the policy. |
| **Already there** | `skipped_exists` | The file (or secret) is already there, so By Dawn left it alone. |
| **Not on this GitHub plan** | `unavailable_plan` | The ruleset couldn't be created: GitHub doesn't offer rulesets on private repos in the Free plan. Everything else still applies. |
| **Permission missing** | `permission_missing` | By Dawn's GitHub App hasn't been given the permission this item needs (see [Permissions on GitHub](#permissions-on-github)). |
| **Waiting for an agent** | `pending` | A secret waiting for a By Dawn agent to push it (see below). |
| **Failed** | `failed` | GitHub refused for another reason. The item says what it answered (`detail`). |

To bootstrap a repo over the API, `POST` to `…/projects/{projectId}/repos/{repoId}/bootstrap`. The answer is the
run: its `items` (a secret's item also says which `store` it went to), an overall `status` (`completed`, `partial`, `pending_secrets` or `refused`) and, when
something stopped it, a `refusal` with a slug and an `acceptUrl`. `GET
…/projects/{projectId}/repo-bootstrap-runs` returns the latest run for each repo in the project. A **slug**
is a fixed error code you can branch on, and [Responses and errors](https://docs.bydawn.com/developers/responses-and-errors/) explains
them.

## Why secrets are pushed by an agent

By Dawn secrets are end-to-end encrypted, so By Dawn's servers can't read them, and that includes pushing them to
GitHub. A repo's secrets have to be **sealed** with the repo's own public key before GitHub accepts them, and
its Actions and Dependabot secrets each have their own key. So a By Dawn agent that's allowed to read the secret seals it on its own machine, and only the sealed value
passes through By Dawn to GitHub. Nobody but GitHub can open it.

That's why secrets come back **Waiting for an agent** when you choose **Bootstrap now**, and the dialog says "Secrets
will be pushed by the next SAW agent that bootstraps this repo." A worker pushes them the next time it runs:

```
saw repo bootstrap <repo> [--overwrite-secrets]
```

Agents can also call the same thing as the `saw_repo_bootstrap` tool. It prints one line per item:

```
file CODEOWNERS: skipped_exists
file README.md: applied
variable DEPLOY_ENV: unchanged
secret NPM_TOKEN: applied
secret NPM_TOKEN (dependabot): applied
ruleset SAW default branch: applied
```

A secret's line without a store is its Actions copy, and `(dependabot)` marks its Dependabot copy.

By default a secret the repo already has is left alone (`skipped_exists`). Pass `--overwrite-secrets` to
replace it, which is how you rotate a secret into the repo. The command only works on a repo bound to the
ticket, and not from a read-only run such as planning.

An agent that needs one more Actions variable, rather than the policy's, runs `saw variable set` instead. See
[Actions variables](https://docs.bydawn.com/actions-variables/).

## Permissions on GitHub

Bootstrap needs four repository permissions on By Dawn's GitHub App beyond the usual ones: **Administration**
(for the ruleset), **Secrets**, **Dependabot secrets** and **Variables**, all read and write. If your organization installed the App
before it asked for them, GitHub holds the request until an owner accepts it. Until then, those items report
**Permission missing**, and the dialog shows **Accept the App’s permissions on GitHub** (over the API, the run's
`refusal` is `github_bootstrap_permission_missing` with an `acceptUrl`). Open it as an owner of the GitHub
organization, review the request and accept it. By Dawn keeps these
permissions inside its own GitHub service and uses each one for a single repo at a time.

> **Caution:** A repo that By Dawn's GitHub App can't reach, for example because the installation is limited to selected
> repositories, is refused as a whole (`github_repo_not_covered`). Add the repo to the installation's repository
> access on GitHub, then run bootstrap again.

## Do and don't

- **Do** turn the ruleset on last when you adopt bootstrap on existing repos. Try a policy with only files and
  variables first, check the results, then add the ruleset.
- **Do** list the status checks your CI actually reports. A required check that never runs blocks every pull
  request.
- **Don't** expect bootstrap to update a `CODEOWNERS` or README you changed in the template. It only adds
  missing files, so change existing ones by pull request.

Next, see [Branching and CI](https://docs.bydawn.com/branching-and-ci/) to make By Dawn's branches match your CI.

Source: https://docs.bydawn.com/repo-bootstrap/
