What: 異常目錄頁的版面從 markdown 表格改成一筆異常一個 H2 區塊,標題就是那一筆的異常頁頁名,時間、頁名、存取庫名稱、觸發 hook、退出碼、摘要六個欄位改成標題底下的一層條列,頁上不再留任何表格。失敗回報腳本不再自己讀回舊頁、附加新列、整頁寫回,改成只組出自己那一個區塊,交給 jsc-gitea 的目錄頁工具做讀回、比對與整頁寫回。範本、技能敘述、行為清單與說明文件一併對齊。 Why: 目錄頁有十幾份,各自在自己的腳本裡寫一套「讀得回舊內容才寫」的判斷,錯一次就少一筆紀錄,而且每一頁長出來的樣子都不一樣。版面與寫入語意收回一份正本之後,改一次全部跟著改。表格欄位遇到換行或半形豎線還會被切斷,條列沒有這個問題。 How: 失敗回報腳本移除目錄專用存取庫的解析與整頁組裝,改為組出區塊檔之後呼叫共用工具的 upsert,並依它的結束碼分流:3 是目錄頁的存取庫沒設定,找不到那支腳本也走同一條,兩者都只寫異常頁、跳過目錄頁、仍回 0;其餘非零一律以 4 回報。摘要不再替換半形豎線,改成把換行併成一行。傳進去的欄位序號 2 指舊表格版持有內容頁連結的那一欄,只供舊頁自動轉條列時取標題用。 Who: 使用 jsc-hooks 失敗回報流程的操作者,以及所有讀異常目錄頁追問題的人。
240 lines
13 KiB
Bash
Executable File
240 lines
13 KiB
Bash
Executable File
#!/usr/bin/env sh
|
||
# report-error.sh — 失敗回報流程:把一筆 hook 或工具異常寫成 wiki 的 ERROR_{HASH},
|
||
# 並在異常目錄頁附上一個索引區塊。頁面內容套用 templates/ 的兩份範本,
|
||
# 範本是文案的唯一來源,本腳本只填欄位;真正寫進 wiki 前,還會先走
|
||
# Gitea 寫入確認。
|
||
# 目錄頁的讀回、比對與整頁寫回一律交給 jsc-gitea 的 tools/wiki-contents.sh,本腳本只組出
|
||
# 自己那一個區塊。目錄頁版面只留一份正本,十幾個目錄頁才不會各長一種樣子。
|
||
#
|
||
# 用法:
|
||
# report-error.sh --hook {名稱} --exit {碼} --summary {摘要}
|
||
# [--repo {owner}/{repo}] [--cli {名稱}] [--session {id}]
|
||
# [--source {stdin|env|command}] [--symptom {現象}]
|
||
# [--cause {可能原因}] [--action {處理結果}]
|
||
# 相關輸出(stdout/stderr 摘要)由標準輸入讀入,可省略。
|
||
#
|
||
# 輸出:
|
||
# 成功印出「{頁名} {網址}」一行。網址在異常頁寫成功之後才取,頁名的 hash 帶時間戳,
|
||
# 每次都是全新的頁,寫之前查一定是 404。取不到網址時只印頁名,原因走 stderr,仍然 exit 0。
|
||
# 下列四種情形安靜降級:不寫任何頁、不輸出任何內容、exit 0。回報失敗不該再變成一次失敗。
|
||
# 1. 找不到 gitea.sh
|
||
# 2. 解析不出異常頁的 wiki 存取庫(JSC_WIKI_REPO_ERROR 與 JSC_WIKI_REPO 都沒設)
|
||
# 3. 算不出 HASH(hash-id 失敗或回空字串)
|
||
# 4. 建不出暫存檔(mktemp 失敗)
|
||
# 異常頁與目錄頁分屬兩個存取庫,各解各的:解不出異常頁的存取庫就整支降級;解得出
|
||
# 異常頁、只解不出目錄頁的存取庫(wiki-contents.sh 回 3),就只寫異常頁、跳過目錄頁
|
||
# 更新,印出頁名,仍然 exit 0。找不到 wiki-contents.sh 也走同一條降級路。
|
||
# 一份寫得成的異常紀錄,不該因為目錄頁沒地方放就整份丟掉。
|
||
# 寫入 wiki 失敗才以 exit 4 回報,訊息走 stderr。
|
||
#
|
||
# 連結:
|
||
# 目錄頁那個區塊裡的「頁名」那一條指向異常頁,一律寫成 [{文字}]({連結}),網址取
|
||
# jsc-gitea 的 gitea.sh wiki-url,不自己組路徑。
|
||
# 寫入前先把那個網址交給 jsc-gitea 的 link-check.sh,結束碼 0 才把連結寫進那一條。
|
||
# 驗不過就只留純文字頁名:那一條照寫、異常頁照寫、結束碼照舊。這一段一律不改結束碼,
|
||
# 本腳本是失敗回報路徑,回報失敗不該再變成一次失敗。
|
||
#
|
||
# 結束碼: 0=已寫入異常頁並印出頁名(取得網址就一併印出),或以上列四種安靜降級原因之一
|
||
# 結束、沒有寫出任何頁也沒有任何輸出——回報失敗不該再變成一次失敗
|
||
# 2=用法錯誤(缺 --hook 或 --summary)
|
||
# 4=寫入 wiki 失敗(異常頁與索引目錄頁,任一支寫不進去就算),或目錄頁的舊內容
|
||
# 讀不回來(wiki-contents.sh 回 7 金鑰失效、8 其他 API 失敗)而放棄寫入;
|
||
# 訊息走 stderr。讀不回來就不寫,是為了不拿範本蓋掉一份還在的目錄頁
|
||
# 註: 本檔以 `. "$ROOT/hooks/lib.sh"` 載入共用函式,沒有接 `|| true`。lib.sh 讀不到時 sh 會
|
||
# 就地結束並回 2,跟用法錯誤同碼;分不出是哪一種時,先確認 hooks/lib.sh 在不在。
|
||
#
|
||
# 頁名:
|
||
# ERROR_{HASH},HASH 取「{owner}/{repo} {hook} {時間}」的 SHA-1 完整 40 碼大寫十六進位
|
||
# (共用 hash 規則)。三段以空白相連當 hash 輸入。
|
||
# 時間放進 hash:同一種失敗再發生時要另開新頁,不覆寫舊紀錄。
|
||
# 索引目錄頁的頁名固定,不帶 HASH。
|
||
#
|
||
# 誰來呼叫:
|
||
# 由操作者手動執行,或由技能步驟執行(`jsc-hooks:hooks-install` 在 wire-cli.sh 回報
|
||
# status=failed 時呼叫)。**不接在失敗的 hook 上自動觸發**:hook 一律安靜 exit 0,
|
||
# 而且自我回報要走網路寫 wiki,失敗的 hook 再去回報自己會疊出迴圈。
|
||
set -u
|
||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||
ROOT=$(cd "$HERE/.." && pwd)
|
||
. "$ROOT/hooks/lib.sh"
|
||
STDIN_JSON="" # 本腳本的標準輸入是錯誤輸出摘要,不是 JSON
|
||
|
||
hook=""; code=""; summary=""; repo=""; cli=""; session=""
|
||
source_kind=""; symptom=""; cause=""; action=""
|
||
while [ $# -gt 0 ]; do
|
||
case "$1" in
|
||
--hook) hook="${2:-}"; shift 2 ;;
|
||
--exit) code="${2:-}"; shift 2 ;;
|
||
--summary) summary="${2:-}"; shift 2 ;;
|
||
--repo) repo="${2:-}"; shift 2 ;;
|
||
--cli) cli="${2:-}"; shift 2 ;;
|
||
--session) session="${2:-}"; shift 2 ;;
|
||
--source) source_kind="${2:-}"; shift 2 ;;
|
||
--symptom) symptom="${2:-}"; shift 2 ;;
|
||
--cause) cause="${2:-}"; shift 2 ;;
|
||
--action) action="${2:-}"; shift 2 ;;
|
||
*) shift ;;
|
||
esac
|
||
done
|
||
if [ -z "$hook" ] || [ -z "$summary" ]; then
|
||
echo "用法:report-error.sh --hook {名稱} --exit {碼} --summary {摘要} [...]" >&2
|
||
exit 2
|
||
fi
|
||
|
||
gsh=$(jsc_gitea_sh) || exit 0
|
||
# 異常頁與索引目錄頁落在兩個不同的存取庫,各解各的:異常頁走自己的型別,目錄頁走目錄專用
|
||
# 型別。異常頁的存取庫解不出來就整支降級,連異常都沒地方寫,做下去也沒意義。
|
||
wrepo=$(sh "$gsh" wiki-repo ERROR 2>/dev/null) || exit 0
|
||
[ -n "$wrepo" ] || exit 0
|
||
# 目錄頁的存取庫不在這裡解:讀回、比對、整頁寫回都由 wiki-contents.sh 做,它自己解目錄專用
|
||
# 存取庫,這邊再解一次就會有兩份規則。解不出來時它回 3,本腳本照原本的語意降級。
|
||
|
||
# 存取庫名稱未指定就取工作目錄的 origin(只用來標記異常屬於哪個存取庫)
|
||
if [ -z "$repo" ]; then
|
||
origin=$(git config --get remote.origin.url 2>/dev/null || true)
|
||
# 尾綴的 .git 在這裡剝掉:hash 工具刻意不做輸入正規化,同一份輸入要算出同一個 HASH,
|
||
# 正規化就是呼叫端的責任。帶不帶 .git 會算出兩個不同的頁,同一個存取庫就分裂成兩份紀錄。
|
||
repo=$(printf '%s' "$origin" \
|
||
| sed -n 's#.*[/:]\([^/]*\)/\([^/]*\)$#\1/\2#p' | sed 's/\.git$//')
|
||
fi
|
||
[ -n "$repo" ] || repo="-"
|
||
[ -n "$cli" ] || cli=$(cli_name)
|
||
[ -n "$session" ] || session=$(session_id)
|
||
[ -n "$code" ] || code="-"
|
||
[ -n "$source_kind" ] || source_kind="command"
|
||
[ -n "$symptom" ] || symptom="$summary"
|
||
[ -n "$cause" ] || cause="待查"
|
||
[ -n "$action" ] || action="待處理"
|
||
|
||
ts=$(date +'%Y-%m-%d %H:%M:%S')
|
||
ticket_ts=$(date +'%Y%m%d_%H%M%S')
|
||
if [ -t 0 ]; then detail=""; else detail=$(cat 2>/dev/null | tr '\n' ' ' | cut -c1-500); fi
|
||
[ -n "$detail" ] || detail="(無)"
|
||
# 相關輸出落在異常頁的表格欄位裡,半形 | 會把欄位切斷,改成全形
|
||
detail=$(printf '%s' "$detail" | sed 's/|/|/g')
|
||
# 摘要在目錄頁是一整條條列,換行會把一條拆成兩行,先併成一行
|
||
summary=$(printf '%s' "$summary" | tr '\n' ' ')
|
||
|
||
hash=$(sh "$gsh" hash-id "$repo $hook $ts" 2>/dev/null) || exit 0
|
||
[ -n "$hash" ] || exit 0
|
||
page="ERROR_$hash"
|
||
|
||
# sed 取代值要先轉義:& 與分隔字元 | 會被 sed 當語法,換行會整行斷掉
|
||
esc() { printf '%s' "$1" | tr '\n' ' ' | sed 's/[\\&|]/\\&/g'; }
|
||
fill() { sed "s|$1|$(esc "$2")|g"; }
|
||
|
||
tmp_page=$(mktemp) || exit 0
|
||
tmp_entry=$(mktemp) || { rm -f "$tmp_page"; exit 0; }
|
||
trap 'rm -f "$tmp_page" "$tmp_entry"' EXIT
|
||
|
||
fill '{HASH}' "$hash" < "$ROOT/templates/error-page.md" \
|
||
| fill '{yyyy-MM-dd HH:mm:ss}' "$ts" \
|
||
| fill '{owner}/{repo}' "$repo" \
|
||
| fill '{cli}' "$cli" \
|
||
| fill '{session_id}' "$session" \
|
||
| fill '{hook_name}' "$hook" \
|
||
| fill '{exit_code}' "$code" \
|
||
| fill '{error_summary}' "$summary" \
|
||
| fill '{現象描述}' "$symptom" \
|
||
| fill '{可能原因}' "$cause" \
|
||
| fill '{處理方式}' "$action" \
|
||
| fill '{stdin / env / command}' "$source_kind" \
|
||
| fill '{stdout / stderr 摘要}' "$detail" \
|
||
| fill '{yyyyMMdd}_{HHmmss}' "$ticket_ts" > "$tmp_page"
|
||
|
||
# 異常頁先寫,網址後取。頁名的 hash 帶時間戳,每次回報都是一個全新的頁,寫進去之前查網址
|
||
# 一定是 404,拿到的必然是空字串;目錄頁那一條會變成沒有連結的死字,stdout 也少一半。
|
||
if ! sh "$gsh" wiki-put "$wrepo" "$page" "$tmp_page" >/dev/null 2>&1; then
|
||
echo "[jsc] 寫入 $page 失敗($wrepo)。" >&2
|
||
exit 4
|
||
fi
|
||
|
||
# 目錄頁那個區塊的「頁名」那一條指向異常頁,連結一律寫成 [{文字}]({連結}),網址取
|
||
# gitea.sh wiki-url。
|
||
# wiki 自己那種雙中括號寫法只在同一個 wiki 裡解得開,寫錯不會報錯,畫面上看起來像正常
|
||
# 文字或死連結,巡不到也修不了。
|
||
# 網址取不到不算失敗:異常頁已經寫成功了,只是這一條少一個連結。這裡把原因記下來走 stderr,
|
||
# 結束碼照舊——安靜降級仍是 exit 0,回報失敗不該再變成一次失敗。
|
||
url=$(sh "$gsh" wiki-url "$wrepo" "$page" 2>/dev/null)
|
||
url_code=$?
|
||
url_note=''
|
||
if [ "$url_code" -ne 0 ]; then
|
||
url=''
|
||
case "$url_code" in
|
||
4) url_note='頁面查不到(wiki-url 回 4),寫入後尚未生效' ;;
|
||
5) url_note='回應裡沒有 html_url(wiki-url 回 5)' ;;
|
||
7) url_note='金鑰失效或權限不足(wiki-url 回 7)' ;;
|
||
*) url_note="wiki-url 結束碼 $url_code" ;;
|
||
esac
|
||
echo "[jsc] 取不到 $page 的網址:$url_note。目錄頁那一條與輸出只留頁名。" >&2
|
||
fi
|
||
|
||
emit() { # 異常頁已經寫成功,頁名一定要印;網址取不到就只印頁名,不印一個空欄位
|
||
if [ -n "$url" ]; then printf '%s %s\n' "$page" "$url"; else printf '%s\n' "$page"; fi
|
||
}
|
||
|
||
# 連結先驗證連得到,才寫進目錄頁那一條。沒驗過的連結寫進去,異常頁一樣會在目錄頁長出
|
||
# 死連結,而目錄頁是別人查問題的入口。驗不過就只留純文字頁名,那一條照寫。
|
||
link_text="$page"
|
||
if [ -n "$url" ]; then
|
||
# link-check.sh 與 gitea.sh 同一個 tools 目錄,路徑直接由已經解出來的那一支推得,
|
||
# 不另寫一套搜尋,兩邊才不會一支解到開發版面、一支解到安裝版面。
|
||
lcs="$(dirname "$gsh")/link-check.sh"
|
||
if [ ! -f "$lcs" ]; then
|
||
echo "[jsc] 找不到 $lcs,這一條的連結沒驗過,只留頁名;$page 已建立。" >&2
|
||
else
|
||
sh "$lcs" "$url" >/dev/null 2>&1
|
||
lc_code=$?
|
||
case "$lc_code" in
|
||
0) link_text=$(printf '[%s](%s)' "$page" "$url") ;;
|
||
# 7 是金鑰失效,不是死連結。金鑰過期時私有存取庫的回應與「頁不存在」分不出來,
|
||
# 把它當成死連結就會連還在的頁一起判死。
|
||
7) echo "[jsc] 連結驗證遇上金鑰失效(link-check.sh 回 7),不判成死連結,這一條只留頁名;$page 已建立。" >&2 ;;
|
||
3) echo "[jsc] GITEA_HOST 未設定(link-check.sh 回 3),連結沒驗過,這一條只留頁名;$page 已建立。" >&2 ;;
|
||
*) echo "[jsc] 連結驗不過(link-check.sh 結束碼 $lc_code),這一條只留頁名;$page 已建立。" >&2 ;;
|
||
esac
|
||
fi
|
||
fi
|
||
|
||
# 目錄頁上這一筆是一個 H2 區塊:標題就是異常頁頁名,欄位一行一條,順序與範本的示範區塊
|
||
# 一致。標題不放連結也不放網址——頁名只由存取庫名稱、hook 與時間決定,換主機或改存取庫
|
||
# 都動不到它,比對鍵才找得到既有那一筆。
|
||
{
|
||
printf '## %s\n\n' "$page"
|
||
printf -- '- 時間:%s\n' "$ts"
|
||
printf -- '- 頁名:%s\n' "$link_text"
|
||
printf -- '- 存取庫名稱:%s\n' "$repo"
|
||
printf -- '- 觸發 hook:%s\n' "$hook"
|
||
printf -- '- 退出碼:%s\n' "$code"
|
||
printf -- '- 摘要:%s\n' "$summary"
|
||
} > "$tmp_entry"
|
||
|
||
# 目錄頁交給 wiki-contents.sh:它負責解目錄專用存取庫、讀回舊頁、比對 H2 標題找既有區塊、
|
||
# 附加或整塊換掉,再整頁寫回,還會把舊的表格頁轉成條列。頁上每一個區塊都是別人回報的異常,
|
||
# 那套「讀得回舊內容才寫」的判斷只留一份正本,才不會每個目錄頁各寫一套、錯一次少一筆紀錄。
|
||
# 傳進去的那個 2 是 key-col:舊表格版目錄頁裡持有身分的欄位序號。舊版第 2 欄是頁名,
|
||
# 舊頁自動轉條列時要靠它取 H2 標題;頁面已經是條列格式時這個參數用不到。
|
||
# 路徑由已經解出來的 gitea.sh 推得,與 link-check.sh 同一套做法,三支同一個 tools 目錄。
|
||
wcs="$(dirname "$gsh")/wiki-contents.sh"
|
||
if [ ! -f "$wcs" ]; then
|
||
echo "[jsc] 找不到 $wcs,只寫異常頁,跳過目錄;$page 已建立($wrepo)。" >&2
|
||
emit
|
||
exit 0
|
||
fi
|
||
|
||
sh "$wcs" upsert ERROR 2 "$page" "$tmp_entry" "$ROOT/templates/error-contents.md" >/dev/null
|
||
wc_code=$?
|
||
case "$wc_code" in
|
||
0) ;;
|
||
# 3 是目錄頁的存取庫沒設定。異常頁已經寫成功,一份寫得成的異常紀錄不該因為索引沒地方放
|
||
# 就整份丟掉,所以只跳過目錄頁,結束碼照舊回 0。
|
||
3) echo "[jsc] 目錄頁的 wiki 存取庫解不出來(wiki-contents.sh 回 3),只寫異常頁,跳過目錄;$page 已建立($wrepo)。" >&2 ;;
|
||
# 其餘結束碼都代表這一筆沒進到目錄頁:7 金鑰失效、8 其他 API 失敗都是舊內容未知而中止,
|
||
# 1 是組不出頁面內容或寫入失敗,2 是用法錯誤,4 是頁面不存在又沒收到範本。
|
||
*)
|
||
echo "[jsc] 目錄頁寫入失敗(wiki-contents.sh 結束碼 $wc_code),$page 已建立。" >&2
|
||
exit 4 ;;
|
||
esac
|
||
|
||
emit
|