Skip to content

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: true and 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, from envs/platform-production.env.
  • Prefix: platform-production/github/terraform.tfstate, from root.hcl.
  • deploy_env MUST be platform-production. The module refuses any other value. A second environment would hold a second state for one organisation.
  • plan and apply go through neph 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 login once. The provider falls back to gh auth token.
  • A CI run MUST use a GitHub App. Set GITHUB_APP_ID, GITHUB_APP_INSTALLATION_ID and GITHUB_APP_PEM_FILE. No such App exists yet, so apply is 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: true repository gets the production-default-branch ruleset. It forbids deletion and force pushes and requires one approving review, plus a passing run of every job in the entry's required_checks on 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 set production_ruleset_enabled = false in terragrunt.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_bypass in the module sets the mode. pull_request is the default, none removes the bypass, and always also allows direct pushes.
  • Deploy key push. An entry with deploy_key_push: true lets 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 is website: magazine-sync.yml imports the scaile magazine sheet and pushes the articles to main twice a day, with the deploy key in MAGAZINE_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

  1. Create it on GitHub, or add the entry and let apply create it.
  2. Add the entry with every field. mise run //infra/terraform/github:lint rejects an incomplete one.
  3. Run mise run //infra/terraform/github:plan. The entry imports or creates. Nothing else changes.
  4. Run mise run //infra/terraform/github:apply.