Skip to content
uslanozanPublic

About

Config-driven GitHub organization management with Terraform and a web dashboard.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

13 stars

Watchers

1 watching

Forks

Latest commit

 

History

222 Commits

Folders and files

Repository files navigation

Tidyorg logo

Tidyorg

Manage your GitHub organization from config files, not the settings UI.

License: MIT Terraform >= 1.5 Provider: integrations/github ~> 6.0 CI Latest release

Tidyorg is a config-driven engine for GitHub organizations. You describe repositories, teams, access, branch protection, and org settings in YAML; Terraform reconciles GitHub to match. Every change is a pull request — reviewed, versioned, and reversible.

Anything you change by hand in the GitHub UI drifts back on the next apply. The config is the single source of truth.

Why

  • Auditable — who can do what is answered by reading config, not clicking through settings. Every change is a commit.
  • Reproducible — the whole org can be rebuilt from config.
  • Least privilege by construction — access comes from team membership derived from config; org-owner escalation lives in a separate, review-gated file the dashboard cannot write.

Architecture

config/*.yml            engine (Terraform)          GitHub
  what you want    ──▶   reconciles           ──▶   reality
  (PR-reviewed)          (this image)                (one-way)

One optional layer on top: a web dashboard to view access across the org and make changes (add/remove members, edit repo access, branch protection, labels, org settings). The dashboard never talks to Terraform directly — every change it makes is a pull request against the config, so the same review gate applies. It authenticates each user via GitHub device flow and can batch many edits into a single PR.

Quick start (Docker)

You need: a GitHub organization you own, and a GitHub App installed on it (below). The published image includes the engine and a starter config, so cloning this repository is not required.

The commands below use Bash syntax. On Windows, the Compose route immediately below is the simplest option because it avoids shell-specific volume syntax.

# 1. Pull the image and scaffold a local config directory
docker pull ghcr.io/uslanozan/tidyorg:0.1.3
mkdir -p config state
docker run --rm \
  -v "$PWD/config:/config" \
  ghcr.io/uslanozan/tidyorg:0.1.3 scaffold

# edit config/ to describe your org (organization.yml, people.yml,
# privileged.yml, repositories/*.yml)

# 2. Drop your GitHub App private key next to the config
#    (downloaded when you created the App)
cp ~/Downloads/your-app.private-key.pem ./app.pem

# 3. Preview, then apply
docker run --rm \
  -v "$PWD/config:/config" \
  -v "$PWD/state:/state" \
  -v "$PWD/app.pem:/secrets/app.pem:ro" \
  -e TF_VAR_github_org_name=your-org \
  -e TF_VAR_github_app_id=123456 \
  -e TF_VAR_github_app_installation_id=12345678 \
  ghcr.io/uslanozan/tidyorg:0.1.3 plan

# swap `plan` for `apply` once the plan looks right

State is kept in ./state on your host — no HCP / Terraform Cloud required. Point at your own remote backend if you prefer (see Backend below).

docker compose users can download docker-compose.ghcr.yml, fill in its variables, then run:

docker compose -f docker-compose.ghcr.yml run --rm engine scaffold # first run only
docker compose -f docker-compose.ghcr.yml run --rm engine plan
docker compose -f docker-compose.ghcr.yml up -d dashboard

The dashboard is then available at http://localhost:8080. Before starting it, replace the placeholder engine App IDs and dashboard GITHUB_CLIENT_ID, CONFIG_OWNER, and CONFIG_REPO values in the Compose file. The file pins v0.1.3 by default; set TIDYORG_VERSION when you deliberately upgrade.

The regular docker-compose.yml remains the source-build setup for contributors.

GHCR is the canonical registry and Docker Hub is an automated mirror. Both receive the same multi-architecture build and version tags, so users may choose either source:

# Engine
docker pull ghcr.io/uslanozan/tidyorg:0.1.3
docker pull uslanozan/tidyorg:0.1.3

# Dashboard
docker pull ghcr.io/uslanozan/tidyorg-dashboard:0.1.3
docker pull uslanozan/tidyorg-dashboard:0.1.3

To use the Docker Hub mirror with Compose, set TIDYORG_REGISTRY=uslanozan.

GitHub Apps

Tidyorg authenticates as a GitHub App (short-lived tokens, org-owned identity — no personal access token). Depending on what you run, you create up to two Apps:

1. Engine bot (required) — used by Terraform. Broad, because it reconciles the whole org:

Scope Permission
Repository → Administration Read & write
Repository → Contents Read & write
Repository → Issues Read & write
Repository → Workflows Read & write
Repository → Metadata Read
Organization → Members Read & write
Organization → Administration Read & write

Supply TF_VAR_github_app_id, TF_VAR_github_app_installation_id, and the private key (-v ./app.pem:/secrets/app.pem).

2. Dashboard app (optional — only if you run the dashboard) — deliberately narrow. It opens PRs on the signed-in user's behalf and nothing else, so it cannot delete repos or edit privileged.yml:

Scope Permission
Repository → Contents Read & write
Repository → Pull requests Read & write
Repository → Actions Read-only (live "applying/in-sync" badge)
Repository → Metadata Read

Enable "Device Flow" and install it on only the config repo. For the published container, pass its client_id as the runtime variable GITHUB_CLIENT_ID. Local Vite development uses VITE_GITHUB_CLIENT_ID instead. See integrations/github-app/ for both manifests and step-by-step setup.

To let project mentors open dashboard PRs, set config_repository in organization.yml to that repo's name and declare the repo under config/repositories/. The engine grants the generated tidyorg-dashboard-writers team push on the config repo; protected main still requires the config repo's normal review rules.

Config schema

Four files under config/, split by ownership:

File Owns Who writes it
organization.yml roles, defaults, org settings, profile humans
repositories/<name>.yml one repo: access, branch protection, labels, files humans / dashboard
people.yml org membership (a list of usernames) humans / dashboard
privileged.yml org owners + org-scoped roles humans only (review-gated)

The split of people.yml / privileged.yml is the escalation gate: the file a dashboard can write cannot express "make this person an org owner." That lives in privileged.yml, which is protected by CODEOWNERS. The org name itself is not in config — it comes from TF_VAR_github_org_name, so there is a single source of truth. See config.example/ for a working set and terraform/config/*.example.yml for the fully-commented schema of every field.

A repository, minimally

description: "Payment gateway service"
language: go            # display metadata; CI auto-detects the real languages
mentors: [alice]        # repo admins (>=1)
developers: [bob, carol]
viewers: [dan]          # read-only (optional)
# everything else inherits from organization.yml defaults;
# override only what differs, e.g.:
protected_branches:
  main:
    required_reviews: 2

Backend (state)

The TF_STATE environment variable selects where Terraform keeps its state:

TF_STATE State lives in Extra variables
local (default) the mounted /state volume — zero setup —
hcp HCP Terraform / Terraform Cloud (recommended for teams) TF_CLOUD_ORGANIZATION, TF_WORKSPACE, TF_TOKEN_app_terraform_io
custom your own backend (S3, GCS, azurerm, …) mount your backend config at /engine/backend.tf
# HCP / Terraform Cloud instead of local state:
docker run --rm \
  -v "$PWD/config:/config" \
  -v "$PWD/app.pem:/secrets/app.pem:ro" \
  -e TF_STATE=hcp \
  -e TF_CLOUD_ORGANIZATION=your-tf-org \
  -e TF_WORKSPACE=tidyorg \
  -e TF_TOKEN_app_terraform_io=... \
  -e TF_VAR_github_org_name=your-org \
  -e TF_VAR_github_app_id=123456 \
  -e TF_VAR_github_app_installation_id=12345678 \
  ghcr.io/uslanozan/tidyorg:0.1.3 plan

The image is built with a local backend, so switching to hcp/custom re-runs terraform init -reconfigure at startup (cached providers are reused).

Verification

The release candidate passed the repository CI, multi-architecture image checks, container smoke tests, and a manual end-to-end run against a disposable GitHub organization. The test steps, evidence, and remaining automation gaps are recorded in docs/verification.md.

Known limitations

  • No backup is built in. Code history and issue/PR metadata are not backed up by Tidyorg; add your own off-GitHub backup if you need one.
  • Free-plan GitHub cannot use branch protection on private repos; Tidyorg surfaces this but cannot work around it.
  • Some org settings (e.g. billing email, PAT policy) are not readable by the API, so their drift is not detected — Tidyorg treats the config as the source of truth for those.

Development

# Dashboard (React + Vite + TS)
cd dashboard && npm ci && npm run dev

# Engine (Terraform) — providers only, no state/credentials
terraform -chdir=terraform init -backend=false && terraform -chdir=terraform validate

# Checks CI runs before merge
terraform -chdir=terraform fmt -check -recursive
terraform -chdir=terraform test
cd dashboard && npm run build && npm run verify:yaml

Build the images locally with docker build -t tidyorg . (engine) and docker build -t tidyorg-dashboard ./dashboard. See CONTRIBUTING.md for conventions.

License

MIT.

Status

Pre-1.0. The engine, the web dashboard (full write mode via PRs), and both Docker images are functional. A fresh disposable organization has been verified through initial apply, dashboard change, pull request, merge, reconciliation, and a final no-change plan. The live pilot is still manual; automated browser and disposable-organization E2E coverage remain before a 1.0 tag.

About

Config-driven GitHub organization management with Terraform and a web dashboard.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

13 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages