---
id: w2/m8/t002
title: lego/backend/internal/github client — app JWT, installation tokens, repo listing
worker: worker2
status: done
estimate: 45m
depends_on: [w2/m8/t001]
---

## Objective

A small GitHub App client package: sign the app JWT (RS256 over `BEX_GITHUB_APP_PRIVATE_KEY`), mint installation access tokens (`POST /app/installations/{id}/access_tokens`), and list an installation's repositories (`GET /installation/repositories`, paginated). No third-party GitHub SDK unless already vendored — the three calls are plain REST.

## Context

- Package home: `lego/backend/internal/github` (client + later the service verbs, matching the `internal/apikeys`-style package shape).
- Config read once at startup like other optional features; nil client ⇒ callers return 503.
- Tokens expire in 1h — the client exposes expiry so callers (m9) can decide freshness.
- New Go files carry the Apache-2.0 header from `lego/operator/hack/boilerplate.go.txt`.

## Steps

1. `client.go`: config struct (AppID, PrivateKey PEM, Slug), app-JWT signing, `MintInstallationToken(ctx, installationID)`, `ListRepos(ctx, installationID)` with pagination (`per_page=100`, follow `Link`).
2. Repo item shape: `{id, fullName, private, defaultBranch, htmlURL, cloneURL}` — the fields the repo picker and deploy path need.
3. Unit tests against `httptest` fakes: JWT claims (iss/iat/exp), token minting, multi-page listing, GitHub error passthrough.

## Files

- `lego/backend/internal/github/client.go` (new)
- `lego/backend/internal/github/client_test.go` (new)

## Acceptance criteria

- [ ] `go test ./...` green in `lego/backend`; fake-server tests cover JWT, token mint, pagination
- [ ] No network calls in tests; client is nil-safe when unconfigured

## Out of scope

- Connection storage (t003), HTTP surfaces (t004–t005), webhook verification (m9)
