# GitHub Enterprise Server on a private network

Your server doesn't need to be on the internet. Run `bumpkit relay` on a machine inside your network, and the release gate works on your pull requests with no inbound firewall hole. The relay connects out to Bumpkit over HTTPS, runs only the requests the gate needs, and keeps your server's token on your side.

## How it connects

Every connection starts inside your network:

- **Relay to Bumpkit:** outbound HTTPS only, to bumpkit.dev. The relay asks for work, and Bumpkit holds that request for up to 25 seconds and answers with anything waiting. Nothing connects in to the relay from the internet.
- **Relay to your server:** HTTPS to the one address in `--host`, using the token in the relay's environment. The relay never follows a redirect.
- **Your server to the relay:** webhooks, over your own network, to the relay's `/webhook` address on port 8787. The relay passes each one to Bumpkit unchanged.

Allow outbound HTTPS from the relay's machine to bumpkit.dev and to your server. That's the whole firewall change.

## Set it up

1. **Connect.** On the Integrations page, open the GitHub card, then **GitHub Enterprise Server**, then **Your server is on a private network**. Enter your server's address and the repository, or an owner to cover all its repositories, and select **Connect through a relay**. Bumpkit shows a relay token and a webhook secret, each once. Owners and admins connect, signed in to the dashboard.

   You can also create the connection with the CLI or the `gate_connections_add` MCP tool, then create its token in the dashboard, because a key can't create one:

   ```bash
   bumpkit gate connections add --provider ghes --host https://github.acme.internal --path acme/api --relay
   ```

2. **Create a token on your server.** A personal access token with the `repo` scope, from a user who can write to the repository. Add `read:org` if your rules name teams. Bumpkit never sees it.

3. **Install and run the relay** on a machine that can reach your server and Bumpkit. There's no container image yet, so install the CLI:

   ```bash
   curl -fsSL https://bumpkit.dev/install.sh | sh
   export BUMPKIT_RELAY_TOKEN=YOUR_RELAY_TOKEN
   export BUMPKIT_RELAY_SERVER_TOKEN=YOUR_SERVER_TOKEN
   bumpkit relay --host https://github.acme.internal --repos acme/api
   ```

   Keep it running with whatever runs your other services. Your server's address must match the connection exactly, or the relay stops and says so.

4. **Add the webhook.** On your server, open the repository, then **Settings → Hooks → Add webhook**. Use `http://YOUR_RELAY:8787/webhook` as the **Payload URL**, `application/json` as the **Content type** and the webhook secret as the **Secret**. Choose **Pull requests**, **Pull request reviews**, **Check runs** and **Statuses**.

5. **Open a pull request.** The **Bumpkit release gate** status and one comment appear. To block merges until the gate passes, require that status in the branch protection rule under **Settings → Branches**.

The connection row shows a status lamp, the relay's version and when it was last seen. `bumpkit gate connections list` and the `gate_connections_list` MCP tool show the same.

## bumpkit relay

| Flag | Default | What it does |
|---|---|---|
| `--host` | `BUMPKIT_RELAY_HOST` | Your server's https address. The only server the relay talks to |
| `--repos` | `BUMPKIT_RELAY_REPOS` | The only repositories it serves, as `owner/repository` or `owner/*`, comma separated. Required |
| `--listen` | `:8787` | Address for your server's webhooks |
| `--tls-cert`, `--tls-key` | | Serve webhooks over https with this certificate and key |
| `--ca-file` | | Also trust this certificate authority for your server, if it uses an internal one |

Set `BUMPKIT_RELAY_TOKEN` and `BUMPKIT_RELAY_SERVER_TOKEN` in its environment. `GET /healthz` on the listen address answers `200` while the relay is connected to Bumpkit and `503` when it isn't.

## What the relay sends, and what it doesn't

**What goes to Bumpkit**, for the repositories in `--repos`:

- Pull request details: title, description, author, branches and commits, including commit messages and author names and emails
- The diff between the pull request's base and head
- `bumpkit.yml`, `.bumpkit.yml` and `CODEOWNERS` from the commit Bumpkit asks for
- Reviews, commit statuses, check runs and code scanning alerts on the pull request
- The text of the pull request's comments, so Bumpkit can find and update its own
- The members of a team, if your rules name it, and a reviewer's permission on the repository
- The default branch name and the rest of the repository's settings summary from the repository endpoint
- Each webhook delivery, whole and unchanged, with its signature

**What never goes to Bumpkit:**

- Your server's token. It stays in the relay's environment, and Bumpkit has no field to store it in
- Any other file. The relay can't read a file outside that list, list a directory or download an archive, though a diff shows the changed lines of whatever files a pull request touches
- Any repository outside `--repos`, or any other server on your network
- Admin data, users' keys, or search results

**What the relay writes to your server:** only the **Bumpkit release gate** commit status and Bumpkit's own comment on a pull request. It can't write another check's status, and it edits a comment only after confirming that Bumpkit wrote it.

## The only requests it runs

The relay checks every request itself, before it touches your server, so it isn't an open door even if something on our side went wrong. Anything else is refused and logged, along with odd encodings, `..` in a path, another host or any query parameter not listed. All paths are under `/api/v3` on `--host`.

| Method | Path |
|---|---|
| `GET` | `/repos/{owner}/{repo}` |
| `GET` | `/repos/{owner}/{repo}/pulls/{number}`, `/pulls/{number}/reviews` |
| `GET` | `/repos/{owner}/{repo}/compare/{sha}...{sha}` |
| `GET` | `/repos/{owner}/{repo}/contents/{file}`, for the files above |
| `GET` | `/repos/{owner}/{repo}/commits/{sha}/check-runs`, `/status`, `/pulls` |
| `GET` | `/repos/{owner}/{repo}/issues/{number}/comments` |
| `GET` | `/repos/{owner}/{repo}/issues/comments/{id}`, only to confirm a comment is Bumpkit's before editing it |
| `GET` | `/repos/{owner}/{repo}/code-scanning/alerts` |
| `GET` | `/repos/{owner}/{repo}/collaborators/{login}/permission` |
| `GET` | `/orgs/{owner}/teams/{team}/members` |
| `POST` | `/repos/{owner}/{repo}/statuses/{sha}`, only the **Bumpkit release gate** status |
| `POST` | `/repos/{owner}/{repo}/issues/{number}/comments`, only Bumpkit's own comment |
| `PATCH` | `/repos/{owner}/{repo}/issues/comments/{id}`, only Bumpkit's own comment |

While it waits to be picked up or read, the content of a request or answer is stored encrypted, and it's deleted as soon as the gate has read it or the request times out after 60 seconds. What the gate keeps afterward is the same as on any other host. See [Release Gate](/docs/release-gate).

## When the relay is offline

Bumpkit never reads an offline relay as a pass.

- **The connection row** shows **Relay offline** and when it was last seen, or **Not connected yet**, plus the problem and how many pull requests are waiting to be checked.
- **Your server** gets nothing, because the relay is what writes to it. A pull request keeps the status it had, so if you require **Bumpkit release gate**, that pull request waits. It doesn't pass, and a person with admin rights on your server can still merge it.
- **Webhooks** can't arrive either, since they come through the relay. Your server keeps failed deliveries under **Settings → Hooks → Recent Deliveries**; use **Redeliver** once the relay is back, if Bumpkit hasn't already seen that delivery.
- **`bumpkit gate` in CI** answers `503 RELAY_OFFLINE` with the last-seen time, and it exits 2 unless your policy sets `on_error: pass`.
- **If the relay drops in the middle of a check,** Bumpkit marks the pull request **Couldn't reach your server** if it can, and `on_error` in `bumpkit.yml` decides whether that blocks. Your workspace's strictness setting applies too, where you have one.

When the relay comes back, Bumpkit checks up to 50 waiting pull requests again on its own, for up to 24 hours.

## Tokens

Each connection has one relay token. It works only for that connection, and Bumpkit stores only its hash. A person signed in to the dashboard creates, rotates and revokes it, each with an audit entry. An API key, the CLI or an agent gets a refusal that says where to go, including `bumpkit gate connections relay-token create`, `rotate` and `revoke` and their MCP tools.

Rotating stops the old token at once, so restart the relay with the new one. A running relay stops when its token is rotated or revoked.

## Webhook checks

Bumpkit checks every delivery, using the connection's webhook secret:

- A delivery with the wrong signature gets `401`, and your server's delivery log shows it.
- A delivery Bumpkit has already seen does nothing, by its delivery ID, for 30 days. That includes **Redeliver** on a delivery that already reached Bumpkit, so push a commit to check a pull request again.
- A delivery for a repository outside the connection's path does nothing.
- A relay connection doesn't accept webhooks at the public webhook address.

Repositories stay identified by server, within your workspace. Another company can have a server with the same name, and its relay never sees your work: a relay token only ever reaches its own connection.
