#!/usr/bin/env python3 """gitea_token.py — 解出 Gitea 憑證(GITEA_TOKEN_CLAUDE_CODE)的唯一 Python 來源。 為什麼存在(inkstone/ISEP#47):舊法用 `git remote -v` + `re.search(r"//[^:/]+:([^@]+)@")` 從 remote URL 抽 token ⇒ token 必須明文嵌在 URL 裡 ⇒ 任何印出 remote URL 的訊息(git-lfs 的 locksverify 那行)都會把它洩進 transcript/log。來源統一到這裡後,remote URL 才能改乾淨。 這是 `scripts/lib/gitea-token.sh` 的 Python 對照——**順序刻意一致**,讓 shell 與 Python 兩邊解出來的是同一把(兩份必然漂移,所以只留一條路:先 env、再 .env)。 解析順序(先環境、後檔案——雲端只有 env、本機有 .env,兩邊都涵蓋): 1) 環境變數 GITEA_TOKEN_CLAUDE_CODE,再退 GITEA_TOKEN(本機 source .env 後也在) 2) $CLAUDE_PROJECT_DIR/.env 的 GITEA_TOKEN_CLAUDE_CODE= 3) git 根目錄的 .env 的 GITEA_TOKEN_CLAUDE_CODE= 讀不到 → 回 None(呼叫者自己判空,不能把 None 當成功)。 🔴 只回值、不印別的——絕不把 token 印進 log。 🔴 不從 remote URL 抽 token——那正是 ISEP#47 要根除的洩漏源。 """ import os import subprocess def _from_file(path): """從一個 .env 檔抓 GITEA_TOKEN_CLAUDE_CODE=,去掉外圍引號。抓不到回 None。""" try: with open(path, encoding="utf-8", errors="replace") as f: for line in f: if line.startswith("GITEA_TOKEN_CLAUDE_CODE="): v = line.split("=", 1)[1].strip() if len(v) >= 2 and v[0] == v[-1] and v[0] in ("'", '"'): v = v[1:-1] return v or None except OSError: pass return None def _git_root(): """git rev-parse --show-toplevel(跟 shell 版同一把尺)。拿不到回 None。""" try: out = subprocess.run(["git", "rev-parse", "--show-toplevel"], capture_output=True, text=True, timeout=20) root = (out.stdout or "").strip() return root or None except Exception: return None def gitea_token(): """回 Gitea token 字串,或 None(拿不到)。順序見模組 docstring。""" for env in ("GITEA_TOKEN_CLAUDE_CODE", "GITEA_TOKEN"): v = os.environ.get(env) if v: return v pd = os.environ.get("CLAUDE_PROJECT_DIR") if pd: v = _from_file(os.path.join(pd, ".env")) if v: return v root = _git_root() if root: v = _from_file(os.path.join(root, ".env")) if v: return v return None