Compare commits

..

4 Commits

Author SHA1 Message Date
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
Leo c7890335cc ISEP#47: gitea-token resolver + credential helper (source from env/.env, not remote URL) 2026-09-18 12:27:57 +00:00
Leo 8a3802c4b7 release: v0.30.10——雲端環境產物分檔,開機腳本不再混進分隔線
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 20:16:19 +08:00
Leo 09769a0dd1 雲端環境產物拆成兩個檔:變數檔只有 KEY=VALUE、開機腳本檔逐位元組=cloud-setup-script.sh(+README)
2026-09-18 實撞:兩段同檔、「════」分隔線被一起貼進 Setup script 欄 ⇒ 雲端開機 exit 127,
cloud-worker 每一趟在開機就死(cse_013LPaaD47m4arFzLjGvSZnN)。新增 V3(變數檔每行都是 KEY=VALUE)、
V4(開機腳本檔逐位元組相同);test-make-cloud-env.sh 15/15。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 20:16:19 +08:00
14 changed files with 162 additions and 71 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "isep",
"description": "InkStone Environment Plugin —— leo 的 Claude Code 環境唯一真相源:62 支機械閘(88 條註冊,白話盤點見 docs/hooks-inventory.md)、7 支 slash command、2 支 skill、8 位有名字的工人(agents/,見 docs/governance/worker-roster.md)、65 支腳本,外加治理規範與標籤真相源。本機與雲端裝同一份,沒有子集。",
"version": "0.30.9",
"version": "0.30.10",
"keywords": [
"inkstone",
"guardrails",
+6 -10
View File
@@ -149,16 +149,12 @@ check_one(){ # $1 owner $2 repo $3 num
J=$(cat "$BATON_GUARD_FIXTURE/issue.json")
C=$(cat "$BATON_GUARD_FIXTURE/comments.json")
else
# token掃已知的 checkout 找第一個帶憑證、指向本站的 remote(同 scripts/ticket 的做法
local ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}" URL="" U dir TOKEN
for dir in "$ROOT" "$ROOT/products/$REPO" "$ROOT/matrix/$REPO" "$ROOT/polaris/$REPO"; do
[ -e "$dir/.git" ] || continue
U=$(git -C "$dir" remote -v 2>/dev/null | grep -m1 'git\.uncle6\.me' | grep '@' | awk '{print $2}') || true
[ -n "${U:-}" ] && { URL="$U"; break; }
done
[ -n "$URL" ] || return 0
TOKEN=$(printf '%s' "$URL" | sed -E 's|.*//[^:]+:([^@]+)@.*|\1|')
[ "$TOKEN" != "$URL" ] || return 0
# token來源=env.envinkstone/ISEP#47),不再從 remote URL 抽(那會逼 token 明文嵌在 URL 裡
local TOKEN _GT_LIB
_GT_LIB="$(dirname -- "$0")/../scripts/lib/gitea-token.sh"; [ -f "$_GT_LIB" ] && . "$_GT_LIB"
command -v gitea_token >/dev/null 2>&1 || return 0
TOKEN=$(gitea_token) || return 0
[ -n "$TOKEN" ] || return 0
J=$(curl -s --max-time 12 -H "Authorization: token $TOKEN" \
"https://git.uncle6.me/api/v1/repos/$OWNER/$REPO/issues/$NUM" 2>/dev/null) || return 0
C=$(curl -s --max-time 12 -H "Authorization: token $TOKEN" \
+4 -1
View File
@@ -294,7 +294,10 @@ for d in /private/tmp/claude-501/*/*/tasks; do
done
[ "$RUNNING" -gt 0 ] && exit 0
TOKEN=$(git -C "$PROJ" remote get-url gitea 2>/dev/null | sed -E 's|.*//[^:]+:([^@]+)@.*|\1|') || TOKEN=""
# token 來源=env.envinkstone/ISEP#47),不再從 remote URL 抽(那會逼 token 明文嵌在 URL 裡)
_GT_LIB="$(dirname -- "$0")/../scripts/lib/gitea-token.sh"; [ -f "$_GT_LIB" ] && . "$_GT_LIB"
command -v gitea_token >/dev/null 2>&1 || exit 0
TOKEN=$(gitea_token) || TOKEN=""
[ -z "$TOKEN" ] && exit 0
# 🔴 org 是 inkstone,不是 Leo2026-08-13 搬遷;舊值讓本閘靜默失效到 08-16)
+6 -11
View File
@@ -43,17 +43,12 @@ for line in p.splitlines():
set -- $SPEC
OWNER="$1"; REPO="$2"; NUM="$3"
# token 從該 repo 的 gitea remote ;取不到就安靜結束(不影響派工)
for d in "$CLAUDE_PROJECT_DIR/products/$REPO" "$CLAUDE_PROJECT_DIR/matrix/$REPO" "$CLAUDE_PROJECT_DIR"; do
[ -d "$d/.git" ] || continue
URL=$(git -C "$d" remote get-url gitea 2>/dev/null) || continue
case "$URL" in *@*) break ;; esac
done
[ -n "${URL:-}" ] || exit 0
TOKEN=$(printf '%s' "$URL" | sed -E 's|.*//[^:]+:([^@]+)@.*|\1|')
HOST=$(printf '%s' "$URL" | sed -E 's|.*@([^/]+)/.*|\1|')
[ "$TOKEN" != "$URL" ] || exit 0
# token 來源=env.envinkstone/ISEP#47),不再從 remote URL 抽;取不到就安靜結束(不影響派工)
_GT_LIB="$(dirname -- "$0")/../scripts/lib/gitea-token.sh"; [ -f "$_GT_LIB" ] && . "$_GT_LIB"
command -v gitea_token >/dev/null 2>&1 || exit 0
TOKEN=$(gitea_token) || exit 0
[ -n "$TOKEN" ] || exit 0
HOST="git.uncle6.me"
curl -s -o /dev/null --max-time 8 \
-X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
+6 -4
View File
@@ -79,10 +79,12 @@ set -- $REF
OWNER="$1"; REPO="$2"; NUM="$3"
# 拿 token(拿不到就放行——這是本地環境問題,不是派工者的錯)
URL=$(git -C "${CLAUDE_PROJECT_DIR:-.}" remote get-url gitea 2>/dev/null || true)
case "${URL:-}" in *@*) ;; *) exit 0 ;; esac
TOKEN=$(printf '%s' "$URL" | sed -E 's|.*//[^:]+:([^@]+)@.*|\1|')
HOST=$(printf '%s' "$URL" | sed -E 's|.*@([^/]+)/.*|\1|')
# token 來源=env.envinkstone/ISEP#47),不再從 remote URL 抽(那會逼 token 明文嵌在 URL 裡)
_GT_LIB="$(dirname -- "$0")/../scripts/lib/gitea-token.sh"; [ -f "$_GT_LIB" ] && . "$_GT_LIB"
command -v gitea_token >/dev/null 2>&1 || exit 0
TOKEN=$(gitea_token) || exit 0
[ -n "$TOKEN" ] || exit 0
HOST="git.uncle6.me"
RESP=$(curl -s --max-time 15 -H "Authorization: token $TOKEN" \
"https://$HOST/api/v1/repos/$OWNER/$REPO/issues/$NUM" 2>/dev/null || true)
+4 -9
View File
@@ -62,17 +62,12 @@ set -u
PROJ="${CLAUDE_PROJECT_DIR:-$(pwd)}"
PAYLOAD=$(cat 2>/dev/null || echo '{}')
# ── token同 scripts/ticket 的做法(掃 remote 找帶憑證的,再退到環境變數)──
# ── token來源=env.envinkstone/ISEP#47),不再從 remote URL 抽 ──
# (舊法掃各 repo 的 remote 找帶憑證的網址,代表 token 得明文嵌在 URL 裡 → 會洩進 log)
TOKEN=""
if [ -z "${PR_VERDICT_FIXTURE:-}" ]; then
for d in "$PROJ" "$PROJ/products"/* "$PROJ/matrix"/* "$PROJ/polaris"/*; do
[ -e "$d/.git" ] || continue
U=$(git -C "$d" remote -v 2>/dev/null | grep -m1 'git\.uncle6\.me' | grep '@' | awk '{print $2}') || true
if [ -n "${U:-}" ]; then
T=$(printf '%s' "$U" | sed -E 's|.*//[^:]+:([^@]+)@.*|\1|')
[ "$T" != "$U" ] && { TOKEN="$T"; break; }
fi
done
_GT_LIB="$(dirname -- "$0")/../scripts/lib/gitea-token.sh"; [ -f "$_GT_LIB" ] && . "$_GT_LIB"
command -v gitea_token >/dev/null 2>&1 && TOKEN=$(gitea_token) || TOKEN=""
[ -n "$TOKEN" ] || TOKEN="${GITEA_TOKEN_CLAUDE_CODE:-${GITEA_TOKEN:-}}"
fi
+4 -4
View File
@@ -116,10 +116,10 @@ fi
# 不影響「擋下來」這個判斷本身。
HITS=""
if [ "$RESOURCE" = "issues" ]; then
REMOTES=$(git -C "${CLAUDE_PROJECT_DIR:-.}" remote -v 2>/dev/null || true)
CRED_LINE=$(printf '%s' "$REMOTES" | grep -m1 'git\.uncle6\.me' | grep -m1 '@' || true)
if [ -n "$CRED_LINE" ]; then
TOKEN=$(printf '%s' "$CRED_LINE" | sed -E 's|.*//[^:]+:([^@]+)@.*|\1|')
# token 來源=env.envinkstone/ISEP#47),不再從 remote URL 抽(那會逼 token 明文嵌在 URL 裡)
_GT_LIB="$(dirname -- "$0")/../scripts/lib/gitea-token.sh"; [ -f "$_GT_LIB" ] && . "$_GT_LIB"
TOKEN=""; command -v gitea_token >/dev/null 2>&1 && TOKEN=$(gitea_token) || TOKEN=""
if [ -n "$TOKEN" ]; then
# 只認雙引號的 "title": "..."curl -djson.dumps 輸出的實際格式,也是
# 2026-08-27 那 13 張票真實用的寫法,見 scripts/test-ticket-api-bypass-guard.sh
# 既有的模擬指令)。刻意不支援單引號版本——這段 python 要塞進 bash 單引號
+26
View File
@@ -0,0 +1,26 @@
#!/bin/sh
# git-credential-gitea.sh — git credential helper:把 Gitea 憑證從 env.env 餵給 git
# 讓 remote URL 不必嵌 tokeninkstone/ISEP#47)。URL 乾淨 ⇒ git 把它印出來也不洩 token。
#
# 安裝(gitea-bootstrap.sh 會自動設好,這裡是手動法):
# git config credential.helper "$(git rev-parse --show-toplevel)/scripts/git-credential-gitea.sh"
# 只對 git.uncle6.me 的 get 供憑證;storeerase 不做事(token 不由 git 保管)。
DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
. "$DIR/lib/gitea-token.sh"
[ "${1:-}" = "get" ] || exit 0
HOST=""
while IFS='=' read -r k v; do
[ -z "$k" ] && break
[ "$k" = "host" ] && HOST="$v"
done
case "$HOST" in
git.uncle6.me|"") : ;;
*) exit 0 ;;
esac
TOK=$(gitea_token) || exit 0
[ -n "$TOK" ] || exit 0
printf 'username=claude-code\n'
printf 'password=%s\n' "$TOK"
+20 -5
View File
@@ -16,14 +16,29 @@ set -eu
REPO=""
[ "${1:-}" = "-r" ] && { REPO="${2:?-r 後面要接 owner/repo}"; }
REMOTE=$(git remote get-url gitea 2>/dev/null) || {
echo "✗ 當前目錄沒有名為 gitea 的 remote。請 cd 到目標 repo,或先加 remote。" >&2; exit 1; }
# 取當前 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=$(printf '%s' "$REMOTE" | sed -E 's|.*//[^:]+:([^@]+)@.*|\1|')
[ "$TOKEN" = "$REMOTE" ] && { echo "✗ gitea remote URL 裡沒有 token,無法取得認證。" >&2; exit 1; }
HOST=$(printf '%s' "$REMOTE" | sed -E 's|.*@([^/]+)/.*|\1|')
# 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)。順序=出貨流程:
+5 -6
View File
@@ -30,16 +30,15 @@ if [[ ! -f "$LABELS_YAML" ]]; then
exit 1
fi
# --- token跟 InkStoneCo 頂層 gitea remote 拿同一把(不落地檔案、不印出來 ---
# --- token來源=env.envinkstone/ISEP#47),不再從 remote URL 抽(那會逼 token 明文嵌在 URL 裡 ---
TOKEN="${GITEA_TOKEN:-}"
if [[ -z "$TOKEN" ]]; then
INKSTONE_TOP="${INKSTONE_TOP:-$HOME/Documents/tech_projects/InkStoneCo}"
if [[ -d "$INKSTONE_TOP/.git" ]]; then
TOKEN="$(git -C "$INKSTONE_TOP" remote get-url gitea 2>/dev/null | sed -E 's|.*//[^:]+:([^@]+)@.*|\1|')"
fi
_GT_LIB="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/lib/gitea-token.sh"
[[ -f "$_GT_LIB" ]] && . "$_GT_LIB"
command -v gitea_token >/dev/null 2>&1 && TOKEN="$(gitea_token || true)"
fi
if [[ -z "$TOKEN" ]]; then
echo "拿不到 Gitea token——設 GITEA_TOKEN 環境變數,或確認 $INKSTONE_TOP 的 gitea remote 存在" >&2
echo "拿不到 Gitea token——設 GITEA_TOKEN_CLAUDE_CODE 環境變數,或在 .env/頂層 .env 放 GITEA_TOKEN_CLAUDE_CODE=" >&2
exit 1
fi
+44
View File
@@ -0,0 +1,44 @@
#!/bin/sh
# gitea-token.sh — 解出 Gitea 憑證(GITEA_TOKEN_CLAUDE_CODE)。source 它、呼叫 gitea_token()。
#
# 為什麼存在(inkstone/ISEP#47):舊法用 `git remote get-url gitea | sed` 從 remote URL
# 抽 token ⇒ token 必須明文嵌在 URL 裡 ⇒ 任何印出 remote URL 的訊息(git-lfs 的
# locksverify 那行)都會把它洩進 transcript/log。來源統一到這裡後,remote URL 才能改乾淨。
#
# 解析順序(先環境、後檔案——雲端只有 env、本機有 .env,兩邊都涵蓋):
# 1) 環境變數 $GITEA_TOKEN_CLAUDE_CODE(雲端 make-cloud-env 注入;本機 source .env 後也在)
# 2) .env 的 GITEA_TOKEN_CLAUDE_CODE=(先找 $CLAUDE_PROJECT_DIR/.env,再找 git 根的 .env
# —— 與既有 gitea_arm_token()scripts/lib/gitea-arm-common.sh)同一條路,照既有寫法走。
# 讀不到 → 印空字串、回非 0(呼叫者必須自己判空,不能把空字串當成功)。
#
# 🔴 只回值、不印別的——絕不 echo 出 token 以外的東西(避免洩進 log)。
# 內部:從一個 .env 檔抓 GITEA_TOKEN_CLAUDE_CODE=,去掉外圍引號。
_gitea_token_from_file() {
[ -f "$1" ] || return 1
_gt_val=$(grep '^GITEA_TOKEN_CLAUDE_CODE=' "$1" 2>/dev/null | head -1 | cut -d= -f2-)
_gt_val=${_gt_val%\"}; _gt_val=${_gt_val#\"}
_gt_val=${_gt_val%\'}; _gt_val=${_gt_val#\'}
[ -n "$_gt_val" ] || return 1
printf '%s' "$_gt_val"
return 0
}
gitea_token() {
# 1) 環境變數優先
if [ -n "${GITEA_TOKEN_CLAUDE_CODE:-}" ]; then
printf '%s' "$GITEA_TOKEN_CLAUDE_CODE"
return 0
fi
# 2) $CLAUDE_PROJECT_DIR/.env
if [ -n "${CLAUDE_PROJECT_DIR:-}" ] && _gitea_token_from_file "$CLAUDE_PROJECT_DIR/.env"; then
return 0
fi
# 3) git 根的 .env
_gt_root=$(git rev-parse --show-toplevel 2>/dev/null || true)
if [ -n "${_gt_root:-}" ] && _gitea_token_from_file "$_gt_root/.env"; then
return 0
fi
printf ''
return 1
}
+16 -11
View File
@@ -143,13 +143,12 @@ SETUP="$(cd "$(dirname "$0")/.." && pwd)/docs/cloud-setup-script.sh"
[ -f "$SETUP" ] || { echo "🔴 找不到 $SETUP" >&2; exit 1; }
MISSING=()
# 🔴 兩個檔分開產(2026-09-18 實撞):原本兩段放同一檔、中間用「════」分隔,
# leo 把分隔線一起貼進 Setup script 欄 ⇒ 雲端開機 exit 127「'════…': command not found」,
# 每一趟 cloud-worker 在開機就死。⇒ 變數一個檔(只有 KEY=VALUE)、開機腳本一個檔(逐位元組=cloud-setup-script.sh),
# **兩個檔都整份貼**,沒有任何要人自己裁切的地方。
SETUP_OUT="${OUT%.txt}-setup-script.sh"
{
echo "claude.ai → Cloud environments → 你的環境。下面兩塊各自貼進對應欄位。"
echo "版本:${VER}${VER_DATE})——${VER_WHY}"
echo "${PREV_VER} 多:${ADDED:-(無)}|少:${REMOVED:-(無)}"
echo "產生時間:$(date '+%Y-%m-%d %H:%M')"
echo
echo "════════ ① Environment variables(一行一個,名字與值分開填)════════"
for n in "${NEEDED[@]}"; do
if v=$(lookup "$n"); then
echo "$n=$v"
@@ -159,13 +158,19 @@ MISSING=()
fi
done
for kv in "${FIXED[@]}"; do echo "$kv"; done
echo
echo "════════ ② Setup script(整段貼,一字不改)════════"
cat "$SETUP"
} > "$OUT"
chmod 600 "$OUT"
cp "$SETUP" "$SETUP_OUT"
chmod 600 "$OUT" "$SETUP_OUT"
{
echo "claude.ai → Cloud environments → 你的環境(${VER}${VER_DATE}${VER_WHY}"
echo "${PREV_VER} 多:${ADDED:-(無)}|少:${REMOVED:-(無)}"
echo "① Environment variables 欄:整份貼 $(basename "$OUT")"
echo "② Setup script 欄:整份貼 $(basename "$SETUP_OUT")(一字不改,不要加任何東西)"
echo "貼完刪掉這三個檔。"
} > "${OUT%.txt}-README.txt"
echo "✅ 產生完成:$OUT"
echo " 版本:${VER}(比 ${PREV_VER} 多:${ADDED:-}|少:${REMOVED:-}"
echo " 變數 ${#NEEDED[@]} 個|找不到值的 ${#MISSING[@]}${MISSING[*]:-}"
echo " 🔴 這個檔含金鑰真身:貼完就刪(rm '$OUT'),它刻意不在任何 repo 裡。"
echo " 開機腳本:$SETUP_OUT"
echo " 🔴 含金鑰真身:貼完就刪(rm '${OUT%.txt}'*),它刻意不在任何 repo 裡。"
+6 -1
View File
@@ -61,7 +61,12 @@ printf '%s\n' "$OUT_LINE" | grep -q "找不到值的 0 個" \
&& ok "A4 收尾那行說「找不到值的 0 個」" || bad "A4 收尾計數不對:$OUT_LINE"
# 版本(leo 2026-09-18):產物檔頭帶現行版本號;清單改了沒升版要拒絕
grep -q "^版本:v[0-9]" "$F" && ok "V1 產物檔頭寫著版本號" || bad "V1 產物檔頭沒有版本號"
grep -q "v[0-9]" "${F%.txt}-README.txt" && ok "V1 README 寫著版本號" || bad "V1 README 沒有版本號"
# 2026-09-18 實撞:兩段同檔、分隔線被貼進 Setup script 欄 ⇒ 雲端開機 exit 127。分檔後兩件事要守住:
bad_lines=$(grep -cvE '^[A-Z][A-Z0-9_]*=' "$F")
chk 0 "$bad_lines" "V3 變數檔每一行都是 KEY=VALUE(沒有分隔線、說明文字)"
cmp -s "${F%.txt}-setup-script.sh" "$(cd "$(dirname "$S")/.." && pwd)/docs/cloud-setup-script.sh" \
&& ok "V4 開機腳本檔與 docs/cloud-setup-script.sh 逐位元組相同" || bad "V4 開機腳本檔被加料了"
VT="$(cd "$(dirname "$S")/.." && pwd)/docs/cloud-env-versions.tsv"; VB="$VT.bak-test"
cp "$VT" "$VB"; printf 'v999\t2099-01-01\tNOPE_ONLY\t測試用\n' >> "$VT"
INKSTONE_ROOT="$FAKE" OUT_DIR="$OUTD" bash "$S" >/dev/null 2>&1; rc=$?
+14 -8
View File
@@ -61,15 +61,21 @@ done
[ -f "$ROSTER" ] || { echo "找不到 roster$ROSTER" >&2; exit 1; }
# ── Gitea base含憑證,**絕不可印出來**)────────────────────────────────
REMOTE=$(git -C "$ROOT" remote get-url gitea 2>/dev/null || true)
[ -n "$REMOTE" ] || { echo "本 repo 沒有 gitea remote,無法取 repo。" >&2; exit 1; }
# ── Gitea base乾淨網址;token 來源=env.env,不從 URL 抽——inkstone/ISEP#47──────────
_GT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
[ -f "$_GT_DIR/lib/gitea-token.sh" ] && . "$_GT_DIR/lib/gitea-token.sh"
REMOTE=$(git -C "$ROOT" remote get-url gitea 2>/dev/null || git -C "$ROOT" remote get-url origin 2>/dev/null || true)
[ -n "$REMOTE" ] || { echo "本 repo 沒有 gitea/origin remote,無法取 repo。" >&2; exit 1; }
# 去掉網址裡可能還嵌著的舊憑證(遷移期相容)→ 乾淨 baseclone/fetch 靠 credential helper 認證
REMOTE=$(printf '%s' "$REMOTE" | sed -E 's#//[^/@]*@#//#')
BASE=${REMOTE%/InkStoneCo.git}
BASE=${BASE%/InkStoneCo}
SELF=$(basename "$REMOTE" .git)
SECRET=$(printf '%s' "$REMOTE" | sed -nE 's|.*//[^:]+:([^@]+)@.*|\1|p')
# 任何要外流的字串都先過這關(錯誤訊息可能夾帶 clone URL)
# scrub 用的 SECRETtoken 從 env.env 取(不從 URL 抽),用來遮掉萬一夾帶在錯誤訊息裡的 token
SECRET=""; command -v gitea_token >/dev/null 2>&1 && SECRET=$(gitea_token || true)
scrub() { if [ -n "$SECRET" ]; then sed "s|$SECRET|***|g"; else cat; fi; }
# clone/fetch 的認證:乾淨 URL + credential helpertoken 不進 URL、不洩進 log
GIT_CRED=""; [ -f "$_GT_DIR/git-credential-gitea.sh" ] && GIT_CRED="-c credential.helper=$_GT_DIR/git-credential-gitea.sh"
mkdir -p "$CACHE"
# 自己這個 repo 一律讀工作副本 ⇒ 快取裡若留著一份舊的自己,是誤導來源(會被人拿去讀)
@@ -95,7 +101,7 @@ for repo in $REPOS; do
if [ -d "$dst/.git" ]; then
if [ "$DO_FETCH" = 1 ]; then
br=$(git -C "$dst" symbolic-ref --short HEAD 2>/dev/null || echo main)
git -C "$dst" fetch --quiet origin "$br" 2>&1 | scrub >&2 || true
git $GIT_CRED -C "$dst" fetch --quiet origin "$br" 2>&1 | scrub >&2 || true
git -C "$dst" reset --quiet --hard FETCH_HEAD 2>/dev/null || true
fi
else
@@ -104,7 +110,7 @@ for repo in $REPOS; do
fi
# blobless + sparse:只下載 wiki 目錄的內容,但保留完整 commit 歷史
#(要歷史才算得出「每個檔最後更新是哪天」——淺 clone 會讓所有檔同一天)
if ! err=$(git clone --quiet --filter=blob:none --sparse "$BASE/$repo.git" "$dst" 2>&1 | scrub); then
if ! err=$(git $GIT_CRED clone --quiet --filter=blob:none --sparse "$BASE/$repo.git" "$dst" 2>&1 | scrub); then
printf '%s\tclone-failed\t%s\n' "$repo" "$(printf '%s' "$err" | tr '\n' ' ')" >> "$STATUS_TSV"
rm -rf "$dst"; continue
fi
@@ -312,7 +318,7 @@ if gap > 0:
f"Gitea `Leo/*` 在 {ASOF} 實查是 **{TOTAL} 個**,名單 {len(repos)} 個 ⇒ **還有 {gap} 個沒被點名**。")
w(" 補法(人發起,一次呼叫,不排程)——拿到完整清單、把缺的名字加進名單再重跑:")
w(" ```")
w(" TOKEN=$(git remote get-url gitea | sed -E 's|.*//[^:]+:([^@]+)@.*|\\1|')")
w(" TOKEN=$GITEA_TOKEN_CLAUDE_CODE # 來源=env.env,不從 remote URL 抽(inkstone/ISEP#47")
w(" curl -s -H \"Authorization: token $TOKEN\" \\")
w(" 'https://git.uncle6.me/api/v1/orgs/Leo/repos?limit=100' | python3 -c \\")
w(" 'import sys,json;[print(r[\"name\"]) for r in json.load(sys.stdin)]'")