iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0

! 本篇文章將會介紹 全管線整合:19:00 生成 → 20:00 發文,期望大家都能看懂兩個排程 job 怎麼靠狀態檔接成一條龍 :D

W4 開張,先還昨天盤點出的第一筆欠帳。D3 誕生的 opencode run 生成器和 D19、D20 組裝的發文腳本,出生日相差十六天,過去三週它們的「合作」其實只是剛好被同一張班表排在一起:一個 19:00 上班、一個 20:00 接手,中間靠檔案系統心電感應。文章倒是天天準時上架(到昨天為止 20 篇全數自動發出),但默契不等於合約——今天把交接明文化:那一小時的緩衝到底在緩衝什麼、兩個 job 之間傳遞了哪些狀態檔、每個環節掛掉時的失敗半徑有多大。而且這篇文章本身就是最好的驗證樣本:你正在讀的這一段,就是那條管線今天 19:00 的產物,一小時後它會被同一套流程送出去。

本篇目標

讀完這篇你會學到:

  • 三班制(19:00 生成 → 20:00 發文 → 20:15 補發檢查)的時間預算設計:緩衝一小時的三個用途
  • generate 與 publish 之間的四份交接憑證:文章檔、內文暫存、發佈 flag、帳本,各自由誰寫、誰讀
  • 「以檔案系統為資料庫」的兩段式管線思維,以及每個環節的失敗半徑分析

環境準備

  • 一台 macOS,加上 W2 裝好的三個 LaunchAgent:com.benben.ironman.generate / publish / republish
  • scripts/generate.sh(W1)與 scripts/publish.sh(W3),都在 /Users/benben/ai/automations/ironman/scripts/
# 進到系列 repo
cd /Users/benben/ai/automations/ironman

# 確認排程三兄弟都在線
launchctl list | grep ironman

# 三班制的時刻表(改了要重跑 scripts/install-launchd.sh 重新產生 plist)
grep -E "GENERATE_TIME|PUBLISH_TIME|REPUBLISH_TIME" config.sh
# GENERATE_TIME="19:00"
# PUBLISH_TIME="20:00"
# REPUBLISH_TIME="20:15"

主要內容

步驟一:一小時緩衝,是整條管線的心臟

先攤開一天的時間軸:

# 19:00  ── generate.sh:opencode run 生成 + 品質判準 + 重試
#          └→ 成功後 Discord 回報「d22 已生成,20:00 自動發佈」
# 20:00  ── publish.sh:找文章 → 掃描 → agent-browser 填表發佈
# 20:15  ── republish job:重跑 publish.sh,flag 在就跳過

19:00 和 20:00 之間這一小時不是隨便抓的,它同時扛三件事:

  • 重試預算:實測單次生成 4–7 分鐘(W3 平均 5 分 20 秒),最壞情況 5 次重試、每次 backoff 30 秒,約 25 分鐘收工——再怎麼倒霉都在 19:30 前有結論。plist 裡再加一道 ExitTimeOut 1800 兜底,就算 agent 鬧起來,launchd 半小時後強制收工,絕不賴到發文班次之後。
  • 人工攔截窗口:Discord 收到「已生成」通知後,距離發文還有約 55 分鐘。看到文章有問題,你有整整一個小時可以改稿或喊卡,這是全自動管線裡唯一的人類煞車踏板。
  • 尖峰吸收:20:00 是全台鐵人賽作者的發文尖峰,d20 光開發文頁就磨了 1 分 47 秒。緩衝把這種延遲留在 20:00 之後,不會回頭擠壓生成端。

步驟二:四份交接憑證——狀態檔就是管線的 API

兩個 job 不共用任何變數,交接全靠 state/ 與 articles/ 裡的檔案。一天一枚,攤開來看:

# 以昨天 d21 為例,管線的四份憑證:
articles/2026-10-01-d21.md       # ① 成品:generate 寫,publish 讀
state/body-d21.md                # ② 內文暫存:publish 剝掉 frontmatter 的進料
state/published-20261001.flag    # ③ 冪等憑證:0 byte,touch 即「已發佈」
state/published.log              # ④ 帳本:日期|天數|URL,一行一發

①是兩個 job 之間唯一的正式合約:frontmatter 的 title 必須是「DD+大綱標題」——generate.sh 的品質判準出廠前逐字比對 outline,publish.sh 用 awk 照同樣格式解析。上游怎麼寫、下游就怎麼讀,介面一天都沒變過,這是 20 天零斷更的底層原因。②是暫存不是存檔:publish 每次都從①重新 awk 出內文(昨晚是 9,320 bytes),餵給 CodeMirror 的 setValue 填進編輯器。③只有 0 byte,卻是 20:15 補發 job 的唯一判準;④則是帳本,30 天成績單的原始數據全在裡面。

小小小測驗:你知道 20:15 的「補發保險」和 20:00 的正式班次,跑的是同一支 publish.sh 嗎?
答案:是。republish 的 plist 裡沒有第二套邏輯,就是再執行一次同一個腳本,全靠一枚 0 byte 的 flag 分辨「發過了」與「還沒發」。同一支腳本+冪等設計,保險本身就不需要維護第二份程式。

步驟三:d21 實錄——一條龍的 66 分鐘

設計講完了,用昨天的 log 走一遍完整流程,時間戳全部真實:

# generate-20261001.log 節錄:生成 5 分 36 秒
[2026-10-01 19:00:05] 開始生成 d21:第三週回顧:Agent 已經會自己發文了
[2026-10-01 19:05:41] 成功:/Users/benben/ai/automations/ironman/articles/2026-10-01-d21.md(1838 個中文字)

# publish-20261001.log 節錄:從起床到上架 21 秒
[2026-10-01 20:00:05] d21 發文來源:/Users/benben/ai/automations/ironman/articles/2026-10-01-d21.md
[2026-10-01 20:00:13] 鐵人草稿確認(article_type=ironman)
[2026-10-01 20:00:20] 點擊發佈(第 1/3 次)
[2026-10-01 20:00:26] 發佈成功:https://ithelp.ithome.com.tw/articles/10419752
[2026-10-01 20:15:05] 今日已發佈過(flag 存在),跳過

生成 5 分 36 秒、發佈 21 秒、補發 0 秒(跳過也是一種成功)。這條管線還藏著一道雙保險:同一篇內文會被 secret_scan 掃兩次——19:00 出廠檢驗,命中就丟進 state/quarantine-* 隔離重試;20:00 送出前再攔一次,命中直接 die 絕不出手。state/ 裡 d04、d09 共六個隔離檔,時間戳全落在 19:04–19:25 的生成時段,就是第一道保險的戰利品。

步驟四:失敗半徑——每個環節掛掉會怎樣

管線整合的最後一塊拼圖是誠實盤點:每個環節壞掉,傷害範圍到哪裡?

  • 19:00 生成全滅:重試 5 次 → 備稿頂上 → Discord 告警(備援細節是明天的主角)
  • 20:00 找不到文章:die 收工 + error 告警,寧可不發也不發錯
  • 20:00 發佈失敗:flag 不存在 → 20:15 補發自動再來一次。d17 斷網那天完整演過一次:20:00 FATAL、20:13 人類重跑成功補上 flag、20:15:05 補發 job 看到 flag 安靜退場——人只快了 72 秒
  • 管線重複觸發:flag 在,全部跳過

至於為什麼用檔案系統當資料庫、不裝個 queue 或 DB?三個理由:ls 就能觀察、改檔就能干預、零依賴不會自己壞。代價是人工介入後要記得補狀態——這是踩坑記錄第一條的血淚。

常見問題 / 踩坑記錄

  • Q:我自己在 iThelp 網頁上手動發佈了文章,20:15 的補發 job 會不會出事?
    A:會,而且是經典的重複發文事故。flag 只認 publish.sh 的 touch——你在網頁上按發佈,系統完全不知情,20:15 一看 flag 不在,就照流程再發一篇一模一樣的。正確姿勢二選一:手動重跑 ./scripts/publish.sh(成功後它會自己補 flag),或自己補一枚 touch state/published-$(date +%Y%m%d).flag。人工搶救完,記得補上班卡。
  • Q:想微調內文,直接改 state/body-d22.md 可以嗎?
    A:不行,改了也白改。body 檔是 publish 每次從 articles/ 來源 awk 剝出來的暫存,20:00 一到就被覆寫。單一事實來源永遠是 articles/*.md——狀態檔設計第一課:分清楚「事實」與「加工品」。
  • Q:Mac 睡著錯過 19:00,生成還會跑嗎?
    A:會,但時間不可控。launchd 和 cron 在這題答案不同:cron 錯過直接跳過,launchd 會在喚醒後把錯過的 job 補跑一次(Apple 文件明載的差異)。保命歸保命,若補跑拖到 20:00 之後,發文班次就會撲空。所以這台機器的設定是永不睡眠,寧可職員天天準時加班,不可班表錯亂。

小結

  • 一小時緩衝同時扛三件事:重試預算(最壞 25 分鐘)、人工攔截窗口(55 分鐘)、發文尖峰吸收;ExitTimeOut 1800 兜底保證不溢出到下一班
  • 四份憑證各司其職:①文章檔是合約、②body 是暫存、③flag 是冪等憑證、④published.log 是帳本
  • 補發保險的省錢之道:同一支 publish.sh 跑兩次,靠 flag 分流,零第二套邏輯
  • 機敏掃描雙保險:19:00 隔離重試、20:00 攔截棄發,六個 quarantine 檔案是第一道防線的實績

明日預告

下一篇我們要介紹「備援設計:斷更即淘汰的保命機制」,把今天的失敗半徑分析升級成重試鏈、通用備稿與告警升級的完備備援——目前系統只有單層備稿,明天把它補齊成真正的保命機制,敬請期待!

參考資料:

有任何疑問但沒有 iT 邦幫忙帳號,或是想匿名提問?
歡迎到 https://dev.benben.me/q/Z5442T 提問或加油打氣,沒意外的話會在完賽之後一起回答 :D


上一篇
21 第三週回顧:Agent 已經會自己發文了
系列文
自我耍廢組:全自動化の鐵人 共 22 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言