A collection of standalone bash automation scripts for GitHub organization and enterprise administration. Each script is a self-contained, self-validating CLI utility that interacts with the GitHub REST and GraphQL APIs using curl + jq. A shared utility library (lib/github-common.sh) provides common validation, output, and API helpers.
Key principle: Scripts are independent — they are never imported or called by each other. Every script manages its own validation, error handling, and environment variable requirements.
github-api-scripts/
├── lib/
│ └── github-common.sh # Shared helpers (source this, never execute directly)
├── org-admin/ # Organization-level administration scripts
│ └── github-<name>/
│ └── github-<name>.sh
├── enterprise/ # Enterprise-level scripts
│ └── github-<name>/
│ └── github-<name>.sh
├── reporting/ # Reporting and audit scripts
│ └── github-<name>/
│ └── github-<name>.sh
├── personal/ # Personal GitHub utility scripts
│ └── github-<name>/
│ └── github-<name>.sh
├── .githooks/
│ └── pre-commit # Secret scanning (gitleaks) + shellcheck
├── .github/
│ ├── copilot-instructions.md # Detailed AI agent conventions
│ ├── agents/ # Custom agent profiles
│ ├── skills/ # Custom skills
│ └── instructions/ # Path-scoped instruction files
├── install-hooks.sh # Installs .githooks/pre-commit via git config
├── LICENSE # MIT
└── README.md # Full script documentation with usage examples
| Tool | Role |
|---|---|
| bash 4+ | All scripts |
| curl | GitHub REST and GraphQL API calls |
| jq | JSON parsing and transformation |
| gh CLI | Used in github-organize-stars |
| base64 | Used in github-auto-repo-creation for CODEOWNERS encoding |
| git | Used in github-import-repo for bare clone + mirror push |
| shellcheck | Linting (pre-commit hook + CI) |
| gitleaks | Secret scanning (pre-commit hook) |
| Release Please | Automated releases and CHANGELOG generation (.github/workflows/release.yml) |
There is no build step. Scripts run directly:
# Install the pre-commit hook (one-time setup)
./install-hooks.sh
# Run any script directly after exporting required env vars
export GITHUB_TOKEN=ghp_yourtoken
export ORG=my-org
./org-admin/github-get-repo-list/github-get-repo-list.shLint all scripts:
find . -name "*.sh" | xargs shellcheck --severity=warning --exclude=SC2034,SC1091 --shell=bashThe project has a bats unit-test suite in tests/. Every new script must ship with tests.
# Run all tests
bats tests/
# Run a single file
bats tests/test_common.bats| File | What it covers |
|---|---|
tests/test_common.bats |
lib/github-common.sh — pure-logic functions (validate_slug, require_env_var, require_command, err, configure_gh_auth, validate_token, get_repo_page_count) and API helpers (gh_api sentinels, gh_api_paginate) |
tests/test_script_validation.bats |
Every script — missing required env vars exit 1, invalid CLI args exit 1, --help exits 0, script-specific enum/allowlist validation |
tests/mock_curl.sh |
Universal drop-in curl mock (used by both test files); response data via env vars MOCK_CURL_CODE, MOCK_CURL_BODY, MOCK_CURL_LINK |
- Missing required env vars — one
@testper required variable, in the order the script checks them. Each test assertsstatus -eq 1. - Invalid CLI args — unknown flag exits 1 with an "Unknown" message.
--helpflag — exits 0 (for scripts that implement it).- Recognised flags —
--dry-runand other known flags do not trigger the unknown-arg error (test by asserting output does not contain "Unknown"). - Script-specific guards — enum validation (
--type,DEPENDABOT_REASON), URL allowlists (GIT_URL_PREFIX), required positional args.
Tests shadow real binaries by prepending a MOCK_BIN directory to PATH:
setup() {
MOCK_BIN="$(mktemp -d)"
# Fail gh auth so GITHUB_TOKEN is never auto-resolved from a session
printf '#!/bin/sh\nexit 1\n' > "$MOCK_BIN/gh"
chmod +x "$MOCK_BIN/gh"
}
teardown() { rm -rf "$MOCK_BIN"; }
@test "my-script: exits 1 when GITHUB_TOKEN is not set" {
run bash -c "export PATH='${MOCK_BIN}:${PATH}'; unset GITHUB_TOKEN; bash '${REPO_ROOT}/domain/my-script/my-script.sh'"
[ "$status" -eq 1 ]
}Use _mock_curl_200 (defined in test_script_validation.bats) when a test must reach code that runs after validate_github_token.
--dry-run— several scripts support it to preview changes without applying them.- Test org first — always run against a non-production GitHub org before production.
The full reference for script anatomy, shared library helpers, authentication, pagination, and adding new scripts lives in
.github/copilot-instructions.md. The sections below cover only what is unique to this guide.
- Pre-commit hook:
.githooks/pre-commit— runs gitleaks + shellcheck on staged.shfiles - Install:
./install-hooks.shorgit config core.hooksPath .githooks - Bypass (emergency only):
git commit --no-verify - CI: shellcheck + bats unit tests run together on every PR (
.github/workflows/ci.yml) - Releases: automated by Release Please (
.github/workflows/release.yml) — pushes tomaintrigger a release PR; merging it publishes the GitHub Release and tag
All commits must follow Conventional Commits. CHANGELOG.md is fully managed by Release Please and must never be edited manually.
| Prefix | Version bump | Visible in changelog |
|---|---|---|
feat: |
minor | ✅ Features |
fix: |
patch | ✅ Bug Fixes |
docs: |
patch | ✅ Documentation |
perf: |
patch | ✅ Performance Improvements |
revert: |
patch | ✅ Reverts |
feat!: / BREAKING CHANGE: |
major | ✅ |
chore: |
none | hidden |
ci: |
none | hidden |
refactor: |
patch | Code Refactoring |
test: |
none | hidden |
- Team slugs vs names: API uses slugs (lowercase, hyphenated). "Platform Team" →
platform-team - Bearer vs token auth: Enterprise endpoints need
Bearer; standard org/repo endpoints usetoken - Space-separated lists:
REPO_ADMINand similar accept multiple space-separated values — loop withfor item in ${VAR} - Comma-separated lists:
COLLABORATORS,REPO_NAMES,ORGSuse commas — split withIFS=',' - URL encoding: Labels in API calls must be URL-encoded (e.g.,
Linked [AC]→Linked%20[AC]) - Public repo filtering: Do not rely on
?type=publicfor enterprise-managed orgs — fetch all and filter injq - macOS vs Linux date:
github-archive-old-repos.shhandles both BSDdate -vand GNUdate -d set -euo pipefail: Must be the first executable line after the header — never omit it- Never edit
CHANGELOG.mdmanually — it is fully managed by Release Please via Conventional Commits