晨間狀態檢查在 cron 下實際不會運作:cron 沒有互動 shell 的環境變數, 而 ~/.bashrc 多數在非互動時提早 return,導致範例腳本永遠拿不到 GITEA_TOKEN 而安靜結束,功能等於無效。 - examples/check-gitea-prs.sh 新增 load_env_var():依序從 ~/.roles/.env、 ~/.bashrc、~/.profile 只抽取所需變數的那一行並 eval 該行賦值, 不要求使用者把權杖複製到新檔案,也不必寫進 crontab - role skill 補設定來源說明:非機密放 ~/.roles/.env(權限 600), 權杖留在原本位置不要複製副本 - 版號 0.0.4 → 0.0.5 驗證:以 env -i 模擬 cron 環境(完全無環境變數)執行,成功取得設定並列出 待合併 PR;確認輸出無疑似權杖字串、.env 不含權杖。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
96 lines
3.7 KiB
Bash
Executable File
96 lines
3.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# ==============================================================================
|
||
# 用途:晨間狀態檢查範例 —— 列出 Gitea 上仍開啟中的 PR,讓角色早上能主動提醒。
|
||
# 更新時間:2026/07/29 09:05:00
|
||
# 相依:bash、curl、node(不使用 jq)。
|
||
#
|
||
# 安裝:複製到 ~/.roles/<角色 ID>.checks/ 並加上執行權限,然後重跑 --install-cron
|
||
# cp check-gitea-prs.sh ~/.roles/YUI01.checks/
|
||
# chmod +x ~/.roles/YUI01.checks/check-gitea-prs.sh
|
||
#
|
||
# 設定(環境變數):
|
||
# GITEA_HOST Gitea 站台,例如 https://gitea.example.com
|
||
# GITEA_TOKEN 存取權杖(本腳本不會輸出它;晨間檢查寫入記憶前仍會再遮蔽一次)
|
||
# GITEA_REPOS 逗號分隔的 owner/repo 清單,例如 plugins/generic,plugins/code
|
||
#
|
||
# cron 沒有互動 shell 的環境變數,且 ~/.bashrc 多數在非互動時會提早 return,
|
||
# 因此本腳本會依序從 ~/.roles/.env、~/.bashrc、~/.profile **只抽取所需變數的那一行**,
|
||
# 不要求使用者把權杖複製到新檔案,也不必寫進 crontab。
|
||
# 建議把非機密設定(HOST/REPOS)放 ~/.roles/.env,權杖留在原本的位置。
|
||
#
|
||
# 慣例:**沒有需要回報的事情就不要輸出任何內容**。晨間檢查只在有輸出時才寫記憶,
|
||
# 靜默即代表「一切正常,不必打擾使用者」。
|
||
# ==============================================================================
|
||
|
||
set -u
|
||
|
||
# 從使用者既有的設定檔補齊未設定的變數。只取用「NAME=」開頭的那一行並 eval 該行賦值,
|
||
# 風險等同使用者自己 source 這些檔案;不會讀取或輸出其他內容。
|
||
load_env_var() {
|
||
local name="$1" file line current
|
||
eval "current=\${$name:-}"
|
||
[ -n "$current" ] && return 0
|
||
for file in "$HOME/.roles/.env" "$HOME/.bashrc" "$HOME/.profile"; do
|
||
[ -f "$file" ] || continue
|
||
line="$(grep -m1 -E "^[[:space:]]*(export[[:space:]]+)?${name}=" "$file" 2>/dev/null)" || true
|
||
[ -n "$line" ] || continue
|
||
eval "$(printf '%s' "$line" | sed -E 's/^[[:space:]]*export[[:space:]]+//')" 2>/dev/null || continue
|
||
export "$name"
|
||
eval "current=\${$name:-}"
|
||
[ -n "$current" ] && return 0
|
||
done
|
||
return 0
|
||
}
|
||
|
||
load_env_var GITEA_HOST
|
||
load_env_var GITEA_TOKEN
|
||
load_env_var GITEA_REPOS
|
||
|
||
HOST="${GITEA_HOST:-}"
|
||
TOKEN="${GITEA_TOKEN:-}"
|
||
REPOS="${GITEA_REPOS:-}"
|
||
|
||
# 設定不全就安靜結束:晨間檢查不該因為沒設定而每天產生雜訊
|
||
[ -n "$HOST" ] && [ -n "$TOKEN" ] && [ -n "$REPOS" ] || exit 0
|
||
command -v curl >/dev/null 2>&1 || exit 0
|
||
command -v node >/dev/null 2>&1 || exit 0
|
||
|
||
lines=""
|
||
|
||
IFS=','
|
||
for repo in $REPOS; do
|
||
repo="$(printf '%s' "$repo" | tr -d '[:space:]')"
|
||
[ -n "$repo" ] || continue
|
||
|
||
body="$(curl -sS --max-time 15 \
|
||
-H "Authorization: token ${TOKEN}" \
|
||
"${HOST}/api/v1/repos/${repo}/pulls?state=open&limit=20" 2>/dev/null)" || continue
|
||
[ -n "$body" ] || continue
|
||
|
||
summary="$(printf '%s' "$body" | node -e '
|
||
let raw = "";
|
||
process.stdin.setEncoding("utf8");
|
||
process.stdin.on("data", (chunk) => { raw += chunk; });
|
||
process.stdin.on("end", () => {
|
||
let data;
|
||
try { data = JSON.parse(raw); } catch { return; }
|
||
if (!Array.isArray(data) || !data.length) return;
|
||
const repo = process.argv[1];
|
||
for (const pr of data) {
|
||
// mergeable 為 false 通常代表有衝突或未過檢查,值得在早上提醒
|
||
const blocked = pr.mergeable === false ? ",有衝突或未過檢查" : "";
|
||
console.log(`${repo} PR #${pr.number}:${pr.title}(${pr.head?.ref ?? "?"} → ${pr.base?.ref ?? "?"}${blocked})`);
|
||
}
|
||
});
|
||
' "$repo" 2>/dev/null)"
|
||
|
||
[ -n "$summary" ] && lines="${lines}${summary}
|
||
"
|
||
done
|
||
unset IFS
|
||
|
||
# 有開啟中的 PR 才輸出;全部合併完畢就靜默
|
||
if [ -n "$lines" ]; then
|
||
printf '尚未合併的 PR:\n%s' "$lines"
|
||
fi
|