From 76cf387d79a3f8158ca176ac8808d214398c1ca0 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 21 Oct 2025 10:21:48 -0500 Subject: [PATCH 1/6] feat: add scripts for GitHub App Manifest flow - Create Python script to generate JWT for GitHub App authentication. - Add Bash script to create GitHub App from manifest code. - Implement README for GitHub App Manifest scripts with detailed usage instructions. - Introduce slim version of the create GitHub App from manifest script. - Generate HTML form for GitHub App Manifest flow with local server instructions. - Add example HTML form demonstrating the GitHub App Manifest process. - Create main HTML form for GitHub App Manifest with automatic code handling. --- gh-cli/github-app-manifest-form.html | 155 ++++++++++++ scripts/README.md | 53 +++- scripts/{get-app-jwt.py => create-app-jwt.py} | 0 scripts/create-app-jwt.sh | 38 +++ scripts/github-app-manifest-scripts/README.md | 236 ++++++++++++++++++ .../create-github-app-from-manifest-slim.sh | 60 +++++ .../create-github-app-from-manifest.sh | 146 +++++++++++ .../generate-github-app-manifest-form.sh | 212 ++++++++++++++++ .../github-app-manifest-form-example.html | 160 ++++++++++++ .../github-app-manifest-form.html | 155 ++++++++++++ 10 files changed, 1206 insertions(+), 9 deletions(-) create mode 100644 gh-cli/github-app-manifest-form.html rename scripts/{get-app-jwt.py => create-app-jwt.py} (100%) create mode 100755 scripts/create-app-jwt.sh create mode 100644 scripts/github-app-manifest-scripts/README.md create mode 100755 scripts/github-app-manifest-scripts/create-github-app-from-manifest-slim.sh create mode 100755 scripts/github-app-manifest-scripts/create-github-app-from-manifest.sh create mode 100755 scripts/github-app-manifest-scripts/generate-github-app-manifest-form.sh create mode 100644 scripts/github-app-manifest-scripts/github-app-manifest-form-example.html create mode 100644 scripts/github-app-manifest-scripts/github-app-manifest-form.html diff --git a/gh-cli/github-app-manifest-form.html b/gh-cli/github-app-manifest-form.html new file mode 100644 index 0000000..fc45977 --- /dev/null +++ b/gh-cli/github-app-manifest-form.html @@ -0,0 +1,155 @@ + + + + GitHub App Manifest Test + + + +
+

GitHub App Manifest Test

+ +
+ What happens next: +
    +
  1. Start a local server (see instructions below)
  2. +
  3. Click "Create GitHub App" below
  4. +
  5. GitHub will redirect you to review and create the app
  6. +
  7. After creation, GitHub redirects back with a temporary code
  8. +
  9. The code will be automatically displayed and you can copy it
  10. +
+
+ +
+ 🚀 How to start local server (click to expand) + +
+ +
+ ⚠️ Important Notes (click to expand) + +
+ + + + +
+

App Manifest Configuration:

+ +

+ +
+ +
+ 📋 How to use the manifest code (click to expand) + +
+
+ + + + diff --git a/scripts/README.md b/scripts/README.md index 9821c6c..cf9cbe1 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -36,15 +36,7 @@ Configuration values to change in the script: Migrate work items from Azure DevOps to GitHub issues - this just links out to a [separate repo](https://github.com/joshjohanning/ado_workitems_to_github_issues) -## delete-branch-protection-rules.ps1 - -Delete branch protection rules programmatically based on a pattern. - -## gei-clean-up-azure-storage-account.sh - -Clean up Azure Storage Account Containers from GEI migrations. - -## get-app-jwt.py +## create-app-jwt.py This script will generate a JWT for a GitHub App. It will use the private key and app ID from the GitHub App's settings page. @@ -55,6 +47,29 @@ This script will generate a JWT for a GitHub App. It will use the private key an Script sourced from [GitHub docs](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app#example-using-python-to-generate-a-jwt). +## create-app-jwt.sh + +Generate a JWT (JSON Web Token) for a GitHub App using bash. This is a shell script alternative to `create-app-jwt.py`. + +Usage: + +```bash +./create-app-jwt.sh +``` + +The script generates a JWT that is valid for 10 minutes, which can be used to authenticate as a GitHub App and obtain installation tokens. + +> [!NOTE] +> Requires `openssl` to be installed. The JWT can be used with the GitHub API to generate installation access tokens. + +## delete-branch-protection-rules.ps1 + +Delete branch protection rules programmatically based on a pattern. + +## gei-clean-up-azure-storage-account.sh + +Clean up Azure Storage Account Containers from GEI migrations. + ## get-app-tokens-for-each-installation.sh This script will generate generate a JWT for a GitHub app and use that JWT to generate installation tokens for each org installation. The installation tokens, returned as `ghs_abc`, can then be used for normal API calls. It will use the private key and app ID from the GitHub App's settings page and the `get-app-jwt.py` script to generate the JWT, and then use the JWT to generate the installation tokens for each org installation. @@ -94,6 +109,26 @@ My use case is to use this list to determine who needs to be added to a organiza 1. Run: `./new-users-to-add-to-project.sh ` 2. Don't delete the `` as it functions as your user database +## github-app-manifest-scripts + +Scripts to create GitHub Apps using the [GitHub App Manifest flow](https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest). The manifest flow allows you to create a GitHub App by posting a JSON manifest to GitHub, which then generates the app credentials. + +**Workflow:** + +1. **Generate HTML form**: Run `generate-github-app-manifest-form.sh [organization]` to create an HTML file +2. **Start local server**: Serve the HTML file (e.g., `python3 -m http.server 8000`) +3. **Create app via browser**: Open the form, submit it to GitHub, and get redirected back with a manifest code +4. **Convert manifest code**: Use `create-github-app-from-manifest.sh` to convert the code into app credentials + +**Scripts:** + +- **generate-github-app-manifest-form.sh**: Generates an HTML form for the manifest flow +- **create-github-app-from-manifest.sh**: Converts manifest code into app credentials with detailed output and error handling +- **github-app-manifest-form-example.html**: Example HTML form (generated output) + +> [!NOTE] +> Requires `curl`, `jq`, and a classic GitHub personal access token (`ghp_*`). Fine-grained tokens are not supported for the manifest conversion endpoint. + ## migrate-discussions See: [migrate-discussions](./migrate-discussions/README.md) diff --git a/scripts/get-app-jwt.py b/scripts/create-app-jwt.py similarity index 100% rename from scripts/get-app-jwt.py rename to scripts/create-app-jwt.py diff --git a/scripts/create-app-jwt.sh b/scripts/create-app-jwt.sh new file mode 100755 index 0000000..9f778bf --- /dev/null +++ b/scripts/create-app-jwt.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +set -o pipefail +client_id=$1 # Client ID as first argument + +pem=$( cat $2 ) # file path of the private key as second argument + +now=$(date +%s) +iat=$((${now} - 60)) # Issues 60 seconds in the past +exp=$((${now} + 600)) # Expires 10 minutes in the future + +b64enc() { openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n'; } + +header_json='{ + "typ":"JWT", + "alg":"RS256" +}' +# Header encode +header=$( echo -n "${header_json}" | b64enc ) + +payload_json="{ + \"iat\":${iat}, + \"exp\":${exp}, + \"iss\":\"${client_id}\" +}" +# Payload encode +payload=$( echo -n "${payload_json}" | b64enc ) + +# Signature +header_payload="${header}"."${payload}" +signature=$( + openssl dgst -sha256 -sign <(echo -n "${pem}") \ + <(echo -n "${header_payload}") | b64enc +) + +# Create JWT +JWT="${header_payload}"."${signature}" +printf '%s\n' "$JWT" diff --git a/scripts/github-app-manifest-scripts/README.md b/scripts/github-app-manifest-scripts/README.md new file mode 100644 index 0000000..8ae661e --- /dev/null +++ b/scripts/github-app-manifest-scripts/README.md @@ -0,0 +1,236 @@ +# GitHub App Manifest Scripts + +Scripts for creating GitHub Apps using the [GitHub App Manifest flow](https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest). The manifest flow is a streamlined way to create GitHub Apps by posting a JSON configuration to GitHub, which automatically generates the app and returns credentials including a private key. + +## Overview + +The GitHub App Manifest flow allows you to: + +- Create GitHub Apps programmatically without manual configuration +- Automatically generate and receive private keys +- Set up apps for users or organizations through a web-based flow +- Simplify GitHub App creation for testing or development environments + +## Workflow + +### 1. Generate HTML Form + +Create an HTML form that will initiate the manifest flow: + +```bash +./generate-github-app-manifest-form.sh [organization] +``` + +**For an organization:** + +```bash +./generate-github-app-manifest-form.sh my-org +``` + +**For a personal account:** + +```bash +./generate-github-app-manifest-form.sh +``` + +This creates a `github-app-manifest-form.html` file with: + +- Pre-configured manifest JSON +- Instructions for local server setup +- Automatic code capture when GitHub redirects back +- Dropdown sections with detailed usage instructions + +### 2. Serve the HTML File + +The HTML file must be served over HTTP (not opened as `file://`). Start a local server: + +```bash +# Python 3 +python3 -m http.server 8000 + +# Python 2 +python -m SimpleHTTPServer 8000 + +# Node.js +npx http-server -p 8000 + +# PHP +php -S localhost:8000 +``` + +Then open: `http://localhost:8000/github-app-manifest-form.html` + +### 3. Create the App + +1. Click "Create GitHub App from Manifest" in the form +2. GitHub redirects you to review the app configuration +3. Click "Create GitHub App" on GitHub's page +4. GitHub redirects back to your local server with a manifest code in the URL +5. The code is automatically displayed in the success message + +### 4. Convert Manifest Code + +Use the manifest code to retrieve your app credentials: + +```bash +export GITHUB_TOKEN=ghp_abc # Classic PAT required +./create-github-app-from-manifest.sh +``` + +The script will: + +- Convert the manifest code into app credentials +- Display the app ID, client ID, client secret, and webhook secret +- Automatically save the private key to a file: `..private-key.pem` + +## Scripts + +### generate-github-app-manifest-form.sh + +Generates an interactive HTML form for the GitHub App Manifest flow. + +**Usage:** + +```bash +./generate-github-app-manifest-form.sh [organization] +``` + +**Features:** + +- Creates customized form for organization or personal account +- Includes collapsible sections with detailed instructions +- Automatically captures and displays the manifest code on redirect +- Provides copy-paste commands for next steps + +**Output:** `github-app-manifest-form.html` + +### create-github-app-from-manifest.sh + +Converts a manifest code into GitHub App credentials with detailed output and error handling. + +**Usage:** + +```bash +export GITHUB_TOKEN=ghp_abc +./create-github-app-from-manifest.sh +``` + +**Features:** + +- Comprehensive error checking and validation +- Detailed explanations of what a manifest code is +- Formatted JSON output with all app details +- Automatic private key file creation +- Helpful instructions for next steps + +**Requirements:** + +- `curl` +- `jq` +- Classic GitHub personal access token (fine-grained tokens not supported) + +**Output:** + +- JSON with app ID, client ID, secrets, permissions, and events +- Private key file: `..private-key.pem` + +### github-app-manifest-form-example.html + +Example of the generated HTML form. This file demonstrates what the `generate-github-app-manifest-form.sh` script creates. + +**Note:** This is a sample output file and should not be edited directly. Regenerate it using the shell script. + +## Requirements + +- **curl**: For API requests +- **jq**: For JSON parsing +- **Classic GitHub PAT**: Fine-grained tokens are not supported for the manifest conversion endpoint + - Token must start with `ghp_` + - No specific scopes required for the conversion endpoint +- **Local HTTP server**: To serve the HTML form (Python, Node.js, PHP, etc.) + +## Complete Example + +```bash +# Step 1: Generate the form +./generate-github-app-manifest-form.sh my-org + +# Step 2: Start a local server +python3 -m http.server 8000 + +# Step 3: Open in browser +# Visit: http://localhost:8000/github-app-manifest-form.html +# Click "Create GitHub App from Manifest" +# Complete the flow on GitHub +# Get redirected back with code (e.g., code=a180b1a3d263c81bc6441d7b990bae27d4c10679) + +# Step 4: Convert the code to credentials +export GITHUB_TOKEN=ghp_your_classic_token_here +./create-github-app-from-manifest.sh a180b1a3d263c81bc6441d7b990bae27d4c10679 + +# Output: JSON with credentials + my-app.2024-01-15.private-key.pem +``` + +## Important Notes + +- **Manifest codes expire in 1 hour** and can only be used once +- **Fine-grained tokens are NOT supported** - you must use a classic personal access token +- The private key can only be retrieved during this conversion - save it securely +- Client secret and webhook secret are also only shown once +- The HTML form must be served over HTTP, not opened as a local file + +## Customizing the Manifest + +To customize the app configuration, edit the `manifest` object in the generated HTML file before submitting: + +```javascript +const manifest = { + name: 'My Custom App', + url: 'https://www.example.com', + hook_attributes: { + url: 'https://example.com/github/events' + }, + redirect_url: redirectUrl, + callback_urls: [redirectUrl], + public: false, + default_permissions: { + metadata: 'read', + contents: 'write', + issues: 'write', + pull_requests: 'write' + }, + default_events: ['issues', 'pull_request', 'push'] +}; +``` + +See the [GitHub App Manifest documentation](https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest#github-app-manifest-parameters) for all available parameters. + +## Troubleshooting + +### "Fine-grained tokens are not supported" + +The manifest conversion endpoint only accepts classic personal access tokens (starting with `ghp_`). Create one at: Settings → Developer settings → Personal access tokens → Tokens (classic) + +### "No parser could be inferred for file" + +Make sure `jq` is installed: + +```bash +brew install jq # macOS +apt-get install jq # Ubuntu/Debian +yum install jq # CentOS/RHEL +``` + +### "Error: manifest code is required" + +The manifest code comes from GitHub after completing the web flow. You cannot generate it manually. + +### Form doesn't work when opened as file:// + +The HTML form must be served over HTTP. Use one of the local server commands provided in the workflow section. + +## References + +- [GitHub App Manifest Flow Documentation](https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest) +- [Create a GitHub App from a manifest API](https://docs.github.com/en/rest/apps/apps#create-a-github-app-from-a-manifest) +- [GitHub App Manifest Parameters](https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest#github-app-manifest-parameters) diff --git a/scripts/github-app-manifest-scripts/create-github-app-from-manifest-slim.sh b/scripts/github-app-manifest-scripts/create-github-app-from-manifest-slim.sh new file mode 100755 index 0000000..cb66d5f --- /dev/null +++ b/scripts/github-app-manifest-scripts/create-github-app-from-manifest-slim.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# Create a GitHub App from a manifest +# Converts a temporary manifest code into GitHub App credentials +# +# Usage: ./create-github-app-from-manifest.sh +# Example: ./create-github-app-from-manifest.sh a180b1a3d263c81bc6441d7b990bae27d4c10679 +# +# Requires: GITHUB_TOKEN environment variable with a valid CLASSIC GitHub token +# The manifest code is generated by GitHub during the App Manifest flow. +# See: https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest + +set -e + +if [ $# -eq 0 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Check for GitHub token +if [ -z "$GITHUB_TOKEN" ]; then + echo "Error: GITHUB_TOKEN environment variable is required" + echo "Set it with: export GITHUB_TOKEN=your_token_here" + exit 1 +fi + +# Check if it's a classic token (starts with ghp_) +if [[ ! "$GITHUB_TOKEN" =~ ^ghp_ ]]; then + echo "Error: GITHUB_TOKEN must be a classic personal access token (starts with 'ghp_')" + echo "Fine-grained tokens are not supported for this endpoint" + exit 1 +fi + +MANIFEST_CODE="$1" + +echo "Converting manifest code: $MANIFEST_CODE" + +# Make the API call +RESPONSE=$(curl -s -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/app-manifests/$MANIFEST_CODE/conversions") + +# Check for errors +if echo "$RESPONSE" | jq -e '.message' > /dev/null 2>&1; then + echo "Error: $(echo "$RESPONSE" | jq -r '.message')" + exit 1 +fi + +# Save PEM to file +APP_NAME=$(echo "$RESPONSE" | jq -r '.name // .slug' | tr ' ' '_' | tr '[:upper:]' '[:lower:]') +PEM_FILE="${APP_NAME}.$(date +%Y-%m-%d).private-key.pem" +echo "$RESPONSE" | jq -r '.pem' > "$PEM_FILE" + +# Display all response data +echo "$RESPONSE" | jq . + +echo "" +echo "✅ Private key saved to: $PEM_FILE" diff --git a/scripts/github-app-manifest-scripts/create-github-app-from-manifest.sh b/scripts/github-app-manifest-scripts/create-github-app-from-manifest.sh new file mode 100755 index 0000000..296d09e --- /dev/null +++ b/scripts/github-app-manifest-scripts/create-github-app-from-manifest.sh @@ -0,0 +1,146 @@ +#!/bin/bash + +# Create a GitHub App from a manifest +# This script converts a temporary manifest code into GitHub App credentials +# Uses curl instead of GitHub CLI for better portability +# +# WHAT IS A MANIFEST CODE? +# The manifest code is a temporary code generated by GitHub during the App Manifest flow. +# It's NOT something you create yourself. Here's how to get one: +# +# 1. Create an HTML form that posts to GitHub's manifest endpoint with your app configuration +# 2. User clicks "Create GitHub App" +# 3. GitHub redirects back to your site with a temporary code parameter +# 4. Use that code with this script to get the actual app credentials +# +# Usage: ./create-github-app-from-manifest.sh +# +# Example: ./create-github-app-from-manifest.sh a180b1a3d263c81bc6441d7b990bae27d4c10679 +# +# Required: curl, jq, and GITHUB_TOKEN environment variable with a valid CLASSIC GitHub token +# Note: The manifest code expires in 1 hour and can only be used once +# +# For full manifest flow documentation: +# https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest +# +# API Reference: https://docs.github.com/en/rest/apps/apps#create-a-github-app-from-a-manifest + +set -e + +# Check for required dependencies +if ! command -v curl &> /dev/null; then + echo "Error: curl is required but not installed" + exit 1 +fi + +if ! command -v jq &> /dev/null; then + echo "Error: jq is required but not installed" + echo "Install with: brew install jq # macOS" + echo " : apt-get install jq # Ubuntu/Debian" + echo " : yum install jq # CentOS/RHEL" + exit 1 +fi + +# Check if required arguments are provided +if [ $# -eq 0 ]; then + echo "Error: Manifest code is required" + echo "Usage: $0 " + echo "" + echo "Example: $0 a180b1a3d263c81bc6441d7b990bae27d4c10679" + echo "" + echo "WHAT IS A MANIFEST CODE?" + echo "The manifest code is generated by GitHub during the App Manifest flow:" + echo "1. Create an HTML form that posts to GitHub with your app configuration" + echo "2. User submits the form and GitHub redirects back with a 'code' parameter" + echo "3. Use that code with this script to get the actual app credentials" + echo "" + echo "For the complete manifest flow, see:" + echo "https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest" + exit 1 +fi + +# Check for GitHub token +if [ -z "$GITHUB_TOKEN" ]; then + echo "Error: GITHUB_TOKEN environment variable is required" + echo "Set it with: export GITHUB_TOKEN=your_token_here" + exit 1 +fi + +# Check if it's a classic token (starts with ghp_) +if [[ ! "$GITHUB_TOKEN" =~ ^ghp_ ]]; then + echo "Error: GITHUB_TOKEN must be a classic personal access token (starts with 'ghp_')" + echo "Fine-grained tokens are not supported for this endpoint" + exit 1 +fi + +MANIFEST_CODE="$1" + +echo "Converting manifest code to GitHub App credentials..." +echo "Manifest code: $MANIFEST_CODE" +echo "" + +# Make the API call to convert the manifest code using curl +RESPONSE=$(curl -s -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/app-manifests/$MANIFEST_CODE/conversions") + +# gh api example +# # Make the API call to convert the manifest code +# RESPONSE=$(gh api \ +# --method POST \ +# -H "Accept: application/vnd.github+json" \ +# -H "X-GitHub-Api-Version: 2022-11-28" \ +# "/app-manifests/$MANIFEST_CODE/conversions") + +# Check if the request was successful +if ! echo "$RESPONSE" | jq -e . > /dev/null 2>&1; then + echo "❌ Error: Failed to parse JSON response" + echo "Response: $RESPONSE" + exit 1 +fi + +# Check for API errors +if echo "$RESPONSE" | jq -e '.message' > /dev/null 2>&1; then + echo "❌ API Error:" + echo "$RESPONSE" | jq -r '.message' + if echo "$RESPONSE" | jq -e '.documentation_url' > /dev/null 2>&1; then + echo "Documentation: $(echo "$RESPONSE" | jq -r '.documentation_url')" + fi + exit 1 +fi + +# Extract values for file naming +APP_NAME=$(echo "$RESPONSE" | jq -r '.name // .slug' | tr ' ' '_' | tr '[:upper:]' '[:lower:]') +CURRENT_DATE=$(date +%Y-%m-%d) +PEM_FILENAME="${APP_NAME}.${CURRENT_DATE}.private-key.pem" + +# Save the PEM to a file +echo "$RESPONSE" | jq -r '.pem' > "$PEM_FILENAME" + +# Display the formatted response +echo "$RESPONSE" | jq '{ + id: .id, + slug: .slug, + name: .name, + description: .description, + client_id: .client_id, + client_secret: .client_secret, + webhook_secret: .webhook_secret, + pem: .pem, + owner: .owner.login, + html_url: .html_url, + permissions: .permissions, + events: .events +}' + +echo "" +echo "✅ GitHub App created successfully!" +echo "" +echo "Important: Save the client_secret, webhook_secret, and pem values securely." +echo "The pem field contains the full RSA private key for the app." +echo "These credentials cannot be retrieved again through the API." +echo "" +echo "� Private key automatically saved to: $PEM_FILENAME" +echo "💡 You can use this file for GitHub App authentication and JWT generation." diff --git a/scripts/github-app-manifest-scripts/generate-github-app-manifest-form.sh b/scripts/github-app-manifest-scripts/generate-github-app-manifest-form.sh new file mode 100755 index 0000000..54c0282 --- /dev/null +++ b/scripts/github-app-manifest-scripts/generate-github-app-manifest-form.sh @@ -0,0 +1,212 @@ +#!/bin/bash + +# Generate GitHub App Manifest HTML Form +# This script creates an HTML file that demonstrates the GitHub App Manifest flow +# +# Usage: ./generate-github-app-manifest-form.sh [organization] +# +# Example: ./generate-github-app-manifest-form.sh my-org +# ./generate-github-app-manifest-form.sh # for personal account +# +# This creates an HTML file that you can open in a browser to test the manifest flow. +# After submitting the form, GitHub will redirect back with a code that you can use +# with the create-github-app-from-manifest.sh script. +# +# Reference: https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest + +set -e + +ORGANIZATION="$1" +OUTPUT_FILE="github-app-manifest-form.html" + +# Determine the action URL based on whether an organization is specified +if [ -n "$ORGANIZATION" ]; then + ACTION_URL="https://github.com/organizations/$ORGANIZATION/settings/apps/new?state=abc123" + TARGET_TEXT="organization '$ORGANIZATION'" +else + ACTION_URL="https://github.com/settings/apps/new?state=abc123" + TARGET_TEXT="your personal account" +fi + +echo "Generating GitHub App Manifest form for $TARGET_TEXT..." + +cat >"$OUTPUT_FILE" <<'EOF' + + + + + GitHub App Manifest Test + + + +
+

GitHub App Manifest Test

+ +
+ What happens next: +
    +
  1. Start a local server (see instructions below)
  2. +
  3. Click "Create GitHub App" below
  4. +
  5. GitHub will redirect you to review and create the app
  6. +
  7. After creation, GitHub redirects back with a temporary code
  8. +
  9. The code will be automatically displayed and you can copy it
  10. +
+
+ +
+ 🚀 How to start local server (click to expand) + +
+ +
+ ⚠️ Important Notes (click to expand) + +
+ + + + +
+

App Manifest Configuration:

+ +

+ +
+ +
+ 📋 How to use the manifest code (click to expand) + +
+
+ + + + +EOF + +# Replace placeholders with actual values +sed -i.bak "s|ACTION_URL_PLACEHOLDER|$ACTION_URL|g" "$OUTPUT_FILE" +sed -i.bak "s|TARGET_PLACEHOLDER|$TARGET_TEXT|g" "$OUTPUT_FILE" +rm "$OUTPUT_FILE.bak" + +echo "✅ Generated HTML form: $OUTPUT_FILE" +echo "" +echo "Next steps:" +echo "1. Start a local HTTP server in this directory:" +echo " python3 -m http.server 8000" +echo "2. Open http://localhost:8000/$OUTPUT_FILE in your browser" +echo "3. Click 'Create GitHub App from Manifest'" +echo "4. Follow GitHub's prompts to create the app" +echo "5. GitHub will redirect back with a code parameter automatically displayed" +echo "6. Use that code with: ./create-github-app-from-manifest.sh " +echo "" +echo "The form now includes local server setup instructions and automatic code handling!" diff --git a/scripts/github-app-manifest-scripts/github-app-manifest-form-example.html b/scripts/github-app-manifest-scripts/github-app-manifest-form-example.html new file mode 100644 index 0000000..2151e9f --- /dev/null +++ b/scripts/github-app-manifest-scripts/github-app-manifest-form-example.html @@ -0,0 +1,160 @@ + + + + + GitHub App Manifest Test + + + +
+

GitHub App Manifest Test

+ +
+ What happens next: +
    +
  1. Start a local server (see instructions below)
  2. +
  3. Click "Create GitHub App" below
  4. +
  5. GitHub will redirect you to review and create the app
  6. +
  7. After creation, GitHub redirects back with a temporary code
  8. +
  9. The code will be automatically displayed and you can copy it
  10. +
+
+ +
+ 🚀 How to start local server (click to expand) + +
+ +
+ ⚠️ Important Notes (click to expand) + +
+ + + + +
+

App Manifest Configuration:

+ +

+ +
+ +
+ 📋 How to use the manifest code (click to expand) + +
+
+ + + + diff --git a/scripts/github-app-manifest-scripts/github-app-manifest-form.html b/scripts/github-app-manifest-scripts/github-app-manifest-form.html new file mode 100644 index 0000000..fc45977 --- /dev/null +++ b/scripts/github-app-manifest-scripts/github-app-manifest-form.html @@ -0,0 +1,155 @@ + + + + GitHub App Manifest Test + + + +
+

GitHub App Manifest Test

+ +
+ What happens next: +
    +
  1. Start a local server (see instructions below)
  2. +
  3. Click "Create GitHub App" below
  4. +
  5. GitHub will redirect you to review and create the app
  6. +
  7. After creation, GitHub redirects back with a temporary code
  8. +
  9. The code will be automatically displayed and you can copy it
  10. +
+
+ +
+ 🚀 How to start local server (click to expand) + +
+ +
+ ⚠️ Important Notes (click to expand) + +
+ + + + +
+

App Manifest Configuration:

+ +

+ +
+ +
+ 📋 How to use the manifest code (click to expand) + +
+
+ + + + From 06ddb1bab90b263c39f5f90f9b3f274f8b00ebbf Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 21 Oct 2025 10:25:05 -0500 Subject: [PATCH 2/6] refactor: rename folder --- scripts/README.md | 2 +- .../README.md | 0 .../create-github-app-from-manifest.sh | 0 .../generate-github-app-manifest-form.sh | 0 .../github-app-manifest-form-example.html | 0 .../create-github-app-from-manifest-slim.sh | 60 ------- .../github-app-manifest-form.html | 155 ------------------ 7 files changed, 1 insertion(+), 216 deletions(-) rename scripts/{github-app-manifest-scripts => github-app-manifest-flow}/README.md (100%) rename scripts/{github-app-manifest-scripts => github-app-manifest-flow}/create-github-app-from-manifest.sh (100%) rename scripts/{github-app-manifest-scripts => github-app-manifest-flow}/generate-github-app-manifest-form.sh (100%) rename scripts/{github-app-manifest-scripts => github-app-manifest-flow}/github-app-manifest-form-example.html (100%) delete mode 100755 scripts/github-app-manifest-scripts/create-github-app-from-manifest-slim.sh delete mode 100644 scripts/github-app-manifest-scripts/github-app-manifest-form.html diff --git a/scripts/README.md b/scripts/README.md index cf9cbe1..e818d88 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -109,7 +109,7 @@ My use case is to use this list to determine who needs to be added to a organiza 1. Run: `./new-users-to-add-to-project.sh ` 2. Don't delete the `` as it functions as your user database -## github-app-manifest-scripts +## github-app-manifest-flow Scripts to create GitHub Apps using the [GitHub App Manifest flow](https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest). The manifest flow allows you to create a GitHub App by posting a JSON manifest to GitHub, which then generates the app credentials. diff --git a/scripts/github-app-manifest-scripts/README.md b/scripts/github-app-manifest-flow/README.md similarity index 100% rename from scripts/github-app-manifest-scripts/README.md rename to scripts/github-app-manifest-flow/README.md diff --git a/scripts/github-app-manifest-scripts/create-github-app-from-manifest.sh b/scripts/github-app-manifest-flow/create-github-app-from-manifest.sh similarity index 100% rename from scripts/github-app-manifest-scripts/create-github-app-from-manifest.sh rename to scripts/github-app-manifest-flow/create-github-app-from-manifest.sh diff --git a/scripts/github-app-manifest-scripts/generate-github-app-manifest-form.sh b/scripts/github-app-manifest-flow/generate-github-app-manifest-form.sh similarity index 100% rename from scripts/github-app-manifest-scripts/generate-github-app-manifest-form.sh rename to scripts/github-app-manifest-flow/generate-github-app-manifest-form.sh diff --git a/scripts/github-app-manifest-scripts/github-app-manifest-form-example.html b/scripts/github-app-manifest-flow/github-app-manifest-form-example.html similarity index 100% rename from scripts/github-app-manifest-scripts/github-app-manifest-form-example.html rename to scripts/github-app-manifest-flow/github-app-manifest-form-example.html diff --git a/scripts/github-app-manifest-scripts/create-github-app-from-manifest-slim.sh b/scripts/github-app-manifest-scripts/create-github-app-from-manifest-slim.sh deleted file mode 100755 index cb66d5f..0000000 --- a/scripts/github-app-manifest-scripts/create-github-app-from-manifest-slim.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/bash - -# Create a GitHub App from a manifest -# Converts a temporary manifest code into GitHub App credentials -# -# Usage: ./create-github-app-from-manifest.sh -# Example: ./create-github-app-from-manifest.sh a180b1a3d263c81bc6441d7b990bae27d4c10679 -# -# Requires: GITHUB_TOKEN environment variable with a valid CLASSIC GitHub token -# The manifest code is generated by GitHub during the App Manifest flow. -# See: https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest - -set -e - -if [ $# -eq 0 ]; then - echo "Usage: $0 " - exit 1 -fi - -# Check for GitHub token -if [ -z "$GITHUB_TOKEN" ]; then - echo "Error: GITHUB_TOKEN environment variable is required" - echo "Set it with: export GITHUB_TOKEN=your_token_here" - exit 1 -fi - -# Check if it's a classic token (starts with ghp_) -if [[ ! "$GITHUB_TOKEN" =~ ^ghp_ ]]; then - echo "Error: GITHUB_TOKEN must be a classic personal access token (starts with 'ghp_')" - echo "Fine-grained tokens are not supported for this endpoint" - exit 1 -fi - -MANIFEST_CODE="$1" - -echo "Converting manifest code: $MANIFEST_CODE" - -# Make the API call -RESPONSE=$(curl -s -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/app-manifests/$MANIFEST_CODE/conversions") - -# Check for errors -if echo "$RESPONSE" | jq -e '.message' > /dev/null 2>&1; then - echo "Error: $(echo "$RESPONSE" | jq -r '.message')" - exit 1 -fi - -# Save PEM to file -APP_NAME=$(echo "$RESPONSE" | jq -r '.name // .slug' | tr ' ' '_' | tr '[:upper:]' '[:lower:]') -PEM_FILE="${APP_NAME}.$(date +%Y-%m-%d).private-key.pem" -echo "$RESPONSE" | jq -r '.pem' > "$PEM_FILE" - -# Display all response data -echo "$RESPONSE" | jq . - -echo "" -echo "✅ Private key saved to: $PEM_FILE" diff --git a/scripts/github-app-manifest-scripts/github-app-manifest-form.html b/scripts/github-app-manifest-scripts/github-app-manifest-form.html deleted file mode 100644 index fc45977..0000000 --- a/scripts/github-app-manifest-scripts/github-app-manifest-form.html +++ /dev/null @@ -1,155 +0,0 @@ - - - - GitHub App Manifest Test - - - -
-

GitHub App Manifest Test

- -
- What happens next: -
    -
  1. Start a local server (see instructions below)
  2. -
  3. Click "Create GitHub App" below
  4. -
  5. GitHub will redirect you to review and create the app
  6. -
  7. After creation, GitHub redirects back with a temporary code
  8. -
  9. The code will be automatically displayed and you can copy it
  10. -
-
- -
- 🚀 How to start local server (click to expand) - -
- -
- ⚠️ Important Notes (click to expand) - -
- - - - -
-

App Manifest Configuration:

- -

- -
- -
- 📋 How to use the manifest code (click to expand) - -
-
- - - - From 7aa1583dc59fe4efc213130be3bb0c1a300aab59 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 21 Oct 2025 10:27:06 -0500 Subject: [PATCH 3/6] fix: apply copilot fixes Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/create-app-jwt.sh | 29 +++++++++++++++++-- .../create-github-app-from-manifest.sh | 2 +- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/scripts/create-app-jwt.sh b/scripts/create-app-jwt.sh index 9f778bf..f94ef04 100755 --- a/scripts/create-app-jwt.sh +++ b/scripts/create-app-jwt.sh @@ -1,9 +1,32 @@ -#!/usr/bin/env bash - +#!/bin/bash + +# ----------------------------------------------------------------------------- +# Script to create a JWT (JSON Web Token) signed with a private RSA key. +# +# Usage: +# ./create-app-jwt.sh +# +# Arguments: +# The client ID to use as the JWT issuer (iss). +# Path to the PEM-encoded RSA private key file. +# +# Example: +# ./create-app-jwt.sh my-client-id /path/to/private-key.pem +# +# The script outputs the generated JWT to stdout. +# ----------------------------------------------------------------------------- set -o pipefail + +# Input validation for required parameters +if [ -z "$1" ] || [ -z "$2" ]; then + echo "Error: Missing required parameters." >&2 + echo "Usage: $0 " >&2 + exit 1 +fi + client_id=$1 # Client ID as first argument -pem=$( cat $2 ) # file path of the private key as second argument +pem=$( cat "$2" ) # file path of the private key as second argument now=$(date +%s) iat=$((${now} - 60)) # Issues 60 seconds in the past diff --git a/scripts/github-app-manifest-flow/create-github-app-from-manifest.sh b/scripts/github-app-manifest-flow/create-github-app-from-manifest.sh index 296d09e..b486284 100755 --- a/scripts/github-app-manifest-flow/create-github-app-from-manifest.sh +++ b/scripts/github-app-manifest-flow/create-github-app-from-manifest.sh @@ -142,5 +142,5 @@ echo "Important: Save the client_secret, webhook_secret, and pem values securely echo "The pem field contains the full RSA private key for the app." echo "These credentials cannot be retrieved again through the API." echo "" -echo "� Private key automatically saved to: $PEM_FILENAME" +echo "🔑 Private key automatically saved to: $PEM_FILENAME" echo "💡 You can use this file for GitHub App authentication and JWT generation." From 2eb5bcf26c36e104dfcff75d4b343957d1a973fc Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 21 Oct 2025 10:35:29 -0500 Subject: [PATCH 4/6] fix: check for file path Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/create-app-jwt.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/create-app-jwt.sh b/scripts/create-app-jwt.sh index f94ef04..d032f86 100755 --- a/scripts/create-app-jwt.sh +++ b/scripts/create-app-jwt.sh @@ -26,6 +26,11 @@ fi client_id=$1 # Client ID as first argument +# Check if the private key file exists and is readable +if [ ! -f "$2" ] || [ ! -r "$2" ]; then + echo "Error: Private key file '$2' does not exist or is not readable." >&2 + exit 1 +fi pem=$( cat "$2" ) # file path of the private key as second argument now=$(date +%s) From 8c61cefbda965b4a025450f4547863c10f0b8457 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 21 Oct 2025 10:42:51 -0500 Subject: [PATCH 5/6] fix: update placeholder replacement to be cross-platform compatible --- .../generate-github-app-manifest-form.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/github-app-manifest-flow/generate-github-app-manifest-form.sh b/scripts/github-app-manifest-flow/generate-github-app-manifest-form.sh index 54c0282..82337c5 100755 --- a/scripts/github-app-manifest-flow/generate-github-app-manifest-form.sh +++ b/scripts/github-app-manifest-flow/generate-github-app-manifest-form.sh @@ -194,9 +194,11 @@ gh api \ EOF # Replace placeholders with actual values -sed -i.bak "s|ACTION_URL_PLACEHOLDER|$ACTION_URL|g" "$OUTPUT_FILE" -sed -i.bak "s|TARGET_PLACEHOLDER|$TARGET_TEXT|g" "$OUTPUT_FILE" -rm "$OUTPUT_FILE.bak" +# Cross-platform approach (works everywhere) +sed "s|ACTION_URL_PLACEHOLDER|$ACTION_URL|g" "$OUTPUT_FILE" > "$OUTPUT_FILE.tmp" +mv "$OUTPUT_FILE.tmp" "$OUTPUT_FILE" +sed "s|TARGET_PLACEHOLDER|$TARGET_TEXT|g" "$OUTPUT_FILE" > "$OUTPUT_FILE.tmp" +mv "$OUTPUT_FILE.tmp" "$OUTPUT_FILE" echo "✅ Generated HTML form: $OUTPUT_FILE" echo "" From 9cff5392003da46d413da6faba27b26462482fd8 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Tue, 21 Oct 2025 10:42:56 -0500 Subject: [PATCH 6/6] fix: update usage instructions and parameter names in create-app-jwt.sh --- scripts/create-app-jwt.sh | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/scripts/create-app-jwt.sh b/scripts/create-app-jwt.sh index d032f86..bd137f5 100755 --- a/scripts/create-app-jwt.sh +++ b/scripts/create-app-jwt.sh @@ -4,14 +4,16 @@ # Script to create a JWT (JSON Web Token) signed with a private RSA key. # # Usage: -# ./create-app-jwt.sh +# ./create-app-jwt.sh # # Arguments: -# The client ID to use as the JWT issuer (iss). -# Path to the PEM-encoded RSA private key file. +# The GitHub App ID (integer) or Client ID (Iv1.xxx) +# to use as the JWT issuer (iss). Both work. +# Path to the PEM-encoded RSA private key file. # # Example: -# ./create-app-jwt.sh my-client-id /path/to/private-key.pem +# ./create-app-jwt.sh 123456 /path/to/private-key.pem +# ./create-app-jwt.sh Iv1.abc123def456 /path/to/private-key.pem # # The script outputs the generated JWT to stdout. # ----------------------------------------------------------------------------- @@ -20,11 +22,11 @@ set -o pipefail # Input validation for required parameters if [ -z "$1" ] || [ -z "$2" ]; then echo "Error: Missing required parameters." >&2 - echo "Usage: $0 " >&2 + echo "Usage: $0 " >&2 exit 1 fi -client_id=$1 # Client ID as first argument +app_id_or_client_id=$1 # App ID (integer) or Client ID (Iv1.xxx) - both work # Check if the private key file exists and is readable if [ ! -f "$2" ] || [ ! -r "$2" ]; then @@ -49,7 +51,7 @@ header=$( echo -n "${header_json}" | b64enc ) payload_json="{ \"iat\":${iat}, \"exp\":${exp}, - \"iss\":\"${client_id}\" + \"iss\":\"${app_id_or_client_id}\" }" # Payload encode payload=$( echo -n "${payload_json}" | b64enc )