Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Bug Report
description: Report a broken or incorrect script behaviour
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to report a bug. Please fill in as much detail as possible so it can be reproduced and fixed quickly.

- type: textarea
id: description
attributes:
label: What happened?
description: A clear description of the incorrect behaviour.
validations:
required: true

- type: textarea
id: reproduce
attributes:
label: Steps to reproduce
description: Exact commands and environment variable exports used (redact the token value).
placeholder: |
export GITHUB_TOKEN=ghp_REDACTED
export ORG=my-org
./org-admin/github-get-repo-list/github-get-repo-list.sh
validations:
required: true

- type: textarea
id: expected
attributes:
label: Expected behaviour
description: What should the script have done?
validations:
required: true

- type: textarea
id: actual
attributes:
label: Actual output
description: Paste the full terminal output (redact any sensitive values).
render: shell
validations:
required: true

- type: input
id: script
attributes:
label: Affected script
description: Which script is affected?
placeholder: org-admin/github-get-repo-list/github-get-repo-list.sh
validations:
required: true

- type: dropdown
id: target
attributes:
label: GitHub target
options:
- github.com
- GitHub Enterprise Server (GHES)
- GitHub Enterprise Cloud (GHEC)
validations:
required: true

- type: textarea
id: environment
attributes:
label: Environment
description: OS, bash version, jq version
placeholder: |
OS: macOS 14 / Ubuntu 22.04
bash: 5.2
jq: 1.7
validations:
required: false
47 changes: 47 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Feature Request
description: Suggest a new script or an improvement to an existing one
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Got an idea for a new script or a useful improvement? Describe it below.

- type: textarea
id: problem
attributes:
label: What problem does this solve?
description: Describe the GitHub admin task you're trying to automate.
validations:
required: true

- type: textarea
id: solution
attributes:
label: Proposed solution
description: Describe the script or change you'd like to see, including what env vars it would accept and what it would output.
validations:
required: true

- type: dropdown
id: domain
attributes:
label: Script domain
description: Which folder would this belong in?
options:
- org-admin
- enterprise
- reporting
- personal
- lib (shared utility)
- not sure
validations:
required: false

- type: textarea
id: alternatives
attributes:
label: Alternatives considered
description: Any other approaches or existing tools you evaluated?
validations:
required: false
34 changes: 34 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## Description

<!-- What does this PR do? Why is it needed? -->

## Changes

<!-- List the scripts added, modified, or removed. -->

- [ ] New script: `<domain>/github-<name>/github-<name>.sh`
- [ ] Modified: `lib/github-common.sh`
- [ ] Other:

## How to test

```bash
export GITHUB_TOKEN=ghp_REDACTED
export ORG=test-org
./path/to/script.sh [--dry-run]
```

<!-- Describe the expected output and any edge cases tested. -->

## Checklist

- [ ] `set -euo pipefail` is the first executable line after the `# ===` header
- [ ] Script sources `lib/github-common.sh` via `SCRIPT_DIR`
- [ ] All required env vars validated with `require_env_var`
- [ ] Token validated with `validate_github_token` (or `validate_token` for secondary tokens)
- [ ] User-supplied slugs validated with `validate_slug`
- [ ] `sleep` added between repo-level operations to respect rate limits
- [ ] README.md updated with the new/changed script (env var table + usage example)
- [ ] Script header comment matches README documentation
- [ ] Tested on a non-production org before production
- [ ] shellcheck passes (`shellcheck --severity=warning --exclude=SC2034,SC1091 --shell=bash <script>`)
75 changes: 75 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,24 @@ For enterprise-level org iteration (GraphQL), use cursor-based pagination — se
### Rate Limiting

Rate limiting delays are calibrated per operation type:
- **Repo-level operations** (permission grants, archival): `sleep 5` between each repository.
- **Code search** (`github-dockerfile-discovery`): configurable via `SEARCH_SLEEP` (default 2 s) and `CONTENT_SLEEP` (default 1 s).
- **gh_api helper** (lib): auto-retries up to 5 times on HTTP 403/429, sleeping 60 s before each retry.

### Authentication Headers

- **Standard org/repo REST endpoints**: `Authorization: token ${GITHUB_TOKEN}`
- **Enterprise endpoints and GraphQL**: `Authorization: Bearer ${GITHUB_TOKEN}`
- **`gh_api` helper** (lib): always uses `Bearer`; pass `"bearer"` to `validate_github_token` for enterprise-level scripts.
- **`validate_github_token`** wrapper: call without arguments for token auth, or pass `"bearer"` for Bearer auth.

### Accept Headers (Media Types)

Different endpoints require specific Accept headers:
- **Standard REST**: `Accept: application/vnd.github+json` (used by `gh_api` helper and newer scripts)
- **Legacy REST** (older scripts): `Accept: application/vnd.github.v3+json`
- **GraphQL**: no Accept header required; POST to `/graphql` with `Content-Type: application/json`
- **All modern API calls**: include `X-GitHub-Api-Version: 2022-11-28` (or `2026-03-10` for Copilot usage-metrics endpoints)

### Enterprise Org Lookup Strategy

Expand All @@ -126,36 +137,80 @@ Scripts follow this validation sequence:

### `github-add-enterprise-team-read-permissions`

Uses GraphQL cursor-based pagination to enumerate all enterprise orgs, then assigns the `all_repo_read` org role (configurable via `ALL_REPO_READ_ROLE_NAME`) to the enterprise team in each org. Requires Bearer token authentication with `admin:enterprise` scope. The `ENTERPRISE_TEAM_SLUG` is the slug without the `"ent:"` prefix.

### `github-add-repo-collaborators-by-pattern`

Adds individual collaborators to org repos whose names match `REPO_NAME_REGEX` (ERE). Accepts a comma-separated `COLLABORATORS` list and an optional `REPO_EXCLUDE_REGEX` to skip matching repos. Uses `token` auth for all REST calls. Iterates all org repos paginated at 100 per page.

### `github-add-repo-permissions`

Grants team permissions across all repos in an org. Reads five space-separated team-slug lists (`REPO_ADMIN`, `REPO_MAINTAIN`, `REPO_PUSH`, `REPO_TRIAGE`, `REPO_PULL`). Optionally filters repos by name prefix via `REPO_NAME_FILTER`. Sleeps 5 s between each repo to stay within rate limits. Uses legacy `Accept: application/vnd.github.v3+json` and `token` auth.

### `github-archive-old-repos`

Calculates a cutoff date based on `YEARS_THRESHOLD` (default 5). Generates a timestamped CSV report under `reports/`, shows the top 10 oldest repos, and prompts for confirmation before archiving. Handles both BSD (`date -v`, macOS) and GNU (`date -d`, Linux) date syntax for cross-platform compatibility.

### `github-auto-repo-creation`

Creates private repos from a comma-separated `REPO_NAMES` list. Configures branch protection on the default branch, creates a CODEOWNERS file (Base64-encoded via API), and grants admin access to comma-separated `ADMIN_TEAMS`. All slug values in `REPO_NAMES`, `ADMIN_TEAMS`, and `REPO_OWNERS` are validated before use. Requires `base64` in addition to `curl` and `jq`.

### `github-close-archived-repo-security-alerts`

Dismisses open Dependabot, code-scanning, and secret-scanning alerts on all archived repos. Supports `--type` to target a single alert type and `--dry-run` to preview without changes. Generates a timestamped CSV report under `reports/`. Requires a token with `security_events` and `repo` scopes.

### `github-dockerfile-discovery`

Uses GitHub code-search API to find Dockerfiles across all enterprise orgs, then fetches and parses each file to extract `FROM` instructions (including multi-stage builds). Produces three timestamped reports in `REPORT_DIR`: a detail CSV, a summary CSV, and a plain-text summary. Supports `ORGS` override, `ORG_FILTER`/`ORG_EXCLUDE` regex filters (validated as syntactically correct ERE before use), and configurable sleep intervals (`SEARCH_SLEEP`, `CONTENT_SLEEP`).

### `github-enable-issues`

Iterates all non-archived repos in an org and enables the Issues feature on any repo where it is disabled. Supports `--dry-run` to list affected repos without making changes.

### `github-get-consumed-licenses`

Calls the enterprise consumed-licenses endpoint using Bearer authentication. Requires `read:enterprise` scope. Returns seat consumption and purchase counts. Token-only script; no pagination or retry logic beyond what the single API call provides.

### `github-get-public-repos`

Discovers all enterprise orgs (three-tier fallback via `get_enterprise_orgs`), fetches all repos in each, and filters to public visibility in `jq` (does not rely on `?type=public` query param, which is unreliable for enterprise-managed orgs). Writes a timestamped CSV to `REPORT_DIR`. Supports `ORGS` override and `ORG_FILTER`/`ORG_EXCLUDE` ERE regex filters.

### `github-get-repo-list`

Outputs a CSV row per repository (full name, owner, visibility, URL, description, fork flag, pushed/created/updated timestamps) to stdout. Pagination handled at 100 repos per page via `get_repo_page_count`.

### `github-import-repo`

Performs a full bare clone (`git clone --mirror`) of a source repo and pushes all branches, tags, and history to a new private destination repo. Validates `GIT_URL_PREFIX` against a GitHub-host allowlist before running any git operations to prevent credential leakage. Repo names and `OWNER_USERNAME` are validated as slugs.

### `github-migrate-internal-repos-to-private`

Fetches all repos with internal visibility (paginated) and converts each to private via PATCH. Logs success or failure per repo. This operation cannot be undone via the API — converting internal to private removes access from members of other enterprise orgs.

### `github-monthly-issues-report`

Generates an HTML report of issues created in a date range (`MONTH_START`/`MONTH_END`), filtered by a hardcoded label (`Linked [AC]` — edit the script to change). Uses the timeline API to track who applied labels. URL-encodes labels for API calls (e.g., `Linked [AC]` → `Linked%20[AC]`). Outputs to `output.txt`.

### `github-organize-stars`

Uses `gh` CLI GraphQL (not `curl`) to fetch all starred repos. Categorizes by primary language, GitHub topics, and name keywords using a `RULES` array (pipe-delimited: `List Name|LANGUAGES|TOPICS|NAME_KEYWORDS`; first matching rule wins). Caches stars at `~/.cache/gh-star-organizer/stars.json`. Supports `--dry-run`, `-y` (skip confirm), `--show-repos`, and `--no-cache`. Adds repos to Lists in batches of 25.

### `github-repo-from-template`

Creates a private repo from `TEMPLATE_REPO` (including all branches), assigns admin permissions to space-separated `REPO_ADMIN` teams, write permissions to `REPO_WRITE` teams, then invites `CD_USERNAME` as a collaborator using `CD_GITHUB_TOKEN` and auto-accepts the invitation. All slug values are validated before use.

### `github-repo-permissions-report`

Uses `gh` CLI (not `curl`/`GITHUB_TOKEN`) for all API calls. Accepts `-r OWNER/REPO`, `-b BRANCH`, and `-o FILE` flags. Outputs a CSV with two record types: `permission` (all users/teams) and `bypass_actor` (explicit PR approval bypass entries). Reports both branch protection rules and repository rulesets. Requires `gh auth login` before running — no `GITHUB_TOKEN` env var needed.

### `github-copilot-report`

Uses `gh` CLI (not `curl`/`GITHUB_TOKEN`) for all GitHub API calls; requires `gh auth refresh --scopes "read:enterprise,manage_billing:enterprise"`. Also requires `az` to be **installed** (not just logged in) — the script calls `require_command az` unconditionally before checking `--no-entra`. When `az` is logged in, enriches each user with Entra ID department and job title via `az rest`.

Auto-detects credits per seat from a promo/standard table keyed on plan type and today's date (promo period Jun 1 – Sep 1, 2026); override with `--credits N` or `$CREDITS_PER_SEAT_OVERRIDE`. Credits are pooled enterprise-wide, not per-user buckets. Code completions are not billed in AI credits.

Uses API version `2026-03-10` and the new usage-metrics NDJSON endpoints (signed download links). The legacy `/copilot/metrics` and `/copilot/usage` endpoints were closed Apr 2, 2026.

## Development Guidelines

### Adding New Scripts
Expand Down Expand Up @@ -188,3 +243,23 @@ Keep new scripts dependency-minimal; document any non-standard dependencies expl
7. **URL encoding:** Labels in API calls must be URL-encoded (`Linked [AC]` → `Linked%20[AC]`)
8. **Public repo filtering:** Do not rely on `?type=public` for enterprise-managed orgs — fetch all and filter in jq
9. **macOS vs Linux date:** `github-archive-old-repos.sh` handles both BSD `date -v` (macOS) and GNU `date -d` (Linux)

## Maintenance Matrix

When you change one of these files, you must also update the files in the "Also update" column.

| When you change… | Also update |
|------------------|-------------|
| `lib/github-common.sh` — any public function signature or behaviour | All 19 scripts that source it; verify each caller still passes the right arguments. Check with: `grep -r "source.*github-common" . --include="*.sh"` |
| `lib/github-common.sh` — add a new helper function | `AGENTS.md` shared library table; `README.md` if the function affects usage |
| Any script's required env vars | That script's `# ===` header comment; the corresponding README.md section's env var table |
| Any script's optional env vars or defaults | Same as above |
| Any script's `--dry-run` or CLI flag behaviour | README.md usage example for that script |
| `README.md` — script documentation | Verify the script's `# ===` header comment still matches (env vars, options, requirements) |
| `.githooks/pre-commit` | `install-hooks.sh` if hook path or installation instructions change; README.md Best Practices section |
| `install-hooks.sh` | README.md Installation section |
| Add a new script | `README.md` (add use case, env var table, usage example); `CHANGELOG.md` under `[Unreleased]` |
| Add a new domain folder | `README.md` top-level structure description; `AGENTS.md` Repository Structure section |
| `.github/workflows/ci.yml` — shellcheck flags | `.githooks/pre-commit` shellcheck invocation (keep them in sync) |
| `.github/workflows/copilot-setup-steps.yml` — tool versions | `AGENTS.md` Tech Stack table |
| `AGENTS.md` | No cascade — but keep in sync with `copilot-instructions.md` if architecture changes |
8 changes: 8 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
commit-message:
prefix: chore
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: CI

on:
pull_request:
paths-ignore:
- '**.md'
- 'LICENSE'
- '.github/FUNDING.yml'
push:
branches:
- main
paths-ignore:
- '**.md'
- 'LICENSE'
- '.github/FUNDING.yml'

jobs:
shellcheck:
name: Shellcheck
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Install shellcheck
run: sudo apt-get update -qq && sudo apt-get install -y shellcheck

- name: Run shellcheck on all scripts
run: |
find . -name "*.sh" -not -path "./.git/*" | sort | xargs shellcheck \
--severity=warning \
--exclude=SC2034,SC1091 \
--shell=bash
29 changes: 29 additions & 0 deletions .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Copilot Setup Steps

on:
workflow_dispatch:

jobs:
copilot-setup-steps:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Install jq
run: sudo apt-get update -qq && sudo apt-get install -y jq

- name: Install shellcheck
run: sudo apt-get install -y shellcheck

- name: Install gitleaks
run: |
GITLEAKS_VERSION=$(curl -s https://api.github.com/repos/gitleaks/gitleaks/releases/latest | jq -r .tag_name)
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION#v}_linux_x64.tar.gz" \
| sudo tar -xz -C /usr/local/bin gitleaks

- name: Verify tools
run: |
jq --version
shellcheck --version
gitleaks version
9 changes: 9 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"mcpServers": {
"github": {
"command": "gh",
"args": ["mcp", "serve"],
"env": {}
}
}
}
Loading
Loading