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=bashThere is no automated test suite. The validation approach is:
- Pre-commit hook — shellcheck on every staged
.shfile; gitleaks secret scan - Dry-run flags — several scripts support
--dry-runto preview changes without applying them:github-close-archived-repo-security-alertsgithub-enable-issuesgithub-organize-stars
- Test org first — always run against a non-production GitHub org before production
Every script begins with a # === header (exactly 79 = chars), then immediately set -euo pipefail:
#!/usr/bin/env bash
# =============================================================================
# github-<name>.sh
#
# <description>
#
# Usage:
# export GITHUB_TOKEN=ghp_yourtoken
# export ORG=my-org
# ./github-<name>.sh
#
# Environment variables:
# GITHUB_TOKEN Required. PAT with <scope> scope
# ORG Required. GitHub organization name
# API_URL_PREFIX Optional. GitHub API base URL (default: https://api.github.com)
#
# Requirements:
# - curl
# - jq
# =============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=../../lib/github-common.sh
source "${SCRIPT_DIR}/../../lib/github-common.sh"Always use SCRIPT_DIR to build the path to lib/github-common.sh. The library is two directory levels up from any script (../../lib/github-common.sh). Never hardcode absolute paths.
- Standard org/repo REST calls:
Authorization: token ${GITHUB_TOKEN} - Enterprise endpoints and GraphQL:
Authorization: Bearer ${GITHUB_TOKEN} - The
gh_apihelper inlib/github-common.shalways uses Bearer; pass"bearer"tovalidate_github_tokenfor enterprise scripts
| Function | Purpose |
|---|---|
print_status / print_success / print_warning / print_error |
Colored output |
require_env_var <VAR> |
Exit with message if variable unset/empty |
require_command <cmd> |
Exit if binary not in PATH |
configure_gh_auth [scope_hint] |
Bridge GITHUB_TOKEN→GH_TOKEN or verify gh auth session |
validate_github_token [bearer] |
Verify GITHUB_TOKEN via /user endpoint |
validate_slug <value> <label> |
Reject values with non-alphanumeric/hyphen/underscore chars |
gh_api <path> [curl args...] |
Bearer-auth REST helper with 5-retry rate-limit handling |
gh_api_paginate <path> [filter] [version] |
Paginated REST helper, follows Link headers, streams items |
get_enterprise_orgs |
Three-tier enterprise org resolver (REST → GraphQL → /user/orgs) |
get_repo_page_count <url> |
Returns total pages for a paginated endpoint |
require_env_varall required variablesvalidate_github_token(orvalidate_tokenfor secondary tokens)- Validate additional inputs with
validate_slugwhere needed - Proceed with main logic
Note on token auto-resolution: Sourcing
lib/github-common.shautomatically populatesGITHUB_TOKENfrom an activeghauth session if the variable is unset. This meansrequire_env_var GITHUB_TOKENmay pass even when no explicit token was provided by the caller — the token was silently resolved fromgh auth token. Script headers should documentGITHUB_TOKENas "Required (or provided by an active gh auth session)". Scripts that use theghCLI instead ofcurlshould callconfigure_gh_authinstead of step 2 above.
for PAGE in $(seq "$(get_repo_page_count "${API_URL_PREFIX}/orgs/${ORG}/repos?per_page=100")"); do
# process page
done- Repo-level operations (permission grants, archival):
sleep 5between each repo - Code search: configurable
SEARCH_SLEEP(default 2s) andCONTENT_SLEEP(default 1s) gh_apiauto-retries on HTTP 403/429 with 60s sleep
- Create the directory:
<domain>/github-<verb-noun>/ - Create the script:
github-<verb-noun>.sh(name must match the directory) - Copy the header template from Script Anatomy above — fill in description, all env vars, requirements
- Source the shared library using
SCRIPT_DIR - Validate all inputs before any API calls
- Create
action.ymlin the same directory — expose every env var as an input (required inputs first, optional inputs with defaults); map CLI flags (--dry-run,--type, etc.) to boolean/string inputs and construct theARGSarray in therun:step. See existingaction.ymlfiles for the pattern. - Add to README.md — follow the existing format: use case, env var table, usage example, output format; add a row to the Available Actions table in the "Using Scripts in GitHub Actions" section
- Place in the correct domain:
org-admin/— organization-level operations (repos, teams, members)enterprise/— enterprise-level operations (licenses, org enumeration)reporting/— read-only reports and auditspersonal/— personal GitHub utilities (stars, profile)
- 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 runs on all
.shfiles 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: |
none | hidden |
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