7 Commits
Author SHA1 Message Date
Jason Ross 28e2c76215 Merge pull request #1 from JMR-dev/add-github-tofu-module
Add OpenTofu module for GitHub repo guard-rails
2026-04-29 18:36:21 -05:00
Jason RossandCopilot 1c4ac7f136 Add bypass_actors variable; allow Admin role to bypass ruleset
Mirrors the addition in JMR-dev/gh-repo-bootstrap. Solo-maintainer
configuration so PRs can be merged without a second approver.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-29 18:35:26 -05:00
Jason RossandCopilot 037edd905a Add OpenTofu module for GitHub repo guard-rails
Manages branch protection ruleset and deployment environments for this
repository. Mirrored to JMR-dev/gh-repo-bootstrap as a reusable module
+ gh CLI extension.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-29 18:30:45 -05:00
Jason RossandCopilot 4abb613f35 chore: ignore local cert/key files
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-29 17:48:02 -05:00
Jason Ross 629a534e15 Merge pull request #2 from JMR-dev/fix-cleanup
cleanup
2026-04-29 17:42:19 -05:00
Jason Ross 1a71ddf1b1 cleanup 2026-04-29 17:41:33 -05:00
Jason Ross f076d22663 Merge pull request #1 from JMR-dev/feat-mvp
Feat mvp
2026-04-29 17:34:17 -05:00
11 changed files with 253 additions and 4 deletions
+9
View File
@@ -12,11 +12,20 @@ yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
test-results/
# environment variables
.env
.env.production
# certificates / keys (never commit)
*.crt
*.key
*.pem
*.pfx
*.p12
caddy-local-root.crt
# macOS-specific files
.DS_Store
+5
View File
@@ -12,3 +12,8 @@ backend.hcl
*.auto.tfvars
terraform.tfvars
!terraform.tfvars.example
# Local backend overrides (developer-specific)
backend_override.tf
*_override.tf
!*_override.tf.example
+24
View File
@@ -0,0 +1,24 @@
# This file is maintained automatically by "tofu init".
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/integrations/github" {
version = "6.12.1"
constraints = "~> 6.2"
hashes = [
"h1:bGz4LIep/7PVrqy6P8cTYbAJpdxXGrupUJjkCczlzIs=",
"zh:3e1a4081ecb9518fdf0074db83c16ad00dc81ffe8249a6e3cf1894e947e28df6",
"zh:4cb8224b7f530795b674ac044675f6b22a7c9154f55eb9f76c5af6c7534056a4",
"zh:560bc08637926191f6871a89e986022ca67c70afda5bebca34b5216e6fac69c9",
"zh:5a70b5d2ac650c5c9819a1875411ebda229d0fcc6c9f57f9d751852ca3cd77ac",
"zh:8668d93bd4dc2ffa2545e1473af600a925d479b16033a71a4498a16f3b683c0c",
"zh:86eacc6059fd057948e178b665ba5cce74bd5488a9e1035734e60ff5ef1b6f8f",
"zh:a329fac98881d8dfc211a9bdc0ec6f2948f0b0c2704d1b6cbe5307403c7ad1b2",
"zh:dadd44abab3c52b9d572955afaef1658790e17ea355ee22b58996d81d28e02d8",
"zh:de9f455ef342cc38fb76bce844bfcd376fb81a4b9f9bc2fae023ff99efdf1338",
"zh:f8c6d2e8351b334491790358574e0a30a7c6d7f5b80f7daf32a7c0f3e9b1ab19",
"zh:fab41971a3edee04ab6eceaeab4eeb9a2b2f38a2af3b06eda93e2117b64994be",
"zh:fb1279b566dd9c8c117b2e4e0cc8344413b8fc8f2a3e24be22a9b2610551777b",
"zh:fbd1fee2c9df3aa19cf8851ce134dea6e45ea01cb85695c1726670c285797e25",
"zh:fe79d2a861fb9af420fa5bd7f02c031b2a0a3edf5dbc46022c8ecc7a33cf2b6d",
]
}
+47
View File
@@ -0,0 +1,47 @@
# GitHub repo configuration (OpenTofu)
Manages branch protection (via repository rulesets) and deployment
environments for `JMR-dev/dev_blog`.
This module is also the source-of-truth copy that is mirrored to the
[`gh-repo-bootstrap`](https://github.com/JMR-dev/gh-repo-bootstrap) repo,
which packages it as a reusable module + `gh` CLI extension
(`gh repo-bootstrap <owner>/<repo>`).
## Prerequisites
- [OpenTofu](https://opentofu.org/) >= 1.8
- A GitHub token with `repo` + `admin:repo_hook` scopes. Easiest:
```sh
export GITHUB_TOKEN=$(gh auth token)
```
- (For remote state) Cloudflare R2 credentials — copy
`backend.hcl.example` to `backend.hcl` and fill it in.
## Usage
```sh
# First time only:
tofu init -backend-config=backend.hcl
# Apply:
tofu apply
```
To run with **local state** (no R2 needed) for experimentation, comment
out the `backend "s3"` block in `versions.tf`, then `tofu init` again.
## What it manages
- A repository ruleset on the default branch enforcing:
- No deletion
- No force-push
- Required PR with N approving reviews (configurable)
- Resolved review threads
- Optional signed commits
- The set of GitHub deployment environments listed in `environments`.
It does **not** create the repository itself, and does not manage
repo-level settings (merge button options, default branch, topic, etc.).
Add those via `gh api` or import the `github_repository` resource if you
need them under OpenTofu control.
+8
View File
@@ -0,0 +1,8 @@
# Example Cloudflare R2 backend configuration.
# Copy to `backend.hcl` (gitignored) and fill in your values, then run:
# tofu init -backend-config=backend.hcl
bucket = "dev-blog-tfstate"
endpoints = { s3 = "https://<account-id>.r2.cloudflarestorage.com" }
access_key = "<R2_ACCESS_KEY_ID>"
secret_key = "<R2_SECRET_ACCESS_KEY>"
+46
View File
@@ -0,0 +1,46 @@
data "github_repository" "this" {
name = var.repo_name
}
resource "github_repository_ruleset" "default_branch" {
repository = data.github_repository.this.name
name = var.ruleset_name
target = "branch"
enforcement = "active"
conditions {
ref_name {
include = ["refs/heads/${var.default_branch}"]
exclude = []
}
}
dynamic "bypass_actors" {
for_each = var.bypass_actors
content {
actor_id = bypass_actors.value.actor_id
actor_type = bypass_actors.value.actor_type
bypass_mode = bypass_actors.value.bypass_mode
}
}
rules {
deletion = true
non_fast_forward = true
required_signatures = var.require_signed_commits
pull_request {
required_approving_review_count = var.required_reviews
dismiss_stale_reviews_on_push = true
require_code_owner_review = false
require_last_push_approval = false
required_review_thread_resolution = true
}
}
}
resource "github_repository_environment" "envs" {
for_each = toset(var.environments)
repository = data.github_repository.this.name
environment = each.value
}
+14
View File
@@ -0,0 +1,14 @@
output "repository_full_name" {
value = data.github_repository.this.full_name
description = "Full name (owner/repo) of the repository being managed."
}
output "ruleset_id" {
value = github_repository_ruleset.default_branch.id
description = "ID of the branch protection ruleset."
}
output "environments" {
value = sort([for e in github_repository_environment.envs : e.environment])
description = "Environments managed by this configuration."
}
+11
View File
@@ -0,0 +1,11 @@
repo_owner = "JMR-dev"
repo_name = "dev_blog"
default_branch = "main"
required_reviews = 1
require_signed_commits = false
environments = [
"production",
"staging",
"preview",
"development",
]
+56
View File
@@ -0,0 +1,56 @@
variable "repo_owner" {
description = "GitHub user or organization that owns the repository."
type = string
}
variable "repo_name" {
description = "Repository name (without owner prefix)."
type = string
}
variable "default_branch" {
description = "Branch protected by the ruleset."
type = string
default = "main"
}
variable "required_reviews" {
description = "Number of required approving reviews on PRs targeting the default branch."
type = number
default = 1
}
variable "require_signed_commits" {
description = "Require signed commits on the protected branch."
type = bool
default = false
}
variable "environments" {
description = "List of GitHub deployment environments to ensure exist."
type = list(string)
default = []
}
variable "ruleset_name" {
description = "Name to give the branch protection ruleset."
type = string
default = "default-branch-protection"
}
variable "bypass_actors" {
description = <<-EOT
Actors permitted to bypass the ruleset. Each entry needs:
- actor_id: numeric ID (for built-in repo roles: 1=read, 2=triage,
3=write, 4=maintain, 5=admin)
- actor_type: one of RepositoryRole, Team, Integration,
OrganizationAdmin, DeployKey
- bypass_mode: "always" or "pull_request"
EOT
type = list(object({
actor_id = number
actor_type = string
bypass_mode = string
}))
default = []
}
+33
View File
@@ -0,0 +1,33 @@
terraform {
required_version = ">= 1.8.0"
required_providers {
github = {
source = "integrations/github"
version = "~> 6.2"
}
}
# Cloudflare R2 (S3-compatible) backend, mirroring infra/versions.tf.
# Account-specific values are supplied at init time:
# tofu init -backend-config=backend.hcl
# For local-only experimentation, comment this entire block out and
# OpenTofu will fall back to the local backend.
backend "s3" {
key = "dev_blog/github.tfstate"
region = "auto"
skip_credentials_validation = true
skip_metadata_api_check = true
skip_region_validation = true
skip_requesting_account_id = true
skip_s3_checksum = true
use_path_style = true
}
}
provider "github" {
owner = var.repo_owner
# Token is read from the GITHUB_TOKEN environment variable.
# Easiest source: `export GITHUB_TOKEN=$(gh auth token)`.
}
-4
View File
@@ -1,4 +0,0 @@
{
"status": "passed",
"failedTests": []
}