Files
ISEP/scripts/gitea-bootstrap.sh
T
Leo daa84b583f ISEP#47: source Gitea token from env/.env, not remote URL (9 scripts + credential helper)
- 6 hooks + gitea-labels-sync/wiki-panorama/gitea-bootstrap 全改用 gitea_token()
- wiki-panorama clone/fetch 改乾淨 URL + credential helper
- gitea-bootstrap 冪等清 remote URL + 設 credential.helper
- 驗證:bootstrap/labels-sync API 認證通過、clone/push 經 helper 無 token 洩漏

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kxf68FcYGqiuLAPiKKU2U3
2026-09-18 12:36:30 +00:00

96 lines
5.9 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh
# gitea-bootstrap.sh — 新 repo 開通工作管理元件(狀態標籤)。冪等,重跑無害。
#
# 為什麼是腳本不是一段叮嚀(leo 2026-08-09:「每到一個新的 repo 就要建立這些管理元件」):
# 靠人/靠 AI 記得 = 必然漂移。今天一天就抓到三條「規則寫了沒人驗」。
# label 是 repo-scoped、沒有跨 repo 繼承 ⇒ 每個 repo 都要建一次 ⇒ 必須一行做完。
#
# 用法(在該 repo 目錄下跑,或用 -r 指定):
# scripts/gitea-bootstrap.sh # 用當前 repo 的 gitea remote
# scripts/gitea-bootstrap.sh -r Leo/some-repo # 指定 repotoken 仍從當前 repo 取)
#
# milestone 不在這裡建——milestone sprint 一次交貨,每個 repo 的交貨內容不同,
# 不能預設。建法見 /issue-handle skill。
set -eu
REPO=""
[ "${1:-}" = "-r" ] && { REPO="${2:?-r 後面要接 owner/repo}"; }
# 取當前 repo 的 remote(雲端叫 origin、本機慣例叫 gitea
REMOTE_NAME=$(git remote get-url gitea >/dev/null 2>&1 && echo gitea || (git remote get-url origin >/dev/null 2>&1 && echo origin))
[ -n "$REMOTE_NAME" ] || { echo "✗ 當前目錄沒有 gitea/origin remote。請 cd 到目標 repo,或先加 remote。" >&2; exit 1; }
REMOTE=$(git remote get-url "$REMOTE_NAME" 2>/dev/null)
# 去掉網址裡可能嵌著的憑證 → 乾淨 URLinkstone/ISEP#47token 不進 remote URL
REMOTE=$(printf '%s' "$REMOTE" | sed -E 's#//[^/@]*@#//#')
# token 來源=env.env(不從 remote URL 抽——那會逼 token 明文嵌在 URL 裡 → 洩進 log)
_GT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
[ -f "$_GT_DIR/lib/gitea-token.sh" ] && . "$_GT_DIR/lib/gitea-token.sh"
TOKEN=""; command -v gitea_token >/dev/null 2>&1 && TOKEN=$(gitea_token || true)
[ -n "$TOKEN" ] || { echo "✗ 取不到 Gitea token(設 GITEA_TOKEN_CLAUDE_CODE,或在 .env 放 GITEA_TOKEN_CLAUDE_CODE=)。" >&2; exit 1; }
HOST=$(printf '%s' "$REMOTE" | sed -E 's|.*://([^/]+)/.*|\1|')
[ -n "$REPO" ] || REPO=$(printf '%s' "$REMOTE" | sed -E 's|.*://[^/]+/(.+)\.git$|\1|')
# 設定乾淨 remote URL + credential helper(冪等):URL 不含 token,認證改由 helper 從 env.env 供
# ⇒ git 把 remote URL 印出來(git-lfs 的 locksverify 那行)也不會洩 tokeninkstone/ISEP#47)。
if [ -f "$_GT_DIR/git-credential-gitea.sh" ]; then
CUR=$(git remote get-url "$REMOTE_NAME" 2>/dev/null || true)
case "$CUR" in *@*) git remote set-url "$REMOTE_NAME" "$REMOTE" && echo " ✓ remote $REMOTE_NAME 已清乾淨(移除嵌入的 token)" ;; esac
git config credential.helper "$_GT_DIR/git-credential-gitea.sh"
fi
echo "→ repo: $REPO host: $HOST"
# 狀態機(互斥 scope label)。順序=出貨流程:
# s/todo → s/doing → s/stage →(leo 蓋章 + arm + 推 prod)→ closed
# 🔴 不建 s/doneclosed 就是 done,多一個就是同一件事兩個真相。
# 🔴 s/triage 存在的理由:**「留白」查不出來**。撈得到 s/todo,卻撈不到
# 「所有還沒驗傷的」——沒有標籤不是一種狀態,是查詢的死角
# leo 2026-08-09 建 Triage 看板時暴露的設計缺陷)。
# p/ 是另一個軸:s/ 答「走到哪」、p/ 答「多重要」——
# s/backlog 的東西也可以是 p/high,併成一組就表達不出來。
set -- \
's/triage|d4c5f9|新進來的,還沒驗傷——還沒決定要不要做' \
's/backlog|c2e0c6|驗過了、確定要做,但還沒排進任何 sprint(wishlist/功能需求/待規劃)' \
's/todo|ededed|已排進 sprint,等開工' \
's/doing|0e8a16|進行中——現在有人在做' \
's/stage|5319e7|已推上 stage,等 leo 去 youlin 的 stage 環境驗收(出貨流程第⑤步)' \
's/pending|fbca04|卡住——等外部/等人,不是沒人做' \
'p/high|b60205|高——擋住交付或有時間壓力' \
'p/low|bfd4f2|低——想做,但晚一點沒關係'
# 🔴 先抓現有清單再建。**Gitea 允許同名 label、回 201 不是 422**
# ⇒ 靠「重複會被擋」達成冪等是錯的。2026-08-09 實撞:本腳本第一版
# 在 arcrun-rag 造出每個標籤各兩份——那會直接弄壞互斥狀態機(同名兩個 id,
# 貼哪一個都不會把另一個頂掉),事後手動刪掉四個重複 id 才救回來。
EXISTING=$(curl -s -H "Authorization: token $TOKEN" -H "Cache-Control: no-cache" \
"https://$HOST/api/v1/repos/$REPO/labels?limit=100" \
| python3 -c "import json,sys;print(' '.join(l['name'] for l in json.load(sys.stdin)))")
created=0; existed=0; failed=0
for spec in "$@"; do
name=${spec%%|*}; rest=${spec#*|}; color=${rest%%|*}; desc=${rest#*|}
case " $EXISTING " in *" $name "*) echo " · 已存在 $name"; existed=$((existed+1)); continue ;; esac
payload=$(NAME="$name" COLOR="$color" DESC="$desc" python3 -c '
import json,os
print(json.dumps({"name":os.environ["NAME"],"color":"#"+os.environ["COLOR"],
"description":os.environ["DESC"],"exclusive":True}))')
code=$(printf '%s' "$payload" | curl -s -o /tmp/.bootstrap-out -w '%{http_code}' \
-X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
--data-binary @- "https://$HOST/api/v1/repos/$REPO/labels")
case "$code" in
201) echo " ✓ 建立 $name"; created=$((created+1)) ;;
422) echo " · 已存在 $name"; existed=$((existed+1)) ;;
*) echo " ✗ $name → HTTP $code: $(head -c 120 /tmp/.bootstrap-out)"; failed=$((failed+1)) ;;
esac
done
# 複驗:從 repo 端讀回來,不信自己送出的指令(2026-08-09 教訓:宣告 vs 證據)
echo "→ 複驗(從 repo 讀回):"
curl -s -H "Authorization: token $TOKEN" -H "Cache-Control: no-cache" \
"https://$HOST/api/v1/repos/$REPO/labels?limit=100" \
| python3 -c "import json,sys;print(' ',sorted(l['name'] for l in json.load(sys.stdin) if l['name'].startswith('s/')))"
echo "→ 新建 $created/已存在 $existed/失敗 $failed"
[ "$failed" -eq 0 ] || exit 1