Compare commits

...

8 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
Leo 3bc2147e36 release: v0.30.9——雲端環境 v7(補回 Telegram 兩把與 ISEP_DRAIN)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 20:00:24 +08:00
Leo 6047dff71b 雲端環境 v7:收回現行雲端有、v1–v6 漏收的 TELEGRAM_CHAT_ID/TELEGRAM_BOT_TOKEN(MIRA_ALLOWED 點名放行,B8 其餘照擋)與固定值 ISEP_DRAIN=1
leo 09-18 貼出現行雲端環境才發現。已驗:chat id 與 leo 貼的相同;bot getMe=總管名片王(雲端工人發訊那支)。test-make-cloud-env.sh 13/13(B8x 反例照樣抓得到)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 20:00:24 +08:00
Leo 147fdb1482 release: v0.30.8——雲端環境變數清單有版本(v6),產物放桌面
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 19:44:47 +08:00
Leo ef11871ba4 雲端環境變數清單有版本了:docs/cloud-env-versions.tsv 記每版 key,產生器印版本與差異、改清單沒升版就拒絕,產物預設放桌面(v6:+ARCRUN_NS_UNCLE6/GITHUB_MIRROR_TOKEN/GITHUB_ACCOUNT_NAME)
leo 2026-09-18:「你這裡記錄每版的 credential 的 key,但 value 自動產生,我就貼上去,你要有版本,因為這件事做了很多次,但你都沒記錄版本」
v1–v5 由 git 歷史還原(f855d82、ca9ea99、7aef943、cb310e9、dd86fef)。test-make-cloud-env.sh 13/13(新增 V1 檔頭版本、V2 不一致拒絕)。
(inkstone/ISEP#158、inkstone/InkStoneCo#98 c7739)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 19:44:40 +08:00
15 changed files with 231 additions and 76 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.7",
"version": "0.30.10",
"keywords": [
"inkstone",
"guardrails",
+11
View File
@@ -0,0 +1,11 @@
# 雲端環境(claude.ai Cloud environment)變數清單的版本紀錄——**只記名字,不記值**。
# leo 2026-09-18:「你這裡記錄每版的 credential 的 key,但 value 自動產生,我就貼上去,你要有版本,因為這件事做了很多次,但你都沒記錄版本」
# 產生器 scripts/make-cloud-env.sh 讀本檔最後一版的號碼印在產物檔頭,並列出與前一版的差異。
# 欄位:版本<TAB>日期<TAB>key(逗號分隔,順序=NEEDED<TAB>為什麼變
v1 2026-08-20 GITEA_TOKEN_CLAUDE_CODE 首版:雲端 bootstrap 與 plugin 安裝(f855d82
v2 2026-09-01 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE 雲端拿得到 youlin 的 CF tokenISEP#115 c5526ca9ea99
v3 2026-09-01 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688 leo「youlin 和 geek6688 都全給」(ISEP#115 c554155427aef943
v4 2026-09-01 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688,UNCLE6_CF_API_KEY 出貨三站實際部署到 uncle6ISEP#115 c5577cb310e9
v5 2026-09-01 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688,UNCLE6_CF_API_KEY,ARCRUN_NS_YOULIN 出貨線實例座標(Arcrun#195dd86fefv0.21.0
v6 2026-09-18 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688,UNCLE6_CF_API_KEY,ARCRUN_NS_YOULIN,ARCRUN_NS_UNCLE6,GITHUB_MIRROR_TOKEN,GITHUB_ACCOUNT_NAME 雲端要能出 stageproduncle6 namespaceGitHub 鏡像兩把(InkStoneCo#98 c7739ISEP#158
v7 2026-09-18 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688,UNCLE6_CF_API_KEY,ARCRUN_NS_YOULIN,ARCRUN_NS_UNCLE6,GITHUB_MIRROR_TOKEN,GITHUB_ACCOUNT_NAME,TELEGRAM_CHAT_ID,TELEGRAM_BOT_TOKEN,ISEP_DRAIN 收回雲端現行卻不在 v1v6 的三個:TELEGRAM_CHAT_IDTELEGRAM_BOT_TOKEN(通知出口,值在 polaris/mira/.env,點名放行)+ISEP_DRAIN=1 固定值(leo 09-18 貼出現行雲端環境才發現漏收)
1 # 雲端環境(claude.ai Cloud environment)變數清單的版本紀錄——**只記名字,不記值**。
2 # leo 2026-09-18:「你這裡記錄每版的 credential 的 key,但 value 自動產生,我就貼上去,你要有版本,因為這件事做了很多次,但你都沒記錄版本」
3 # 產生器 scripts/make-cloud-env.sh 讀本檔最後一版的號碼印在產物檔頭,並列出與前一版的差異。
4 # 欄位:版本<TAB>日期<TAB>key(逗號分隔,順序=NEEDED)<TAB>為什麼變
5 v1 2026-08-20 GITEA_TOKEN_CLAUDE_CODE 首版:雲端 bootstrap 與 plugin 安裝(f855d82)
6 v2 2026-09-01 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE 雲端拿得到 youlin 的 CF token(ISEP#115 c5526,ca9ea99)
7 v3 2026-09-01 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688 leo「youlin 和 geek6688 都全給」(ISEP#115 c5541/5542,7aef943)
8 v4 2026-09-01 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688,UNCLE6_CF_API_KEY 出貨三站實際部署到 uncle6(ISEP#115 c5577,cb310e9)
9 v5 2026-09-01 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688,UNCLE6_CF_API_KEY,ARCRUN_NS_YOULIN 出貨線實例座標(Arcrun#195,dd86fef/v0.21.0)
10 v6 2026-09-18 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688,UNCLE6_CF_API_KEY,ARCRUN_NS_YOULIN,ARCRUN_NS_UNCLE6,GITHUB_MIRROR_TOKEN,GITHUB_ACCOUNT_NAME 雲端要能出 stage/prod:uncle6 namespace+GitHub 鏡像兩把(InkStoneCo#98 c7739;ISEP#158)
11 v7 2026-09-18 GITEA_TOKEN_CLAUDE_CODE,CLOUDFLARE_API_TOKEN_YOULIN_CC_USE,CLOUDFLARE_ACCOUNT_ID_YOULIN,YOULIN_MCP_OWNER_SECRETS,STAGE_USERNAME_YOULIN,STAGE_PASSWORD_YOULIN,CLOUDFLARE_API_TOKEN_CC_SHIPPING_CORE,CLOUDFLARE_ACCOUNT_ID_GEEK6688,UNCLE6_CF_API_KEY,ARCRUN_NS_YOULIN,ARCRUN_NS_UNCLE6,GITHUB_MIRROR_TOKEN,GITHUB_ACCOUNT_NAME,TELEGRAM_CHAT_ID,TELEGRAM_BOT_TOKEN,ISEP_DRAIN 收回雲端現行卻不在 v1–v6 的三個:TELEGRAM_CHAT_ID/TELEGRAM_BOT_TOKEN(通知出口,值在 polaris/mira/.env,點名放行)+ISEP_DRAIN=1 固定值(leo 09-18 貼出現行雲端環境才發現漏收)
+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
}
+65 -16
View File
@@ -5,7 +5,7 @@
# 誤觸時怎麼關: 這支不擋任何東西。不想產生就別跑;產物在版控外,刪掉即可。
#
# 用法:bash scripts/make-cloud-env.sh
# 產物:~/.claude/cloud-env/<日期>.txt(權限 600**不在任何 repo 裡**
# 產物:~/Desktop/cloud-env-<版本>-<日期>.txt(權限 600**不在任何 repo 裡**;版本紀錄 docs/cloud-env-versions.tsv
# 本腳本只寫「變數名字」,值在執行當下才從既有 .env 讀出來寫進產物 —— 值不進版控、不進對話。
set -euo pipefail
@@ -44,6 +44,28 @@ NEEDED=(
# 只有 namespace 是機敏 ⇒ 走環境變數。原本只住在 ~/.arcrun/config.yaml 的 api_key
# 那個檔在家目錄、不隨 clone 走 ⇒ 雲端永遠拿不到。
ARCRUN_NS_YOULIN # youlin 實例的 namespaceX-Arcrun-API-Key
ARCRUN_NS_UNCLE6 # uncle6 實例的 namespace:出貨第 1、19、20 站派給 uncle6 工作流(inkstone/InkStoneCo#98 → comment 7739
# ── prod 出貨把產物推上 GitHub 鏡像(第 14、21、22 站)──
# 🔴 名字進清單 ≠ 雲端可以自由寫 GitHub`github-contact-guard.sh` 照樣擋,
# 寫入仍要 D20 開閘(保險檔落在哪由 inkstone/ISEP#157 處理)。
# 進清單是因為「拿不到這兩個名字」會讓 prod 那半在雲端**連開閘之後都走不下去**。
GITHUB_MIRROR_TOKEN # GitHub 鏡像 repo 的寫入 token(走標頭,不進網址)
GITHUB_ACCOUNT_NAME # 上面那把的帳號名(組鏡像 repo 路徑用,不是機密)
# ── 雲端工人的通知出口(v7,2026-09-18:leo 貼出現行雲端環境才發現 v1–v6 漏收)──
# 雲端走 Bot API 直送(`scripts/isep-notify`leo21c 的 notify_leo 已於 08-14 消失,InkStoneCo#110
TELEGRAM_CHAT_ID # 收訊的 chat id(不是機密)
TELEGRAM_BOT_TOKEN # 通知 bot 的 token
)
# 🔓 例外:值只住在 polaris/mira/.env、但**不是**打得到 leo21c 的憑證——點名放行,其他照擋(B8)
# Telegram 兩把是通知 bot,不碰任何 CF 帳號;雲端 09-18 前就已手動設了同樣的值。
MIRA_ALLOWED=(TELEGRAM_CHAT_ID TELEGRAM_BOT_TOKEN)
# ── 固定值(不是金鑰,產生器直接寫)──
FIXED=(
"ISEP_DRAIN=1" # 雲端工人=接票環境:ticket pick 自帶 --pool、佇列閘不准有票就收工(ISEP#130)
)
#
# 🔴 這份清單的邊界——加一把之前先問「這把打得到哪個實例」,不是「這把方便嗎」
@@ -91,23 +113,42 @@ lookup() { # $1=變數名 → 印出值(找不到就空)
return 1
}
# 預設丟 ~/.claude/cloud-env(權限 700)。要放別的地方= OUT_DIR=~/Desktop bash scripts/make-cloud-env.sh
OUT_DIR="${OUT_DIR:-$HOME/.claude/cloud-env}"
# ── 版本(leo 2026-09-18:「你要有版本,因為這件事做了很多次,但你都沒記錄版本」)──
# 真相源=docs/cloud-env-versions.tsv(只記 key,不記值)。最後一列=現行版。
# 🔴 NEEDED 跟最後一列的 key 不一致 ⇒ 拒絕產生:改了清單就必須在 tsv 加一版。
VERSIONS="$(cd "$(dirname "$0")/.." && pwd)/docs/cloud-env-versions.tsv"
[ -f "$VERSIONS" ] || { echo "🔴 找不到 $VERSIONS" >&2; exit 1; }
CUR_LINE=$(grep -v '^#' "$VERSIONS" | grep -v '^[[:space:]]*$' | tail -1)
PREV_LINE=$(grep -v '^#' "$VERSIONS" | grep -v '^[[:space:]]*$' | tail -2 | head -1)
VER=$(printf '%s' "$CUR_LINE" | cut -f1); VER_DATE=$(printf '%s' "$CUR_LINE" | cut -f2)
VER_KEYS=$(printf '%s' "$CUR_LINE" | cut -f3); VER_WHY=$(printf '%s' "$CUR_LINE" | cut -f4)
PREV_VER=$(printf '%s' "$PREV_LINE" | cut -f1); PREV_KEYS=$(printf '%s' "$PREV_LINE" | cut -f3)
FIXED_NAMES=(); for kv in "${FIXED[@]}"; do FIXED_NAMES+=("${kv%%=*}"); done
NEEDED_JOINED=$(IFS=,; printf '%s' "${NEEDED[*]} ${FIXED_NAMES[*]}" | tr ' ' ',')
if [ "$NEEDED_JOINED" != "$VER_KEYS" ]; then
echo "🔴 NEEDED 與 $VERSIONS 最後一版($VER)的 key 不一致——改了清單就要在 tsv 加一列新版本再跑。" >&2
echo " NEEDED$NEEDED_JOINED" >&2
echo " $VER $VER_KEYS" >&2
exit 2
fi
ADDED=$(comm -13 <(tr , '\n' <<<"$PREV_KEYS" | sort) <(tr , '\n' <<<"$VER_KEYS" | sort) | paste -sd, -)
REMOVED=$(comm -23 <(tr , '\n' <<<"$PREV_KEYS" | sort) <(tr , '\n' <<<"$VER_KEYS" | sort) | paste -sd, -)
# 預設丟桌面(leo 2026-09-18 指定)。要放別的地方= OUT_DIR=<目錄> bash scripts/make-cloud-env.sh
OUT_DIR="${OUT_DIR:-$HOME/Desktop}"
mkdir -p "$OUT_DIR"
# 只在「這個目錄是我們自己造的預設位置」時才收緊權限——
# OUT_DIR 可被覆寫,不該對使用者指定的既有目錄(例如 ~/Desktop)動權限。
[ "$OUT_DIR" = "$HOME/.claude/cloud-env" ] && chmod 700 "$OUT_DIR"
OUT="$OUT_DIR/$(date +%Y%m%d-%H%M%S).txt"
OUT="$OUT_DIR/cloud-env-${VER}-$(date +%Y%m%d-%H%M).txt"
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 "產生時間:$(date '+%Y-%m-%d %H:%M')"
echo
echo "════════ ① Environment variables(一行一個,名字與值分開填)════════"
for n in "${NEEDED[@]}"; do
if v=$(lookup "$n"); then
echo "$n=$v"
@@ -116,12 +157,20 @@ MISSING=()
MISSING+=("$n")
fi
done
echo
echo "════════ ② Setup script(整段貼,一字不改)════════"
cat "$SETUP"
for kv in "${FIXED[@]}"; do echo "$kv"; done
} > "$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 裡。"
+15 -1
View File
@@ -35,6 +35,7 @@ NEEDED=()
while IFS= read -r l; do NEEDED+=("$l"); done < <(
sed -n '/^NEEDED=(/,/^)/p' "$S" | sed -E '1d;$d; s/[[:space:]]*#.*$//; s/[[:space:]]//g' | grep -v '^$')
[ "${#NEEDED[@]}" -gt 0 ] || { echo "🔴 從 $S 讀不出 NEEDED,測試本身壞了"; exit 1; }
MIRA_ALLOWED=($(sed -n 's/^MIRA_ALLOWED=(\(.*\))/\1/p' "$S"))
echo "清單(只有名字):${NEEDED[*]}"
echo
@@ -59,6 +60,19 @@ chk 0 "$miss" "A3 每個名字都帶著值出現在 ① 區塊"
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%.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=$?
mv "$VB" "$VT"
chk 2 "$rc" "V2 清單與最後一版 key 不一致時拒絕產生(離開碼 2)"
# 缺值時不准假綠:拿掉第一個,它要變成 <🔴 …> 且計數變 1
grep -v "^${NEEDED[0]}=" "$FAKE/.env" > "$FAKE/.env.tmp" && mv "$FAKE/.env.tmp" "$FAKE/.env"
OUT2=$(INKSTONE_ROOT="$FAKE" OUT_DIR="$OUTD" bash "$S" 2>&1)
@@ -104,7 +118,7 @@ else
if r=$(real_src "$n"); then
len="${r%% *}"; src="${r#* }"
printf ' %-38s → 找得到(長度 %s)來自 %s\n' "$n" "$len" "${src#$BASE/}"
case "$src" in */polaris/mira/.env) prod=$((prod+1)); echo " 🔴 ↑ 這是 leo21c 現役正式環境那份 .env";; esac
case "$src" in */polaris/mira/.env) if printf '%s\n' "${MIRA_ALLOWED[@]}" | grep -qx "$n"; then echo " 🔓 ↑ 點名放行(MIRA_ALLOWED:通知 bot,不碰 CF"; else prod=$((prod+1)); echo " 🔴 ↑ 這是 leo21c 現役正式環境那份 .env"; fi;; esac
else
printf ' %-38s → 🔴 這台拿不到\n' "$n"; gone=$((gone+1))
fi
+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)]'")