GitHub repositories
infra/inventory/repositories.yaml describes every repository in the lyceum-tech GitHub
organisation. The Terragrunt unit infra/terraform/github makes GitHub match it. Rules:
delivery and environment rules.
The inventory
One entry per repository. These are the fields:
| Field | On GitHub | Meaning |
|---|---|---|
name |
the repository name | Unique. Also the Terraform key |
description |
the description | MAY be empty |
visibility |
PRIVATE or PUBLIC |
Nothing else |
archived |
archived | An archived repository MUST be production: false |
features |
discussions, issues, projects, wiki | Exactly these four booleans |
responsible |
not applied | A directory under people/. Never a placeholder |
production |
the default-branch ruleset | A non-archived PUBLIC repository MUST be production: true |
required_checks |
the ruleset's required status checks | Optional. Job names from GitHub Actions. Only on a non-archived production: true repository |
deploy_key_push |
the ruleset's DeployKey bypass |
Optional boolean. Lets the repository's write deploy keys push to the default branch. Only on a non-archived production: true repository. See deploy key push |
production and visibility are the two ISO risk lenses. They overlap by design.
- Every repository in the organisation MUST have an entry. The plan fails and names the missing ones.
- DO NOT remove an entry to delete a repository. The plan refuses. Set
archived: trueand keep the entry. - Only the fields above are managed, plus the organisation-wide defaults below. Every other setting keeps whatever GitHub holds.
These settings are the same for every repository, so they are module variables, not inventory fields:
| Setting | Value | On GitHub |
|---|---|---|
delete_branch_on_merge |
true |
Settings, General, "Automatically delete head branches" |
Tasks
Everything is a task under //infra/terraform/github. DO NOT run terraform or terragrunt in the unit by hand.
| Task | Does | Needs |
|---|---|---|
check-inventory |
The inventory's own rules, and responsible against people/ |
Nothing |
lint-python |
ruff on the check script |
Nothing |
lint-terraform |
terragrunt hcl fmt --check, hcl validate, terraform fmt -check, validate, tflint |
Network, for the provider |
lint |
The three above. The sweep runs it on every pull request | As above |
format |
The mutating twin of lint |
Nothing |
plan |
Compare GitHub with the inventory | gh login, ADC |
apply |
Update GitHub to match the inventory. Prompts first | gh login, ADC |
Environment and state
The organisation serves every environment, so the unit lives in production's platform project
(ENV-4). It is applied with --env platform-production only, and its state lives there:
- Bucket:
platform-production-510011-terraform-state, the project's own, fromenvs/platform-production.env. - Prefix:
platform-production/github/terraform.tfstate, fromroot.hcl. deploy_envMUST beplatform-production. The module refuses any other value. A second environment would hold a second state for one organisation.planandapplygo throughneph terraform --env platform-production --component github.
The bucket is owned by the platform/state_bucket unit, like every platform state bucket. If it does
not exist yet, then apply that unit for this environment first. See
bootstrapping platform images.
LYC_DEPLOY_ENV=platform-production mise run //infra/terraform/platform/state_bucket:apply
Credentials
- An operator authenticates as themselves. Run
gh auth loginonce. The provider falls back togh auth token. - A CI run MUST use a GitHub App. Set
GITHUB_APP_ID,GITHUB_APP_INSTALLATION_IDandGITHUB_APP_PEM_FILE. No such App exists yet, soapplyis operator-driven. This is an INFRA-1 gap. - State access is your Application Default Credentials, as for every platform unit. neph reads
infra/inventory/platform-production/config.yml, which holds no secrets.
What a plan shows
- Import. Every entry whose repository exists is imported on the first plan. An entry already in state is a no-op. Imports are computed from the organisation listing, so an entry with no repository behind it is created instead.
- Ruleset. Every non-archived
production: truerepository gets theproduction-default-branchruleset. It forbids deletion and force pushes and requires one approving review, plus a passing run of every job in the entry'srequired_checkson a branch that is up to date with the default branch. Use "Update branch" on the pull request when it falls behind. The first apply creates one per repository. If a repository's workflow cannot take it yet, then setproduction_ruleset_enabled = falseinterragrunt.hcl. - Admin bypass. A repository admin MAY merge a pull request without the review. They MUST NOT
push, force-push or delete the branch directly. GitHub logs every bypass under the repository's
rule insights and in the organisation audit log, which is the exception trail ISO 27001 change
management needs.
production_ruleset_admin_bypassin the module sets the mode.pull_requestis the default,noneremoves the bypass, andalwaysalso allows direct pushes. - Deploy key push. An entry with
deploy_key_push: truelets the repository's write deploy keys push to the default branch directly. It is for a bot that commits generated content and has no one to review it. GitHub logs each push past the ruleset like an admin bypass. The bypass covers every write deploy key on the repository, not one key, so such a repository MUST keep exactly one, held only as an Actions secret. Today that iswebsite:magazine-sync.ymlimports the scaile magazine sheet and pushes the articles tomaintwice a day, with the deploy key inMAGAZINE_SYNC_DEPLOY_KEY. - Other rulesets. The organisation's own "Default branch" ruleset also applies to every default branch, and it is stricter: code-owner review, re-approval after the last push and resolved threads. This unit does not manage it, so a plan never shows it.
- Search lag. The organisation listing is the GitHub search API. A repository created minutes ago MAY be missing from it. Run the plan again.
- Archived. GitHub refuses changes to an archived repository. If the plan changes one, then unarchive it first, or make the inventory match GitHub.
Adopting a new repository
- Create it on GitHub, or add the entry and let
applycreate it. - Add the entry with every field.
mise run //infra/terraform/github:lintrejects an incomplete one. - Run
mise run //infra/terraform/github:plan. The entry imports or creates. Nothing else changes. - Run
mise run //infra/terraform/github:apply.