-
Notifications
You must be signed in to change notification settings - Fork 0
feat: cloudbird-agent App 令牌交换脚本与一键创建页 #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <!doctype html> | ||
| <html lang="zh-CN"> | ||
| <head> | ||
| <meta charset="utf-8"> | ||
| <title>创建 cloudbird-agent GitHub App</title> | ||
| </head> | ||
| <body onload="document.forms[0].submit()"> | ||
| <p>正在跳转到 GitHub 创建 <strong>cloudbird-agent</strong> App……<br> | ||
| (浏览器需已登录 Cloudbird-Software 组织 owner 账号;表单已按最小权限预填,确认后点 <em>Create GitHub App</em>。)</p> | ||
| <form method="post" action="https://github.com/organizations/Cloudbird-Software/settings/apps/new"> | ||
| <input type="hidden" name="manifest" value='{ | ||
| "name": "cloudbird-agent", | ||
| "description": "AI agent 的仓库写入身份:分支/PR/Issue,无 CI 改动权,受 ruleset 约束", | ||
| "url": "https://github.com/Cloudbird-Software", | ||
| "public": false, | ||
| "default_permissions": { | ||
| "contents": "write", | ||
| "issues": "write", | ||
| "pull_requests": "write", | ||
| "metadata": "read" | ||
| } | ||
| }'> | ||
| <noscript><button type="submit">继续</button></noscript> | ||
| </form> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,70 @@ | ||||||||||||||||||||||||
| #!/usr/bin/env bash | ||||||||||||||||||||||||
| # gh-app-token.sh —— 用 cloudbird-agent App 私钥换取 1 小时有效的安装令牌 | ||||||||||||||||||||||||
| # | ||||||||||||||||||||||||
| # 这是 agent 与 GitHub 交互的唯一推荐认证方式: | ||||||||||||||||||||||||
| # - 权限最小:Contents / Pull requests / Issues 读写,无 admin、无 Workflows 改动权 | ||||||||||||||||||||||||
| # - 令牌 1 小时自动过期,磁盘上不落任何长期凭据(私钥妥善保管即可) | ||||||||||||||||||||||||
| # - 所有操作以 cloudbird-agent[bot] 身份进入审计日志,与人类账号区分 | ||||||||||||||||||||||||
| # - ruleset 照样生效:App 不能直推 main,必须走 PR 过 gate | ||||||||||||||||||||||||
| # | ||||||||||||||||||||||||
| # 依赖:bash curl openssl jq(无需任何 GitHub SDK) | ||||||||||||||||||||||||
| # | ||||||||||||||||||||||||
| # 用法: | ||||||||||||||||||||||||
| # export CB_APP_ID=123456 # App 详情页的 App ID | ||||||||||||||||||||||||
| # export CB_APP_KEY_FILE=~/.config/cloudbird/cloudbird-agent.pem | ||||||||||||||||||||||||
| # # CI / secret 场景改用字面量: export CB_APP_KEY="<PEM 全文>" | ||||||||||||||||||||||||
| # | ||||||||||||||||||||||||
| # GH_TOKEN=$(bash gh-app-token.sh) # 作用域=安装的全部仓库 | ||||||||||||||||||||||||
| # GH_TOKEN=$(REPO=template-service bash gh-app-token.sh) # 作用域=单仓库(推荐) | ||||||||||||||||||||||||
| # gh api user # 验证:应显示 cloudbird-agent[bot] | ||||||||||||||||||||||||
| set -euo pipefail | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| API="${CB_GITHUB_API:-https://api.github.com}" | ||||||||||||||||||||||||
| ORG="${ORG:-Cloudbird-Software}" | ||||||||||||||||||||||||
| APP_ID="${CB_APP_ID:?需要环境变量 CB_APP_ID(GitHub App 设置页的 App ID)}" | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # 私钥:优先文件,其次字面量(CI secret 里通常存 PEM 全文) | ||||||||||||||||||||||||
| if [[ -n "${CB_APP_KEY_FILE:-}" && -f "$CB_APP_KEY_FILE" ]]; then | ||||||||||||||||||||||||
| KEY=$(cat "$CB_APP_KEY_FILE") | ||||||||||||||||||||||||
| elif [[ -n "${CB_APP_KEY:-}" ]]; then | ||||||||||||||||||||||||
| KEY="$CB_APP_KEY" | ||||||||||||||||||||||||
| else | ||||||||||||||||||||||||
| echo "错误:需要 CB_APP_KEY_FILE(私钥文件路径)或 CB_APP_KEY(PEM 内容)" >&2 | ||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| b64url() { openssl base64 -A | tr '+/' '-_' | tr -d '=\n'; } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # 1) 生成 App JWT(RS256 签名,9 分钟有效,容忍时钟偏差) | ||||||||||||||||||||||||
| now=$(date +%s) | ||||||||||||||||||||||||
| header=$(printf '{"alg":"RS256","typ":"JWT"}' | b64url) | ||||||||||||||||||||||||
| payload=$(printf '{"iat":%d,"exp":%d,"iss":"%s"}' "$((now - 60))" "$((now + 480))" "$APP_ID" | b64url) | ||||||||||||||||||||||||
| signature=$(printf '%s.%s' "$header" "$payload" | openssl dgst -sha256 -sign <(printf '%s\n' "$KEY") | b64url) | ||||||||||||||||||||||||
| JWT="$header.$payload.$signature" | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| gh_api() { curl -sS -H "Authorization: Bearer $JWT" -H "Accept: application/vnd.github+json" "$@"; } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # 2) 定位组织内的 installation id | ||||||||||||||||||||||||
| INSTALL_ID=$(gh_api "$API/app/installations?per_page=100" \ | ||||||||||||||||||||||||
| | jq -r --arg org "$ORG" '.[] | select((.account.login | ascii_downcase) == ($org | ascii_downcase)) | .id' | head -1) | ||||||||||||||||||||||||
| if [[ -z "$INSTALL_ID" ]]; then | ||||||||||||||||||||||||
| echo "错误:找不到 $ORG 的 installation。请先安装 App:Settings → Applications → cloudbird-agent → Configure" >&2 | ||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # 3) JWT 换安装令牌;REPO 非空时把令牌限定到单仓库(最小权限) | ||||||||||||||||||||||||
| BODY='{}' | ||||||||||||||||||||||||
| [[ -n "${REPO:-}" ]] && BODY=$(jq -nc --arg r "$REPO" '{repositories: [$r]}') | ||||||||||||||||||||||||
|
Comment on lines
+55
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 默认要求 当 默认拒绝空 建议修改+if [[ -z "${REPO:-}" && "${ALLOW_ALL_REPOSITORIES:-}" != "1" ]]; then
+ echo "错误:需要 REPO。全仓库令牌请显式设置 ALLOW_ALL_REPOSITORIES=1" >&2
+ exit 1
+fi
+
BODY='{}'
[[ -n "${REPO:-}" ]] && BODY=$(jq -nc --arg r "$REPO" '{repositories: [$r]}')同时更新 Line 17 的用法说明,避免将全仓库令牌作为默认调用方式。 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| RESP=$(curl -sS -X POST \ | ||||||||||||||||||||||||
| -H "Authorization: Bearer $JWT" \ | ||||||||||||||||||||||||
| -H "Accept: application/vnd.github+json" \ | ||||||||||||||||||||||||
| -d "$BODY" "$API/app/installations/$INSTALL_ID/access_tokens") | ||||||||||||||||||||||||
|
Comment on lines
+45
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
text = Path("scripts/gh-app-token.sh").read_text()
assert text.count("curl") >= 2
assert text.count("--connect-timeout") >= 2
assert text.count("--max-time") >= 2
PYRepository: Cloudbird-Software/.github Length of output: 252 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- file outline ---'
if command -v ast-grep >/dev/null 2>&1; then
ast-grep outline scripts/gh-app-token.sh || true
fi
printf '%s\n' '--- curl call sites ---'
rg -n -C 4 '\bcurl\b|connect-timeout|max-time|gh_api|RESP=' scripts/gh-app-token.sh
printf '%s\n' '--- numbered file ---'
nl -ba scripts/gh-app-token.shRepository: Cloudbird-Software/.github Length of output: 1741 为所有 GitHub API 调用设置连接和总超时。
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| TOKEN=$(jq -r '.token // empty' <<<"$RESP") | ||||||||||||||||||||||||
| if [[ -z "$TOKEN" ]]; then | ||||||||||||||||||||||||
| echo "错误:换令牌失败:$RESP" >&2 | ||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| echo "令牌有效至 $(jq -r .expires_at <<<"$RESP")(作用域:${REPO:-全部已安装仓库},身份 cloudbird-agent[bot])" >&2 | ||||||||||||||||||||||||
| echo "$TOKEN" | ||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: Cloudbird-Software/.github
Length of output: 227
🏁 Script executed:
Repository: Cloudbird-Software/.github
Length of output: 4713
🌐 Web query:
GitHub REST API GET /orgs/{org}/installation authenticated as GitHub App JWT response installation id pagination /app/installations💡 Result:
To retrieve an organization's installation ID while authenticated as a GitHub App using a JSON Web Token (JWT), you use the GET /orgs/{org}/installation endpoint [1][2]. Authentication Process: 1. Generate a JWT using your GitHub App's private key and App ID [3][4]. 2. Include the JWT in the Authorization header of your request as a Bearer token: Authorization: Bearer YOUR_JWT [3][4]. 3. Call the endpoint: GET /orgs/{org}/installation [1][2]. The response will be a JSON object containing the installation details, including the installation id (found under the id key) [5]. Regarding Pagination: - The GET /orgs/{org}/installation endpoint returns a single installation object for the specified organization, so it does not require pagination [1]. - In contrast, the GET /app/installations endpoint, which lists all installations for your authenticated app, does support pagination [6][7]. This endpoint accepts per_page and page query parameters (default per_page is 30, max 100) and provides paginated results [8][9]. You can use the Link header in the response to fetch additional pages or use the GitHub Octokit SDK's built-in pagination methods [10].
Citations:
改用组织 Installation 查询端点。
/app/installations?per_page=100只读取第一页。目标组织不在第一页时,脚本会找不到INSTALL_ID并退出。改用GET /orgs/{org}/installation直接查询目标组织的 Installation。🤖 Prompt for AI Agents