Guidant AI 線上更新機制

FR-167 線上更新 · 給 PM 的機制總覽簡報(2026-10-31)· 共 10 張,往下捲動即可;原始檔在 briefing/pm-deck-2026-10-31/project/(claude.ai Slides 格式)

  1. 這次做了什麼、整體架構
  2. 出版流程與簽章加密機制
  3. 客戶主機升級流程、狀態判斷、資料保護
  4. 整體服務架構

GUIDANT AI · 落地版

線上更新機制

出版流程、簽章與加密、升級流程與狀態判斷、服務架構

FR-167 · 2026-10-31 · 技術說明

講者備註

開場一句話:客戶主機從此不用搬檔、不用敲指令;每一版由原廠蓋章,主機不信任任何中間人,升級在資料庫複本上做、失敗自動退回。

這次做了什麼:客戶主機自己升級,原廠只蓋章

以前

  • 工程師把 5 GB 安裝包搬到客戶主機
  • 登入主機敲指令、人工看 log
  • 升級失敗要人工救、停機時間不確定
  • 沒有簽章,誰給的包都裝

現在

  • 主系統每 6 小時自動問更新站有沒有新版
  • 管理員在畫面按「更新」,可立即或排程
  • 每一版由決策者本人離線蓋章,主機驗不過就拒裝
  • 升級在資料庫複本上做,失敗自動退回,現用資料不動

三個原則:任何版本都不自動裝 · 只維護最新版 · 授權管「能用什麼」不管「能不能裝」

決策定案 D1~D31,見 design.md §3

講者備註

重點:自動化的是「發現與下載」,「裝」永遠要人按。老闆若問為什麼不全自動:因為停機時間是客戶的,不是我們的。

整體架構:四個角色,私鑰只在一個人手上

① 原廠 CI(GitLab)

出包、登記草稿

  • Nuitka 編譯、SBOM、弱點掃描
  • 算整包 SHA-256
  • 傳 R2、向更新站登記草稿
  • 沒有私鑰

② 決策者電腦

蓋章、發布

  • Ed25519 私鑰,passphrase 加密
  • 一次 publish 打一次密碼
  • 主力章+備用章離線封存
  • 唯一有私鑰的地方

③ 更新站(Cloudflare)

轉交清單、發下載網址

  • Worker+D1+R2
  • 只存公鑰與蓋好章的清單
  • 後台受 Access 保護
  • 沒有私鑰

④ 客戶主機

驗章、升級、退回

  • 主系統(容器)只寫更新單
  • 主機 systemd 跑升級程式
  • 公鑰裝在 /etc/guidant/release-keys
  • 驗證更新包有沒有被竄改

更新站被入侵的最壞結果:客戶下不到新版。因為它沒有私鑰,改不了任何一份清單,主機驗章就會擋下。

更新站網址 guidantai-update.jedicotech.com · 離線客戶由原廠交付單一離線檔,走同一套驗證

講者備註

老闆最可能問的一句:更新站被打下來會怎樣?答案在底下那行。

出版流程:推一個版號,到客戶看得到新版

① 工程師

推版號

git tag 一個新版

② CI 自動

編譯與掃描

編譯、產 SBOM、弱點掃描紅就停

③ CI 自動

打包上傳

算指紋、傳更新站、登記成草稿(客戶還看不到)

④ 決策者本人

蓋章放行

一條指令、打一次密碼

⑤ 客戶主機自動

看到新版

每 6 小時問一次,通知管理員

人只做兩個動作:推版號、打一次密碼。中間編譯、掃描、打包、上傳、登記全部自動。包先上傳只是存放,狀態是草稿,客戶看不到;決策者蓋章後才變成可下載。

第 ④ 步刻意不能自動:私鑰不放 CI,避免任何人推個版號、或 CI 被打,就能發一版客戶會裝的東西。

給客戶看的說明文字(更新了什麼、影響、建議)隨 release note 一起進更新站,發布後可改。

要撤回某版:後台一鍵下架,客戶端路徑自動繞過它

講者備註

打折處要誠實:測試期 1.27.x 是用測試章、腳本直寫更新站資料庫,正式路徑(CI 登記 → 蓋章放行)出貨前要完整走一次。說明文字目前還是人工填,要接進 CI。

簽章與加密:用在哪三個地方

原廠 CI

出包
算安裝包指紋

① 簽名

Ed25519

決策者電腦

打密碼解開私鑰
對清單蓋章

② 轉交

HTTPS

更新站

驗客戶授權
原樣轉交清單與包

③ 驗證

公鑰驗章+SHA-256

客戶主機

驗證更新包
有沒有被竄改

① 簽名(Ed25519)

私鑰只在決策者電腦,檔案用密碼加密(AES-256)。CI、更新站、客戶都沒有私鑰

② 轉交(HTTPS)

更新站只驗客戶授權是真的(授權中心簽的),清單與包一個字不改地交出去,下載網址 15 分鐘到期

③ 驗證(公鑰+SHA-256)

公鑰隨安裝包裝在主機;先核簽名是原廠的,再算整個檔案指紋對清單,不對就不裝

安裝包本身不加密:內容是公開發行的程式,要防的是「被改」與「冒充原廠」

講者備註

為何用 Ed25519 不用 RSA:金鑰短、驗章快、沒參數可選錯,openssl、Python、Node、瀏覽器都原生支援,與授權中心同一套。

客戶主機拿到更新後、開始升級前:先確認五件事

時機:管理員按「更新」之後、服務停下之前。目的只有一個,不要裝到假的、壞的、不該裝的東西。任何一道不過就停下,系統不動。

確認什麼擋住哪種壞事怎麼確認
是原廠簽的有人冒充原廠發一個假更新用主機內存的公鑰核對清單簽名
檔案沒被改下載途中被換包、或下載不完整整個檔案算指紋對清單(SHA-256)
版本只能往上拿舊版合法的包把系統降回有漏洞的版本包的版號要比現行大,且不低於清單寫的起始版
主版號相同1.x 的授權裝到 2.x,授權範圍不符包的主版號要等於現行主版號
主機裝得了裝到一半磁碟滿、資料庫連不上、docker 壞了預檢:空間、資料庫、docker、停機估算

為什麼要主機自己驗,不信更新站:更新站在外網,是整條鏈最可能被打的點。它被攻破最多只能「不給」,給不了假的。

離線客戶一樣:原廠交離線檔,管理員上傳,走同一套確認,驗不過即刪。

前兩項在下載完就先驗一次,開始升級時再驗一次;都是機器做,管理員不用操作

講者備註

老闆問「如果公鑰被換掉呢」:公鑰在主機 /etc/guidant/release-keys,root 才改得了;能改那裡的人本來就能對主機做任何事,這不是更新機制要防的範圍。

升級流程:八步,只有中間四步停服務

0

換升級程式

先把自己換成新版的

1

預檢

簽名、指紋、空間、資料庫

2

停接新工作

等進行中的做完

3 · 停服務

複製資料庫

現用那份不再動

4 · 停服務

在複本上升級

載映像、跑 migration

5 · 停服務

健康檢查

用複本起新版實打

6 · 停服務

換接

複本轉正、舊的留快照

7

清理

刪舊映像

第 0~2 步失敗

系統沒動過,只記「這次沒更新」,包留著可以重按

第 3~5 步失敗或逾時

自動退回:丟掉複本、用原本那份把舊版起回來,資料零損失

第 6 步寫完

新版已在服務,不再退回;中途斷電開機自動補做退回

每步有時限;停機只在第 3~6 步,實測 5~6 分鐘;畫面自動切到維護頁、升完自動跳回

講者備註

可以講一句:主系統容器沒有 docker 權限,只會往目錄寫一張更新單;真正動 docker 的是主機上的 systemd,這是刻意的權限切割。

畫面怎麼知道進度:兩個地方記狀態,誰做事誰寫

紀錄一:資料庫裡的更新紀錄

「這次更新走到哪」

下載中 → 已下載 → 已送出 → 升級中 → 結果(成功/失敗/已退回/這次沒更新/已取消)

由主系統後端寫:下載完改「已下載」,按更新改「已送出」並把單子交給主機

紀錄二:主機上的進度檔

「升級八步各跑到哪」

每一步:等待中/進行中/完成/逾時/失敗;整體:進行中/成功/已退回/退回失敗

由主機升級程式寫:每做完一步就改一次;主系統只能讀不能改

階段畫面資料從哪來多久問一次
按下更新 → 服務停下前更新中心頁問主系統後端,後端把兩份紀錄一起回3 秒
服務停下 → 換接完成維護頁(自動切換)直接讀主機進度檔,不經後端固定輪詢
換接完成自動跳回更新中心進度檔寫「成功」且後端回應正常才跳—

兩份紀錄的對齊:主系統每分鐘把進度檔的結果抄回更新紀錄;資料庫是歷史,進度檔是現在

講者備註

維護頁一個細節:第 5 步健康檢查會先用複本把新版起來,那時後端已經有回應但還沒換接,所以進度檔寫「進行中」期間維護頁不跳回,避免放人進半套系統。

資料不掉:升級在複本上做,現用資料庫從頭到尾沒動

複本

第 3 步把現用 pgdata 複製成 pgdata-next,migration 全跑在複本;原本那份只讀

健康檢查

第 5 步用複本起新版,實打版本、登入、3 支 API;不過就不換接

自動退回

第 3~5 步失敗或逾時:刪複本、用原本那份把舊版起回來,進度檔記 rolled_back

斷電

開機 boot-check:上次停在第 3 步之後沒收尾就退回;換接已完成就不動

換接之後:舊資料庫留作快照(pgdata-prev-舊版),下一次升級成功才清掉;退回也失敗時 guidantai recover 人工救,維護頁與更新中心都看得到。

實測紀錄(190 測試機):10/30 四次實測,含兩次刻意失敗退回成功、一次退回失敗用 recover 救回;10/31 連升五版全成功。

預檢會算磁碟:需要包大小+解開+資料庫複本,不夠不開始

講者備註

老闆若問「退回要多久」:退回就是把舊容器起回來,資料庫沒動過,實測 1 分內。

整體架構:三個區塊,客戶端一台裝完

客戶端(客戶機房,一台主機)

Guidant AI 主系統

前門與畫面

後端 API

背景工作

即時推播

資料庫 PostgreSQL

快取 Redis

檔案儲存(內建)

證據拆檔沙箱

錯誤追蹤站

升級程式(主機層)

全部跑在一台主機的 docker 上;資料只存在客戶機房;升級程式是主機層的指令,不在容器裡

每 6 小時

HTTPS 出站

原廠端(雲端,只為更新)

更新站

更新站程式(Cloudflare Worker)

版本與回報資料庫(D1)

安裝包儲存(R2)

沒有私鑰、沒有客戶資料;只存公鑰、蓋好章的清單、誰在哪一版

檢測 Agent(選配)

客戶內網另一台,跑弱點掃描與主機檢測;主系統升完可一鍵跟進升級

原廠其他系統

授權中心(簽授權檔,獨立系統)· GitLab CI(出包)· 決策者電腦(蓋章);三者都不與客戶主機直接連線

客戶防火牆只需放行兩個出站網域(更新站、安裝包下載),不需開任何入站;封閉網路可改離線上傳

講者備註

Cloudflare 免費方案就夠:更新站沒有運算負擔,只轉交清單與發下載網址,5 GB 下載直接走 R2。