Skip to content

Commit c16f40a

Browse files
committed
Enhance README.md: Expand overview, add shared library and GitHub Actions integration details, and improve script descriptions
1 parent 388a364 commit c16f40a

2 files changed

Lines changed: 756 additions & 393 deletions

File tree

‎.github/copilot-instructions.md‎

Lines changed: 181 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,37 @@
22

33
## Project Overview
44

5-
This is a collection of **standalone bash automation scripts** for GitHub organization administration. Each script lives in its own directory with a single `.sh` file. Scripts use `curl` + `jq` to interact with the GitHub REST API v3.
5+
This is a collection of **standalone bash automation scripts** for GitHub organization and enterprise administration. Each script lives in its own directory with a single `.sh` file. Scripts use `curl` + `jq` to interact with the GitHub REST and GraphQL APIs. A shared utility library lives in `lib/github-common.sh`.
66

7-
**Key principle:** Scripts are independent, self-contained utilities—not a unified codebase. There's no shared library or common framework. Each script is a complete, self-validating executable that manages its own error handling and input validation.
7+
**Key principle:** Scripts are independent, self-contained utilities. Each script is a complete, self-validating executable that manages its own error handling and input validation. Scripts source `lib/github-common.sh` for shared validation and output helpers where applicable.
88

99
## Architecture Pattern
1010

1111
### Directory Structure
1212
```
1313
github-<script-name>/
1414
└── github-<script-name>.sh # Single executable script
15+
lib/
16+
└── github-common.sh # Shared utility functions
1517
```
1618

1719
Each script follows this exact naming convention. Scripts are NOT imported or called by each other.
1820

21+
### Shared Library: `lib/github-common.sh`
22+
23+
Provides reusable helpers sourced by scripts:
24+
25+
- **Color output:** `print_status` (blue INFO), `print_success` (green), `print_warning` (yellow), `print_error` (red, to stderr)
26+
- **Validation:** `require_env_var <VAR> [desc]` — exits with status 1 if empty; `require_command <cmd> [hint]` — exits if not in PATH
27+
- **Token validation:** `validate_token <VAR_NAME> [bearer]` — calls `/user` endpoint; `validate_github_token [bearer]` — convenience wrapper for `GITHUB_TOKEN`
28+
- **Pagination:** `get_repo_page_count <url>` — returns total page count from Link header
29+
1930
### Script Anatomy (Standardized Pattern)
2031

2132
All scripts follow this structure:
2233

2334
```bash
24-
#!/usr/bin/env /bin/bash
35+
#!/bin/bash
2536
set -euo pipefail # ALWAYS include: exit on error, undefined vars, pipe failures
2637

2738
### GLOBAL VARIABLES
@@ -31,25 +42,29 @@ API_URL_PREFIX=${API_URL_PREFIX:-'https://api.github.com'}
3142
GIT_URL_PREFIX=${GIT_URL_PREFIX:-'https://github.com'}
3243
# Script-specific required vars...
3344

34-
# Token validation block (if applicable)
45+
# Source shared library (when available)
46+
# SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
47+
# source "${SCRIPT_DIR}/../lib/github-common.sh"
48+
49+
# Token validation block
3550
# Helper functions for pagination/processing
3651
# Main execution logic
3752
```
3853

3954
**Critical patterns:**
4055
- Use `${VARIABLE:-''}` for all env vars to provide empty defaults
4156
- Set `API_URL_PREFIX` and `GIT_URL_PREFIX` for GitHub Enterprise Server compatibility
42-
- Include `sleep 5` between API calls to avoid rate limits (see `github-add-repo-permissions.sh` line 99)
43-
- Use pagination helpers for org-level operations (pattern in `github-add-repo-permissions.sh` lines 45-51)
44-
- **ALWAYS validate required variables** with explicit `if [ -z "${VAR}" ]` checks before any API calls
45-
- **ALWAYS validate GITHUB_TOKEN** by calling `/user` endpoint and checking for 200 status code
57+
- **ALWAYS validate required variables** before any API calls — use `require_env_var` from lib or explicit `if [ -z "${VAR}" ]` checks
58+
- **ALWAYS validate GITHUB_TOKEN** via `validate_github_token` (lib) or the `/user` endpoint pattern
4659
- Use `curl -s -o /dev/null -w "%{http_code}"` pattern to check HTTP status codes
4760
- Exit with status code 1 on validation failures with descriptive error messages
61+
- Rate limiting delays vary by script: `sleep 5` for per-repo team operations, `sleep 2` for archive operations, `sleep 0.5` or `sleep 0.2` for lighter patch operations
4862

4963
## GitHub API Patterns
5064

5165
### Pagination
52-
When iterating over all repos in an org, use this pattern:
66+
67+
For org-level repo iteration (REST):
5368
```bash
5469
get_repo_pagination () {
5570
repo_pages=$(curl -H "Authorization: token ${GITHUB_TOKEN}" -I "${API_URL_PREFIX}/orgs/${ORG}/repos?per_page=100" | grep -Eo '&page=[0-9]+' | grep -Eo '[0-9]+' | tail -1;)
@@ -61,105 +76,216 @@ for PAGE in $(seq "$(get_repo_pagination)"); do
6176
done
6277
```
6378

79+
For enterprise-level org iteration (GraphQL), use cursor-based pagination — see `github-add-enterprise-team-read-permissions.sh` and `github-dockerfile-discovery.sh` for examples.
80+
6481
### Rate Limiting
65-
**Always include `sleep 5`** between repository-level operations to stay under GitHub's rate limits. See `github-add-repo-permissions.sh` line 99 or `github-repo-from-template.sh` lines 77, 83.
82+
83+
Rate limiting delays are calibrated per operation type:
84+
- **Per-repo team permission assignments:** `sleep 5` (see `github-add-repo-permissions.sh`, `github-repo-from-template.sh`)
85+
- **Archive operations:** `sleep 2` (see `github-archive-old-repos.sh`)
86+
- **Lightweight patch operations:** `sleep 0.5` (see `github-enable-issues.sh`) or `sleep 0.2` (see `github-close-archived-repo-security-alerts.sh`)
87+
- **Code search + content fetch:** configurable `SEARCH_SLEEP` / `CONTENT_SLEEP` with exponential backoff (see `github-dockerfile-discovery.sh`, `github-get-public-repos.sh`)
6688

6789
### Authentication Headers
68-
- Use `Bearer` token for enterprise endpoints: `-H "Authorization: Bearer ${GITHUB_TOKEN}"`
69-
- Use `token` for standard API: `-H "Authorization: token ${GITHUB_TOKEN}"`
90+
91+
- `token` scheme for standard org/repo API: `-H "Authorization: token ${GITHUB_TOKEN}"`
92+
- `Bearer` scheme for enterprise endpoints and GraphQL: `-H "Authorization: Bearer ${GITHUB_TOKEN}"`
93+
- Some scripts use **both** (e.g., `github-dockerfile-discovery.sh`, `github-get-public-repos.sh`)
7094
- Include API version when specified: `-H "X-GitHub-Api-Version: 2022-11-28"`
7195

7296
### Accept Headers (Media Types)
97+
7398
Different endpoints require specific Accept headers:
7499
- Template generation: `application/vnd.github.baptiste-preview+json`
75100
- Timeline events: `application/vnd.github.mockingbird-preview+json`
76101
- Issue reactions: `application/vnd.github.mercy-preview+json`
77102
- Repository visibility: `application/vnd.github.nebula-preview+json`
78103
- Standard operations: `application/vnd.github.v3+json`
79104

105+
### Enterprise Org Lookup Strategy
106+
107+
Scripts that iterate across enterprise orgs (e.g., `github-dockerfile-discovery.sh`, `github-get-public-repos.sh`) use a three-tier fallback:
108+
1. REST `/enterprises/{slug}/organizations` (enterprise owner token)
109+
2. GraphQL `enterprise(slug).organizations` (enterprise member token)
110+
3. Fallback to `/user/orgs` (any `read:org` token)
111+
80112
### Error Handling Pattern
81-
All scripts follow this validation sequence:
113+
114+
Scripts follow this validation sequence:
82115
1. Check all required env vars are non-empty (exit with descriptive message if missing)
83116
2. Validate token by calling API `/user` endpoint (exit if not 200 status)
84117
3. Validate additional tokens if used (e.g., `CD_GITHUB_TOKEN`)
85118
4. Proceed with main logic only after all validations pass
86119

87120
## Script-Specific Behaviors
88121

122+
### `github-add-enterprise-team-read-permissions`
123+
- Assigns the "All-repository read" built-in org role to an enterprise team across all orgs in an enterprise
124+
- Uses **GraphQL** with cursor-based pagination to iterate enterprise organizations
125+
- Variables: `ENTERPRISE`, `ENTERPRISE_TEAM_SLUG`, `ALL_REPO_READ_ROLE_NAME` (default: `all_repo_read`)
126+
- Handles SAML SSO blocks gracefully (skips affected orgs with a warning)
127+
- Provides a summary of skipped and failed orgs at the end
128+
129+
### `github-add-repo-collaborators-by-pattern`
130+
- Adds individual GitHub users as collaborators to repositories whose names match a regex
131+
- Variables: `ORG`, `COLLABORATORS` (comma-separated usernames), `REPO_NAME_REGEX` (ERE regex)
132+
- Optional: `PERMISSION` (default: `push`), `REPO_EXCLUDE_REGEX`
133+
- Paginates through all org repos; applies both include and exclude regex filters
134+
- No explicit rate-limit delay between user additions
135+
89136
### `github-add-repo-permissions`
90137
- Grants team permissions to ALL repos in an org across 5 permission levels
91-
- Uses pagination + 5-second delay between repos
92138
- Variables: `REPO_ADMIN`, `REPO_MAINTAIN`, `REPO_PUSH`, `REPO_TRIAGE`, `REPO_PULL` (all accept **space-separated team slugs**)
93139
- At least one permission level variable must be set
140+
- Uses pagination + `sleep 5` delay between repos
94141
- Loops through teams with helper function `apply_team_permissions()`
95142

96-
### `github-repo-from-template`
97-
- `REPO_ADMIN` and `REPO_WRITE` are **space-separated lists** of team slugs
98-
- Automatically accepts collaborator invite using `CD_GITHUB_TOKEN` (a separate token for the CD user)
99-
- Permission mapping: `admin` = admin, `push` = write access
100-
- Retrieves invite ID via `/user/repository_invitations` and accepts via PATCH request
101-
- Creates repo with `private: true` and `include_all_branches: true` by default
143+
### `github-archive-old-repos`
144+
- Identifies repos not updated in the last N years (configurable via `YEARS_THRESHOLD`, default: `5`)
145+
- Generates a timestamped CSV report in `reports/old_repos_${TIMESTAMP}.csv`
146+
- **Interactive:** Prompts for confirmation before archiving; shows top 10 oldest repos first
147+
- Skips already-archived repos; uses `sleep 2` between archive operations
148+
- macOS (`date -v`) and Linux (`date -d`) compatible date arithmetic
149+
150+
### `github-auto-repo-creation`
151+
- Creates repos with branch protection, a `CODEOWNERS` file, and team assignments
152+
- Variables: `REPO_NAMES` (comma-separated), `REPO_OWNERS` (comma-separated team slugs with write), `ADMIN_TEAMS` (comma-separated team slugs with admin)
153+
- Creates repos as `private: true`, with issues enabled, no projects or wiki
154+
- Encodes CODEOWNERS content via `base64` for API upload
155+
- Enforces branch protection on `main`: admin enforcement, 1 required approval, code owner reviews required
156+
157+
### `github-close-archived-repo-security-alerts`
158+
- Dismisses Dependabot, code-scanning, and secret-scanning alerts on all archived repos in an org
159+
- Accepts CLI flags: `--type dependabot|code-scanning|secret-scanning|all` (default: all), `--dry-run`
160+
- Optional dismiss/resolve reasons: `DEPENDABOT_REASON`, `CODE_SCANNING_REASON`, `SECRET_SCANNING_RESOLUTION`
161+
- Generates timestamped CSV report in `reports/security_alerts_closed_${TIMESTAMP}.csv`
162+
- Uses `sleep 0.2` between alert closure operations
163+
164+
### `github-dockerfile-discovery`
165+
- Searches all orgs in a GitHub Enterprise for Dockerfiles; extracts base images from `FROM` instructions
166+
- Variables: `ENTERPRISE` (or `ORGS` to skip enterprise lookup), optional `ORG_FILTER` / `ORG_EXCLUDE` ERE regex
167+
- Configurable delays: `SEARCH_SLEEP` (default: `2`), `CONTENT_SLEEP` (default: `1`)
168+
- Parses multi-stage builds, `ARG` defaults, image digests, and `--platform` overrides
169+
- Generates two CSVs (detail + summary) and a human-readable TXT summary in `REPORT_DIR` (default: `./reports`)
170+
- Requires `base64` (file content decoding) and `python3` (URL-encoding)
171+
- Uses exponential backoff (up to 5 retries) for rate-limited requests
172+
173+
### `github-enable-issues`
174+
- Enables the Issues feature on every repo in an org that has it disabled
175+
- Accepts `--dry-run` flag
176+
- Skips archived repos and repos already with issues enabled
177+
- Uses `sleep 0.5` between patch operations
178+
- Reports four counters: enabled, errors, already-on, archived
179+
180+
### `github-get-consumed-licenses`
181+
- Queries enterprise seat license consumption
182+
- Uses **Bearer token** authentication; requires `read:enterprise` token scope
183+
- Calls `/enterprises/{ENTERPRISE}/consumed-licenses` endpoint
184+
- Outputs two metrics: seats consumed and seats purchased
185+
186+
### `github-get-public-repos`
187+
- Lists all public repos across every org in a GitHub Enterprise; writes a timestamped CSV
188+
- Variables: `ENTERPRISE` (or `ORGS` override), optional `ORG_FILTER` / `ORG_EXCLUDE`
189+
- Fetches all repos per org and filters by `visibility == "public"` in jq (avoids unreliable `?type=public` param)
190+
- Uses exponential backoff for rate-limited requests
191+
- Generates `public_repos_${TIMESTAMP}.csv` in `REPORT_DIR` (default: `./reports`)
192+
193+
### `github-get-repo-list`
194+
- Generates a CSV of all repos in an org with full metadata
195+
- Writes `repo-list.csv` (relative to working directory)
196+
- Makes an extra individual API call per repo to fetch full details — slow on large orgs
102197

103198
### `github-import-repo`
104-
- Performs **bare clone** + mirror push (full git history)
199+
- Imports a repo from one org to another using **bare clone + mirror push** (preserves full history)
105200
- Takes 2 positional args: `$1=SRC_REPO`, `$2=DEST_REPO`
106-
- Cleans up local `.git` directory after mirroring
107-
- Creates new repo with `visibility: internal`
108-
- Grants admin permissions to `OWNER_USERNAME` on destination repo
109-
- Requires git to be installed and accessible in PATH
201+
- Variables: `ORG` (destination), `OWNER_USERNAME` (granted admin on new repo)
202+
- Creates destination repo with `visibility: internal`
203+
- Uses `mktemp -d` for working directory; cleans up after push
204+
- Accepts HTTP 201 (created) or 422 (already exists) as success
205+
- Requires `git` in PATH
206+
207+
### `github-migrate-internal-repos-to-private`
208+
- Converts all internal repos in an org to private visibility
209+
- Queries only `?type=internal` repos; sends `visibility: private` PATCH to each
210+
- No rate-limit delay between updates
110211

111212
### `github-monthly-issues-report`
112-
- Generates HTML output to `output.txt` (not stdout)
113-
- Creates temporary `test.json` file (cleaned up at end)
114-
- Hardcoded label filter: `Linked%20[AC]` (URL-encoded)
115-
- Filters issues by creation date between `MONTH_START` and `MONTH_END`
116-
- Uses timeline API to track who applied labels
117-
- Aggregates statistics and sorts by count (descending)
213+
- Generates an HTML report of issues created during a specified month
214+
- Variables: `ORG`, `REPO`, `MONTH_START`, `MONTH_END` (ISO format `YYYY-MM-DD`)
215+
- **Hardcoded label filter:** `Linked%20[AC]` (URL-encoded `Linked [AC]`)
216+
- Uses timeline API (`mockingbird-preview` header) to track who applied labels
217+
- Output: `output.txt` (HTML, appended); temp file `test.json` (cleaned up)
218+
- Aggregates and sorts issue authors and label contributors by count (descending)
219+
220+
### `github-organize-stars`
221+
- Fetches all starred repos and organizes them into GitHub Lists by category rules
222+
- **Uses `gh` CLI** for authentication (not `GITHUB_TOKEN` env var)
223+
- Accepts flags: `--dry-run`, `-y`/`--yes`, `--show-repos`, `--no-cache`
224+
- Categorization via ordered rules (language, topics, name keywords); first match wins
225+
- Caches stars in `~/.cache/gh-star-organizer/stars.json`; batch-adds repos in groups of 25
226+
- Requires `gh` (GitHub CLI) and `jq`
118227

119-
### `github-get-consumed-licenses`
120-
- Uses **Bearer token** authentication (different from other scripts)
121-
- Requires enterprise-level token scope (`read:enterprise`)
122-
- Calls `/enterprises/{ENTERPRISE}/consumed-licenses` endpoint
123-
- Outputs two metrics: seats consumed and seats purchased
228+
### `github-repo-from-template`
229+
- Creates a new repo from a template, sets team permissions, and auto-accepts the CD user invite
230+
- Variables: `ORG`, `TEMPLATE_REPO`, `CD_USERNAME`, `CD_GITHUB_TOKEN`; positional arg: `REPO_NAME`
231+
- `REPO_ADMIN` and `REPO_WRITE` are **space-separated lists** of team slugs
232+
- Uses `baptiste-preview` Accept header for template generation API
233+
- Creates repo with `private: true` and `include_all_branches: true`
234+
- **Dual-token workflow:** Main token for creation; `CD_GITHUB_TOKEN` for auto-accepting invite
235+
- Polls `/user/repository_invitations` up to 12 times (60-second timeout) to find the invitation
236+
- Uses `sleep 5` between team permission assignments
124237

125238
## Development Guidelines
126239

127240
### Adding New Scripts
128241
1. Create directory: `github-<action-verb-object>/`
129242
2. Create script: `github-<action-verb-object>.sh` (match directory name)
130-
3. Start with the standard boilerplate (see Script Anatomy above)
131-
4. Document in README.md following existing format:
243+
3. Source `lib/github-common.sh` for validation and output helpers
244+
4. Start with the standard boilerplate (see Script Anatomy above)
245+
5. Document in README.md following existing format:
132246
- Use case description
133247
- Required variables table
134248
- Usage example with exports
135249
- Output format (if applicable)
136250

137251
### Testing Approach
138-
- **Always test on a test organization first** (mentioned in README tips)
139-
- Scripts have no built-in dry-run mode—handle with care
252+
- **Always test on a test organization first**
253+
- Several scripts support `--dry-run` — use it; others have no dry-run mode so handle with care
140254
- Use `2>&1 | tee execution.log` to capture output for auditing
141255

142256
### Variable Naming Conventions
143-
- `GITHUB_TOKEN` - main admin token
144-
- `ORG` - organization name
145-
- `ENTERPRISE` - enterprise account name
146-
- `REPO` - single repository name
147-
- `TEMPLATE_REPO` - source template repository
148-
- `CD_USERNAME` / `CD_GITHUB_TOKEN` - automation user credentials
149-
- `MONTH_START` / `MONTH_END` - ISO date format `YYYY-MM-DD`
257+
- `GITHUB_TOKEN` — main admin token
258+
- `ORG` — organization name
259+
- `ENTERPRISE` — enterprise account slug (from enterprise URL)
260+
- `ENTERPRISE_TEAM_SLUG` — enterprise team slug (without "ent:" prefix)
261+
- `REPO` — single repository name
262+
- `TEMPLATE_REPO` — source template repository
263+
- `CD_USERNAME` / `CD_GITHUB_TOKEN` — automation/CI user credentials
264+
- `MONTH_START` / `MONTH_END` — ISO date format `YYYY-MM-DD`
265+
- `ORGS` — comma-separated org list (overrides enterprise lookup)
266+
- `ORG_FILTER` / `ORG_EXCLUDE` — ERE regex for org name filtering
267+
- `REPORT_DIR` — output directory for report files (default: `./reports`)
150268

151269
### Dependencies
152270
- **bash 4+** (for modern features)
153-
- **curl** (API requests)
154-
- **jq** (JSON parsing - REQUIRED for all scripts)
155-
- **git** (only for `github-import-repo`)
271+
- **curl** (API requests — all scripts)
272+
- **jq** (JSON parsing — all scripts)
273+
- **git** (`github-import-repo` only)
274+
- **gh** (GitHub CLI — `github-organize-stars` only)
275+
- **base64** (`github-auto-repo-creation`, `github-dockerfile-discovery`)
276+
- **python3** (`github-dockerfile-discovery` — URL-encoding)
277+
- **date** (`github-archive-old-repos` — BSD/GNU compatible)
156278

157-
No other dependencies. Keep scripts dependency-minimal.
279+
Keep new scripts dependency-minimal; document any non-standard dependencies explicitly.
158280

159281
## Common Pitfalls
160282

161283
1. **Team slugs vs names:** GitHub API uses team slugs (lowercase, hyphenated). Example: "Platform Team" → "platform-team"
162-
2. **Enterprise vs Org endpoints:** License consumption requires enterprise-level token scopes
163-
3. **Git URL authentication:** When using `git push --mirror`, ensure token has repo scope
164-
4. **Space-separated lists:** Some variables like `REPO_ADMIN` accept multiple values separated by spaces (loop over them)
165-
5. **URL encoding:** Labels in API calls must be URL-encoded (`Linked [AC]` → `Linked%20[AC]`)
284+
2. **Enterprise vs Org endpoints:** License consumption and enterprise team roles require enterprise-level token scopes
285+
3. **Bearer vs token auth:** Enterprise endpoints and GraphQL use `Bearer`; standard REST org/repo endpoints use `token`
286+
4. **Git URL authentication:** When using `git push --mirror`, ensure token has `repo` scope
287+
5. **Space-separated lists:** Variables like `REPO_ADMIN` and `REPO_WRITE` accept multiple values separated by spaces — loop over them with `for team in ${REPO_ADMIN}; do`
288+
6. **Comma-separated lists:** Variables like `COLLABORATORS`, `REPO_NAMES`, and `ORGS` use commas — split with `IFS=','`
289+
7. **URL encoding:** Labels in API calls must be URL-encoded (`Linked [AC]` → `Linked%20[AC]`)
290+
8. **Public repo filtering:** Do not rely on `?type=public` for enterprise-managed orgs — fetch all and filter in jq
291+
9. **macOS vs Linux date:** `github-archive-old-repos.sh` handles both BSD `date -v` (macOS) and GNU `date -d` (Linux)

0 commit comments

Comments
 (0)