跳至主要内容

使用 GitHub App 註冊 Self-Hosted Runner

本文說明如何建立 GitHub App,並以 App 憑證(而非 Personal Access Token)向 GitHub API 取得 Runner Registration Token,進而完成 self-hosted runner 的自動化註冊。

:::info 官方參考文件


為什麼用 GitHub App 而非 PAT?

比較項目Personal Access Token (PAT)GitHub App
授權範圍綁定個人帳號綁定 App,每次安裝獨立授權
多 Org 部署須個別建立 PAT同一個 App 可被多個 Org 安裝(需設為 Public)
Token 有效期最長 1 年(fine-grained)Installation Token 1 小時,可自動刷新
稽核追蹤以個人身份記錄以 App 名稱記錄,責任歸屬清晰
適合 CI/CD較不建議用於自動化官方推薦

:::note 關於跨 Org 安裝 建立 App 時的 Where can this GitHub App be installed? 設定決定可安裝範圍:

  • Only on this account(Private):僅 App owner 帳號可安裝
  • Any account(Public):任何 user / org 皆可安裝,每次安裝會產生獨立的 Installation ID

若僅內部使用,建議選 Private 以降低風險。 :::


步驟一:建立 GitHub App

  1. 前往 GitHub → Settings → Developer settings → GitHub Apps → New GitHub App (或 Organization 層級:Org Settings → Developer settings → GitHub Apps)

  2. 填寫基本資訊:

    • GitHub App name:任意,例如 my-runner-app
    • Homepage URL:任意有效 URL,例如公司網站
    • Webhook:取消勾選 Active(runner 不需要 webhook)
  3. 設定 Permissions

    分類權限
    RepositoryActionsRead & write
    OrganizationSelf-hosted runnersRead & write

    若 runner 僅用於單一 repository,也可選擇 Repository 層級的 Self-hosted runners 權限即可。

  4. Where can this GitHub App be installed? 選擇 Only on this accountAny account,視需求而定。

  5. 點擊 Create GitHub App


步驟二:認識 App 設定頁面的三個識別碼

App 建立後,設定頁面頂端會顯示三個識別碼,用途各不同:

欄位格式用途
App ID純數字,例如 1234567簽署 JWT 的 iss 欄位(傳統方式)
Client ID字串,例如 Iv1.abc123def456簽署 JWT 的 iss 欄位(新方式,可替代 App ID)
Client Secret隨機字串僅用於 OAuth 使用者授權流程,取得 Installation Token 不需要此值

:::tip App ID vs Client ID GitHub 目前在設定頁面會提示:

"Using your App ID to get installation tokens? You can now use your Client ID instead."

兩者皆可作為 JWT 的 iss 值,Client ID 是較新的推薦做法。 本文範例使用 APP_ID 變數,實際填入 App ID 或 Client ID 均可。 :::


步驟三:產生 Private Key

在 App 設定頁面:

  1. 往下捲動至 Private keys
  2. 點擊 Generate a private key
  3. 下載 .pem 檔案並妥善保管(此檔案只會顯示一次

步驟四:安裝 App 到 Organization 或 Repository

  1. 在 App 設定頁面點擊 Install App
  2. 選擇目標 Organization 或 Account
  3. 選擇要授權的 Repositories(All repositories 或指定 repo)
  4. 確認安裝後,從瀏覽器 URL 取得 Installation ID
    https://github.com/organizations/<org>/settings/installations/<installation_id>

步驟五:取得 Installation Access Token

GitHub App 的認證流程分兩步:

App Private Key → 簽署 JWT → 呼叫 API 取得 Installation Token

所需資訊

變數來源備註
APP_IDApp ID(數字) Client ID(字串)均可填入其中一個即可
INSTALLATION_ID安裝後 URL 中的數字見步驟四
PRIVATE_KEY下載的 .pem 檔案內容機密,必須存入 Secret

:::warning 敏感資訊處理

  • APP_ID / Client IDINSTALLATION_ID 本身非機密
  • Private Key(.pem)絕對不可洩漏,請存入 GitHub Actions Secret 或 Kubernetes Secret,切勿提交進 repo
  • Client Secret 與此流程無關,請勿混淆 :::

Shell 腳本範例(使用 curl + openssl

#!/usr/bin/env bash
set -euo pipefail

APP_ID="${GITHUB_APP_ID}"
INSTALLATION_ID="${GITHUB_APP_INSTALLATION_ID}"
PRIVATE_KEY="${GITHUB_APP_PRIVATE_KEY}" # PEM 內容(含 header/footer)

# 1. 產生 JWT(有效期 10 分鐘)
NOW=$(date +%s)
IAT=$((NOW - 60))
EXP=$((NOW + 600))
HEADER=$(echo -n '{"alg":"RS256","typ":"JWT"}' | base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n')
PAYLOAD=$(echo -n "{\"iat\":${IAT},\"exp\":${EXP},\"iss\":\"${APP_ID}\"}" | base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n')
UNSIGNED="${HEADER}.${PAYLOAD}"
SIG=$(echo -n "${UNSIGNED}" | openssl dgst -sha256 -sign <(echo "${PRIVATE_KEY}") | base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n')
JWT="${UNSIGNED}.${SIG}"

# 2. 用 JWT 取得 Installation Access Token
INSTALLATION_TOKEN=$(curl -s -X POST \
-H "Authorization: Bearer ${JWT}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/app/installations/${INSTALLATION_ID}/access_tokens" \
| jq -r '.token')

echo "Installation Token: ${INSTALLATION_TOKEN}"

Python 範例(使用 PyJWT + requests

import time, jwt, requests

APP_ID = "YOUR_APP_ID"
INSTALLATION_ID = "YOUR_INSTALLATION_ID"
PRIVATE_KEY = open("my-app.private-key.pem").read() # 實務上從環境變數或 Secret 讀取

# 1. 產生 JWT
payload = {
"iat": int(time.time()) - 60,
"exp": int(time.time()) + 600,
"iss": APP_ID,
}
token = jwt.encode(payload, PRIVATE_KEY, algorithm="RS256")

# 2. 取得 Installation Access Token
resp = requests.post(
f"https://api.github.com/app/installations/{INSTALLATION_ID}/access_tokens",
headers={
"Authorization": f"Bearer {token}",
"Accept": "application/vnd.github+json",
},
)
resp.raise_for_status()
installation_token = resp.json()["token"]
print(f"Token: {installation_token}")

步驟六:取得 Runner Registration Token 並啟動 Runner

取得 Installation Access Token 後,再呼叫 Runner API:

# 取得 Repository 層級的 Registration Token
REG_TOKEN=$(curl -s -X POST \
-H "Authorization: Bearer ${INSTALLATION_TOKEN}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/<owner>/<repo>/actions/runners/registration-token" \
| jq -r '.token')

# 或 Organization 層級
REG_TOKEN=$(curl -s -X POST \
-H "Authorization: Bearer ${INSTALLATION_TOKEN}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/orgs/<org>/actions/runners/registration-token" \
| jq -r '.token')

# 執行 Runner 設定腳本
./config.sh \
--url https://github.com/<owner>/<repo> \
--token "${REG_TOKEN}" \
--name "my-runner" \
--labels "self-hosted,linux,x64" \
--unattended \
--replace

# 啟動 Runner
./run.sh

在 GitHub Actions Workflow 中使用機密

將以下三個值存入 Repository / Organization Secrets

Secret 名稱對應值
GH_APP_IDApp 數字 ID
GH_APP_INSTALLATION_IDInstallation 數字 ID
GH_APP_PRIVATE_KEY.pem 檔案的完整內容

Workflow 範例:

name: Register Runner

on:
workflow_dispatch:

jobs:
register:
runs-on: ubuntu-latest
steps:
- name: Get Runner Registration Token
env:
GITHUB_APP_ID: ${{ secrets.GH_APP_ID }}
GITHUB_APP_INSTALLATION_ID: ${{ secrets.GH_APP_INSTALLATION_ID }}
GITHUB_APP_PRIVATE_KEY: ${{ secrets.GH_APP_PRIVATE_KEY }}
run: |
# 此處執行上方 Shell 腳本取得 INSTALLATION_TOKEN
# 再呼叫 registration-token API
echo "Registration token obtained"

Kubernetes 環境下的整合範例

若 runner 跑在 K8s,可將三個值存入 Secret:

kubectl create secret generic github-app-credentials \
--from-literal=app-id='YOUR_APP_ID' \
--from-literal=installation-id='YOUR_INSTALLATION_ID' \
--from-file=private-key=my-app.private-key.pem \
-n your-namespace

Pod 中透過環境變數讀取:

env:
- name: GITHUB_APP_ID
valueFrom:
secretKeyRef:
name: github-app-credentials
key: app-id
- name: GITHUB_APP_INSTALLATION_ID
valueFrom:
secretKeyRef:
name: github-app-credentials
key: installation-id
- name: GITHUB_APP_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: github-app-credentials
key: private-key

常見問題

JWT 簽署錯誤

確認 Private Key 的格式正確,-----BEGIN RSA PRIVATE KEY----- 開頭與結尾需完整,換行不可遺失。

Installation Token 取得 401

  • 確認 App 已安裝到正確的 Organization 或 Repository
  • 確認 INSTALLATION_ID 是安裝 ID,不是 App ID

Runner 啟動後顯示 Offline

Runner 成功啟動後需要保持 run.sh 行程持續運行,若作為 Daemon 使用請以 ./svc.sh install && ./svc.sh start 安裝成系統服務。