#!/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