OpenTofu-managed GitHub organization governance for the organization selected by each workspace. Each organization uses the same root with its own state and per-org configuration.
Organization-level rulesets, repository settings, and labels apply common policy across an organization's repositories. This root defines that policy once, and each workspace selects one organization and its own configuration.
| Resource | Effect | Source |
|---|---|---|
github_organization_ruleset.org_push_protection |
Push protection | rulesets-push-protection.tf |
restapi_object.org_branch_protection |
Branch protection | rulesets-branch-protection.tf |
github_organization_ruleset.required_signatures |
Required signatures | rulesets-branch-protection.tf |
github_organization_ruleset.commit_subject_branches |
Branch commit subjects | rulesets-branch-protection.tf |
restapi_object.org_review_gate |
Review gate | rulesets-branch-protection.tf |
restapi_object.org_automerge_manual |
Manual-repo approval | automerge.tf |
github_organization_custom_properties.* |
Organization property schemas | Each property's own file, e.g. family.tf, gitflow.tf |
github_repository_custom_property.* |
Repository property values | Each property's own file, e.g. ai-untrusted-property.tf, automerge.tf |
restapi_object.docs_publisher |
Documentation publisher | docs-publisher.tf |
github_organization_ruleset.org_release_freeze |
Release freeze | release-freeze.tf |
github_organization_ruleset.org_branch_write_restriction |
Branch-write restriction | rulesets-branch-write-restriction.tf |
github_organization_ruleset.org_gitflow_base |
Git-flow base branch ruleset | rulesets-gitflow.tf |
restapi_object.org_gitflow_main |
Git-flow main branch ruleset | rulesets-gitflow.tf |
restapi_object.org_gitflow_develop |
Git-flow develop branch ruleset | rulesets-gitflow.tf |
| Enterprise-level required workflow | Git-flow main-source gate | Attached outside this root |
restapi_object.org_upstream_fork_protection |
Upstream-fork branch ruleset | upstream-forks.tf |
github_organization_ruleset.org_merge_gate |
Required Merge Gate checks | merge-gate.tf |
github_organization_ruleset.org_merge_gate_strict |
Up-to-date Merge Gate checks | merge-gate-strict.tf |
github_organization_ruleset.org_copilot_review |
Explicit Copilot review targets | copilot-review.tf |
github_actions_organization_permissions.org |
Allowed Actions and SHA policy | actions.tf |
github_actions_organization_workflow_permissions.org |
Default GITHUB_TOKEN permissions |
actions.tf |
module.repo_settings |
Repository settings | repos.tf, modules/repo-settings/main.tf |
github_issue_label.org |
Labels read from the org's .github repo |
labels.tf |
github_actions_variable.iac_drift_enabled |
Per-repository IaC drift opt-in variable | iac-drift.tf |
github_actions_runner_group.homelab |
Homelab runner role | runner-groups.tf |
github_organization_webhook.hermes_pr_review |
Optional pull-request webhook, enabled when both webhook variables are set | org-webhook.tf |
Pull-request rulesets use the REST provider because the GitHub provider cannot set require_extra_approval_for_unattributed_changes.
Repository settings cover every unarchived repo plus the always-managed public
.github organization-defaults repo and private .github-private members-only
profile repo. The optional family single-select property is unset unless a
repo's selected config/orgs/<github_owner>/repos.yml entry supplies a value.
The selected config/orgs/<github_owner>/ai-untrusted.yml lists repositories opted into lower-trust AI sandbox access.
Native GitHub push rules at the git layer (no workflow runs). Hard
ceiling on individual file size + banned-extension list, applied to
every repo, every ref. Thresholds + list live in
config/rulesets-defaults.yml.
Quality gate on every default branch: required signatures, branch name pattern, strict Conventional Commits regex, PR thread resolution. No bypass — applies to everyone including org admins.
Extends required signatures to every branch. Imported from the live org ruleset without narrowing its all-branch coverage.
Refuses a push to any non-default branch when a commit subject is not a Conventional Commit or a merge message. Upstream forks are excluded.
Review gate on every default branch: resolved review threads on PRs, with no required approving or CODEOWNER review. Disabled by default.
All repositories and Actions are allowed. SHA pinning is not required so first-party references can use floating major-version tags. Workflow tokens default to read-only, and workflows cannot approve pull requests.
The homelab runner role cannot use public repositories. The PR-Agent runner group is enterprise-level and is not managed here.
Applies to repos with the automerge: manual property, and requires no
approving review. Required status checks are the only merge gate.
Requires the configured approval count for documentation pull requests. The
release automation and configured triage apps bypass review in
pull_request mode.
Requires the release-freeze status on develop in repos with the
gitflow: true property. The ruleset defaults to evaluate and can be set to
active after the shared status workflow is called from every git-flow repo.
For repositories whose organization custom property gitflow is true, a
pull request into main must come from the same repository's develop
branch or from a hotfix/<name> branch. An enterprise-level required
workflow runs .github/workflows/main-source-gate.yml and reports the
Main Source Gate check. The ruleset that attaches it is managed outside this
root. It runs in evaluate mode and moves to active after the evaluation
results are clean.
main accepts merge commits only, and develop accepts squash merges only.
A hotfix/<name> branch starts from main: its pull request targets main,
and a second pull request from that branch targets develop. Under
Branch-write restriction, only the apps listed in
automation_bypass_app_ids can create or update a hotfix/<name> branch.
Only the GitHub Apps listed in automation_bypass_app_ids can create or
update a branch other than main. Everyone else, org admins included,
can open and merge pull requests into main but cannot push or create
any other branch. The list is a required workspace variable with no
default.
The existing org adopts its live rulesets through these import blocks. The
IDs live in config/orgs/<github_owner>/ruleset-imports.yml; other orgs omit
that optional file:
org_branch_protection← the live ruleset originally namedmainorg_review_gate← the live org review gateorg_gitflow_main← the live git-flow main rulesetorg_gitflow_develop← the live git-flow develop rulesetorg_automerge_manual← the live manual-repository rulesetdocs_publisher← the live documentation-publisher rulesetorg_upstream_fork_protection← the live upstream-fork rulesetrequired_signatures← the live all-branch signature ruleset
Org Actions variables and secrets are configured in the secret manager and
synced to GitHub; this root does not manage them (ai-variables.tf). CodeQL
default setup on the org's public repos is managed in code_scanning.tf.
versions.tf # tofu + provider pins, Terrakube cloud backend
providers.tf # github + REST providers, token minted per run (ephemeral)
variables.tf # all input variables (no magic numbers in .tf below)
data.tf # live lookups: repo IDs, org metadata — never literals
locals.tf # shared and selected per-org config decoded into named locals
rulesets-push-protection.tf # org_push_protection
rulesets-branch-protection.tf # org_branch_protection, required_signatures, commit_subject_branches, org_review_gate + import blocks
rulesets-gitflow.tf # org_gitflow_base, org_gitflow_main, org_gitflow_develop
release-freeze.tf # org_release_freeze
rulesets-branch-write-restriction.tf # org_branch_write_restriction
main.tf # multi-file entrypoint stub (resources organized by topic)
outputs.tf # intentionally empty — see file header
config/ # shared policy defaults and owner-scoped config
config/orgs/ # selected by github_owner; files are optional
CODEOWNERS is not committed or managed in this repo. A static
.github/CODEOWNERS would bake a specific identity into the source tree.
config/ holds shared ruleset defaults. Each workspace selects its organization
through github_owner, which also selects config/orgs/<github_owner>/ when
present. A missing org directory or config file means defaults or no opt-in,
and repository metadata comes from GitHub data sources.
config/commit-message-pattern.txt and config/rulesets-defaults.yml remain
shared. The label set is read from
the central label file named in config/central.yml by data.tf and
applied by labels.tf; label sets are not stored in config/. Canonical text
the org doesn't author — MIT LICENSE body, CODE_OF_CONDUCT, etc. — is fetched
at apply time from a trustworthy upstream via data "http", not committed as
a local template.
private_repo_overrides is read from the protected OpenBao KV v2 document at
secret/infrastructure/tofu-github/private-overrides for orgs whose merge-gate config
requires private repos. Its optional merge_gate map carries required-check
contexts for those repositories; the repo list is derived from each outer repo
key.
-
OpenTofu (>= 1.11; local pin 1.12.2 in
.opentofu-version) and theintegrations/githubprovider, pinned inversions.tf. Dev shell via direnv:git clone git@github.com:dryvist/tofu-github.git cd tofu-github && direnv allow # provides tofu, sops, linters
-
A reachable Terrakube workspace. State, locking, and remote execution come from the homelab Terrakube instance; the
tofu-githubworkspace is defined as code in the platform repo. The platform is deliberately not-24/7 — power its node on for off-hours work. -
One-time login per machine (no keychain, no stored passwords). The
TF_CLOUD_*coordinates (TF_CLOUD_HOSTNAME,TF_CLOUD_ORGANIZATION,TF_WORKSPACE) are a fleet-wide fact owned by the platform, stored once in OpenBao (secret/platform/terrakube/main)..envrcpulls them atdirenv allowvia the platform AppRole (BAO_ADDR+ role/secret ID from the operator's environment) — so nothing sensitive, not even the internal endpoint, is committed in this repo:tofu login "$TF_CLOUD_HOSTNAME"Requires membership in the org's
terrakube-adminsteam.
No AWS account, no aws-vault, no local GITHUB_TOKEN: providers.tf
mints a short-lived GitHub App installation token from OpenBao on every
run, through an ephemeral resource, so it is never stored or written to state.
Daily flow:
tofu init # one-time per worktree
tofu plan # executes remotely; output streams back
tofu apply # remote apply, gated on plan confirmationValidation only (no backend, no credentials):
tofu init -backend=false && tofu validateEnforcement defaults vary by ruleset. See Applying.
Part of a larger ecosystem of ~40 repos — see how it all fits together.