forked from farhoodlabs/skills
refactor: replace github-app-token docs with executable script
Move token generation logic from inline SKILL.md code blocks into a single scripts/generate-token.sh that agents can invoke directly. Adds env var validation, error handling, and gh CLI auth in one step. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -5,84 +5,22 @@ description: Generate a GitHub installation access token from a GitHub App PEM k
|
|||||||
|
|
||||||
# GitHub App Token Skill
|
# GitHub App Token Skill
|
||||||
|
|
||||||
Generate a short-lived GitHub installation access token from a GitHub App's credentials and use it to authenticate the `gh` CLI.
|
Generate a short-lived GitHub App installation token and authenticate `gh`.
|
||||||
|
|
||||||
## Prerequisites
|
## Required Environment Variables
|
||||||
|
|
||||||
The following environment variables MUST be set before invoking this skill:
|
|
||||||
|
|
||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `GITHUB_APP_ID` | The numeric App ID from the GitHub App settings page |
|
| `GITHUB_APP_ID` | Numeric App ID from GitHub App settings |
|
||||||
| `GITHUB_APP_INSTALLATION_ID` | The numeric Installation ID for the target org/user |
|
| `GITHUB_APP_INSTALLATION_ID` | Numeric Installation ID for the target org/user |
|
||||||
| `GITHUB_APP_PEM_FILE` | Absolute path to the GitHub App's PEM private key file |
|
| `GITHUB_APP_PEM_FILE` | Absolute path to the App's PEM private key file |
|
||||||
|
|
||||||
If any variable is missing, stop and tell the user which ones are required.
|
## Usage
|
||||||
|
|
||||||
Requires `openssl`, `curl`, and `jq`.
|
|
||||||
|
|
||||||
## Generate a Token
|
|
||||||
|
|
||||||
Build a JWT signed with the App's private key, then exchange it for an installation access token and write it to a file:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Base64url helper
|
bash github-app-token/scripts/generate-token.sh
|
||||||
b64enc() { openssl enc -base64 -A | tr '+/' '-_' | tr -d '='; }
|
|
||||||
|
|
||||||
# Build JWT (valid 10 minutes)
|
|
||||||
NOW=$(date +%s)
|
|
||||||
HEADER=$(printf '{"alg":"RS256","typ":"JWT"}' | jq -r -c .)
|
|
||||||
PAYLOAD=$(printf '{"iat":%s,"exp":%s,"iss":"%s"}' "$NOW" "$((NOW + 600))" "$GITHUB_APP_ID" | jq -r -c .)
|
|
||||||
SIGNED=$(printf '%s' "$HEADER" | b64enc).$(printf '%s' "$PAYLOAD" | b64enc)
|
|
||||||
SIG=$(printf '%s' "$SIGNED" | openssl dgst -binary -sha256 -sign "$GITHUB_APP_PEM_FILE" | b64enc)
|
|
||||||
JWT="${SIGNED}.${SIG}"
|
|
||||||
|
|
||||||
# Token file — unique per agent to avoid env-var collisions
|
|
||||||
GH_TOKEN_FILE="${AGENT_HOME:+${AGENT_HOME}/.gh-token}"
|
|
||||||
GH_TOKEN_FILE="${GH_TOKEN_FILE:-$(mktemp)}"
|
|
||||||
|
|
||||||
# Exchange JWT for installation token and write to file
|
|
||||||
curl -s -X POST \
|
|
||||||
-H "Authorization: Bearer ${JWT}" \
|
|
||||||
-H "Accept: application/vnd.github+json" \
|
|
||||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
||||||
"https://api.github.com/app/installations/${GITHUB_APP_INSTALLATION_ID}/access_tokens" \
|
|
||||||
| jq -r '.token' > "$GH_TOKEN_FILE"
|
|
||||||
|
|
||||||
chmod 600 "$GH_TOKEN_FILE"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The token file path defaults to `$AGENT_HOME/.gh-token` (unique per agent) or a temporary file when `AGENT_HOME` is not set. This avoids env-var collisions when multiple agents generate tokens concurrently.
|
The script validates env vars, generates a JWT, exchanges it for an installation token, writes the token to `$AGENT_HOME/.gh-token`, and runs `gh auth login`. On success it prints a confirmation line. On failure it exits non-zero with a descriptive error.
|
||||||
|
|
||||||
## Authenticate the gh CLI
|
Requires `openssl`, `curl`, `jq`, and `gh`.
|
||||||
|
|
||||||
Read the token from the file and log in:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gh auth login --with-token < "$GH_TOKEN_FILE"
|
|
||||||
```
|
|
||||||
|
|
||||||
To use `GH_TOKEN` in a single command without polluting the environment:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
GH_TOKEN=$(cat "$GH_TOKEN_FILE") gh api user
|
|
||||||
```
|
|
||||||
|
|
||||||
## Cleanup
|
|
||||||
|
|
||||||
The installation access token expires after 1 hour. To revoke it early and remove the token file:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -s -X DELETE \
|
|
||||||
-H "Authorization: Bearer $(cat "$GH_TOKEN_FILE")" \
|
|
||||||
-H "Accept: application/vnd.github+json" \
|
|
||||||
"https://api.github.com/installation/token"
|
|
||||||
rm -f "$GH_TOKEN_FILE"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security Notes
|
|
||||||
|
|
||||||
- Never log or echo the PEM key or installation token to stdout in production.
|
|
||||||
- The installation token is valid for 1 hour from generation.
|
|
||||||
- Store the PEM file with restrictive permissions (`chmod 600`) and never check it into git.
|
|
||||||
- The token file is written with mode `600` and should be cleaned up after use.
|
|
||||||
|
|||||||
Executable
+47
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||||
|
|
||||||
|
# --- Validate required env vars ---
|
||||||
|
[[ -z "${GITHUB_APP_ID:-}" ]] && die "GITHUB_APP_ID is not set"
|
||||||
|
[[ -z "${GITHUB_APP_INSTALLATION_ID:-}" ]] && die "GITHUB_APP_INSTALLATION_ID is not set"
|
||||||
|
[[ -z "${GITHUB_APP_PEM_FILE:-}" ]] && die "GITHUB_APP_PEM_FILE is not set"
|
||||||
|
[[ ! -f "$GITHUB_APP_PEM_FILE" ]] && die "PEM file not found: $GITHUB_APP_PEM_FILE"
|
||||||
|
|
||||||
|
for cmd in openssl curl jq gh; do
|
||||||
|
command -v "$cmd" >/dev/null 2>&1 || die "Required command not found: $cmd"
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- Build JWT (valid 10 minutes) ---
|
||||||
|
b64url() { openssl enc -base64 -A | tr '+/' '-_' | tr -d '='; }
|
||||||
|
|
||||||
|
NOW=$(date +%s)
|
||||||
|
HEADER=$(printf '{"alg":"RS256","typ":"JWT"}' | b64url)
|
||||||
|
PAYLOAD=$(printf '{"iat":%s,"exp":%s,"iss":"%s"}' "$NOW" "$((NOW + 600))" "$GITHUB_APP_ID" | b64url)
|
||||||
|
SIGNED="${HEADER}.${PAYLOAD}"
|
||||||
|
SIG=$(printf '%s' "$SIGNED" | openssl dgst -binary -sha256 -sign "$GITHUB_APP_PEM_FILE" | b64url)
|
||||||
|
JWT="${SIGNED}.${SIG}"
|
||||||
|
|
||||||
|
# --- Exchange JWT for installation access token ---
|
||||||
|
RESPONSE=$(curl -sf -X POST \
|
||||||
|
-H "Authorization: Bearer ${JWT}" \
|
||||||
|
-H "Accept: application/vnd.github+json" \
|
||||||
|
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||||
|
"https://api.github.com/app/installations/${GITHUB_APP_INSTALLATION_ID}/access_tokens") \
|
||||||
|
|| die "GitHub API request failed — check App ID, Installation ID, and PEM key"
|
||||||
|
|
||||||
|
TOKEN=$(echo "$RESPONSE" | jq -r '.token // empty')
|
||||||
|
[[ -z "$TOKEN" ]] && die "No token in GitHub response: $RESPONSE"
|
||||||
|
|
||||||
|
# --- Write token to file ---
|
||||||
|
GH_TOKEN_FILE="${AGENT_HOME:+${AGENT_HOME}/.gh-token}"
|
||||||
|
GH_TOKEN_FILE="${GH_TOKEN_FILE:-$(mktemp)}"
|
||||||
|
|
||||||
|
printf '%s' "$TOKEN" > "$GH_TOKEN_FILE"
|
||||||
|
chmod 600 "$GH_TOKEN_FILE"
|
||||||
|
|
||||||
|
# --- Authenticate gh CLI ---
|
||||||
|
gh auth login --with-token < "$GH_TOKEN_FILE"
|
||||||
|
|
||||||
|
echo "Authenticated. Token written to $GH_TOKEN_FILE (expires in 1 hour)."
|
||||||
Reference in New Issue
Block a user