FR-167 · 需求討論稿 · 2026-10-29(v8 待審)
有權限的管理員按下載、按更新,其餘全自動;每一步有時限,失敗、卡住、斷電都自己退回,資料一筆不掉,不必派人到現場
現在每出一版,客戶要自己把 2.9GB 的出貨包搬上主機、照手冊敲指令升級;中途出事,要人照著畫面印出的還原指令一行行打,打不出來就得派人過去。客戶要是在國外,這條路走不通。本案分兩層解:第一層是讓升級指令絕不出事——在資料庫複本上升級、每步有時限、失敗或超時或斷電都自動退回,開放使用之前現在那份資料庫從頭到尾沒動過,所以退回不需要還原、不會掉資料。第二層是把搬檔與敲指令拿掉——系統每天向我們的更新站問一次有沒有新版,有就通知管理員;管理員確認後系統才下載(跟 iPhone 一樣),下載驗證完,有「系統更新」權限的人按「立即更新」或選排程時間,其餘全自動;連不到外網的客戶自己上傳更新包。客戶主機上不多任何常駐程式、不開任何新的埠:主系統只會寫一張更新單,主機上的 systemd 看到更新單才呼叫裝機時就在的 guidantai upgrade;真要人出手,走客戶本來就有的主機登入。
人工升級失敗時,現在要人照畫面印出的指令手動還原
會掉主系統停機那段時間,Agent 送不出的檢測結果現在會被丟掉
5 步install.sh --upgrade 已有的升級步驟,本案沿用
650MBPOC 資料庫大小,複製一份只要幾十秒
決策者的話:「要能在 user 按下 accept 後自動化更新,要顯示進度,而且不可以卡住,就算卡住也要能復原」「不能因為卡死了就沒辦法復原,這樣還是我們人要去一次,要是客戶在俄羅斯怎辦」「要可以在更新的時候讓 user 反悔或是中斷」「資料絕對不可以掉」「救援頁感覺像後門」「有沒有現成的工具可以用,不要這樣搞」「沒有輸入指令這件事,就是讓有權限的人按」。
需求拆成兩層,價值差很多:
| 層 | 內容 | 價值 | 怎麼做到 | 詳見 |
|---|---|---|---|---|
| A 更新不會出事 | 複本升級、每步時限、失敗/超時/斷電自動退回、進度看得到、可重試、資料不掉 | 這是「不派人到俄羅斯」的全部 | 改現有的升級指令,幾百行 | §5 更新流程、§6 資料不掉、§7 卡死怎麼救 |
| B 不用搬檔、不用敲指令 | 系統自己發現新版、確認後自己下載;有權限的人按一下或選時間,到點自己跑 | 管理員不必登入主機 | 更新站+更新中心頁;「按一下就跑」靠更新單+systemd 觸發器(D26) | §11 更新站 |
A 是本案第一期,先做;B 第二、三期。
整案:升級指令改成絕不出事;我們這邊多一個更新站;畫面多一頁更新中心負責通知、下載、按更新。客戶主機上不多任何常駐程式、不開任何新的埠。
| 期 | 做什麼 | 產出 | 完成怎麼判定(決策者檢查法) |
|---|---|---|---|
| 一 升級變安全 | guidantai upgrade 改成:預檢 → 停接新工作、等做完 → 停服務 → 複製資料庫 → 在複本上升級 → 健康檢查 → 開放;每步有時限、寫進度檔;失敗或超時自動退回;斷電後開機自動退回;保留上一版映像;前門在停機期間顯示進度頁;必經版本由 manifest 自動算;CI 拿真實舊庫排練;Agent 送不出的結果先存著、恢復後補送 |
IT 現在就敲的那道指令變安全,不必等畫面 | 在測試機裝 1.23.1、灌 POC 資料庫備份,一道指令直升新版成功;放一支會失敗的 migration,自動退回,資料筆數與升級前一致;升級跑到一半直接重開機,開機後自動退回;停機期間打開原網址看得到進度 |
| 二 更新站 | 我們這邊:獨立更新站(與授權中心分開)、發布 CLI(決策者電腦離線簽名)、CI 出包後自動登記草稿 | 我們發一版,客戶查得到 | 跑一次 tag pipeline,更新站後台出現草稿;在自己電腦跑發布指令變「已發布」;用 DEV 的授權問一次,回應裡有新版 |
| 三 更新中心 | 客戶這邊:每日檢查、通知、管理員確認後才下載、驗簽名、離線上傳、預檢、就緒後「立即更新」或選排程時間、寫更新單、主機 systemd 觸發器看到更新單呼叫 guidantai upgrade、進度、結果、更新紀錄、診斷包下載;新增「系統更新」權限 |
管理員不必搬檔、不必登入主機 | DEV 收到「有新版」通知,按「下載」,看到就緒;按「立即更新」,更新中心與維護頁都看得到進度跑到完成;再試一次選排程,到點自己跑;沒有「系統更新」權限的帳號看不到更新鈕;拔網路改走離線上傳,一樣就緒 |
| 四 Agent 跟進 | 主系統升完後,Agent 先推一台、正常再推其餘;心跳帶目標版本 | Agent 不必逐台手動升 | 三台 Agent 的測試環境:主系統升完,先一台變新版,其餘兩台接著升;故意讓第一台失敗,其餘兩台不會被推 |
依賴:一與二可以同時做;三要等一、二;四要等三。決策者功課:更新站網域出貨前再定(開發期用 workers.dev 子網域);CRA 兩題先不管;在自己電腦產發布印章一正一備(見 D3)。
背景:客戶 A 跑 1.24.1,接了 3 台 Agent。我們剛發 1.27.0,可以從 1.22.0 以上直升。
| 時間 | 誰 | 發生什麼 |
|---|---|---|
| 週三 | 我們 | CI 出 1.27.0,在更新站登記草稿;決策者在自己電腦跑 guidant-release publish 1.27.0 簽名發布 |
| 週四 03:00 | 系統 | 每日檢查:向更新站出示本機授權,問「我 1.24.1,有新的嗎」。拿回簽好的版本資訊(幾 KB):有 1.27.0、可直升、2.9GB、更新說明。這時還沒下載 |
| 週四 09:00 | 管理員 | 登入看到「有新版本 1.27.0」,點進更新中心看更新說明、「服務會中斷約 10 到 20 分鐘」、預檢結果(空間夠、版本可直升) |
| 週四 09:05 | 管理員 | 按「下載」。系統記下是誰按的 |
| 週四 09:05–09:20 | 系統 | 這時才開始下載,服務照常。下載完驗簽名與雜湊,通過後更新中心顯示「已就緒」,並寄通知給管理員 |
| 週四 09:25 | 管理員 | 回更新中心,選「排程」週六 22:00,按「確認更新」。主系統在主機上寫一張更新單(版本、時間、誰按的) |
| 週六 21:30 | 系統 | 頁面上方提示所有人「30 分鐘後系統更新」 |
| 週六 22:00 | 主機 | systemd 觸發器到點呼叫 guidantai upgrade:預檢 → 停止接新工作,等跑到一半的證據分類做完 → 停服務 → 複製資料庫 → 在複本上套 migration → 啟動新版 → 健康檢查 |
| 週六 22:00–22:15 | 管理員 | 打開原網址看到維護頁:目前在第幾步、這步跑了多久、剩多少時限。這頁由前門直接顯示,主系統與資料庫停機時照樣看得到 |
| 週六 22:15 | 系統 | 健康檢查通過 → 開放使用 → 通知管理員「已更新到 1.27.0」 |
| 週六 22:20 | 系統 | 先讓 1 台 Agent 升級,正常後推給另外 2 台 |
| 週一 | 管理員 | 更新中心頁顯示:主系統 1.27.0 正常、Agent 3/3 正常、下載人王大明、按更新的人王大明(排程) |
變化情境:
| 情境 | 怎麼走 |
|---|---|
| 22:08 套 migration 卡住不動 | 超過時限沒有進度 → 自動退回 1.24.1 → 通知管理員「更新失敗,已退回,資料完整」,附原因與診斷包下載鈕。管理員把診斷包寄給我們,我們修好再發 |
| 22:05 機房斷電 | 來電開機 → 升級指令的開機檢查讀進度檔,發現上次沒做完 → 自動退回 1.24.1 |
| 22:03 管理員臨時反悔 | 服務還沒停(還在等工作做完):更新中心按「取消」,立刻恢復接新工作,什麼都沒動。服務已經停了:沒有取消鈕(D25),這段每步有時限,最多跑滿時限就自動有結果——升完,或退回 |
| 22:00 還有一件檢測派送在跑 | 等它做完,不強制停。等到 23:30 維護時段結束還沒做完,這次就不更新,恢復接新工作,通知管理員改約時間 |
| docker 本身當掉,自動退回沒跑 | 唯一要人登入主機的情況:客戶 IT 用 SSH 執行 sudo guidantai recover,效果等同自動退回。這是客戶本來就有的門 |
| 客戶 A 停在 1.20.0 | 比必經版本 1.22.0 舊 → 更新中心頁顯示「需先升 1.22.0,再升 1.27.0」。按一次,兩個包都下載;更新分兩段跑;第二段失敗會停在 1.22.0,那是一個穩定、可用的版本 |
| 週四下載到一半網路斷了 | 恢復後從斷點續傳;沒下載完就不會顯示「已就緒」,更新鈕不會出現,升級指令也會拒絕用不完整的包開始 |
| 客戶 B 的主機不能連外網 | 走離線更新:我們把 1.27.0 更新包交給客戶,管理員在更新中心「離線更新」上傳。從驗簽名開始,後面完全一樣;只是沒有自動通知 |
| 升完兩天才發現功能有問題 | 不退回(退回會丟掉這兩天的資料),我們發修正版 |
D22 先通知,管理員確認後才下載(跟 iPhone 一樣)
系統每天只問「有沒有新版」,拿回的是幾 KB 的版本資訊,不下載。管理員按「下載」後才開始下載 2.9GB;下載時服務照常,下載完驗過簽名才算就緒。不先下載的理由:客戶不一定要升這一版,先下載會白佔頻寬與硬碟。
D23 離線更新:管理員自己上傳
連不到外網的客戶,我們把更新包交給客戶,管理員在更新中心「離線更新」上傳。上傳後從驗簽名開始,跟線上下載完全同一條路。
D24 記錄是誰更新的
每一次更新都記:誰按的下載、誰按的更新(主系統帳號與名稱、時間、立即或排程)、誰取消的;若是 IT 在主機上手動執行或用 guidantai recover,記主機登入帳號(由指令寫進進度檔)。主系統走既有稽核事件;指令這邊寫在進度檔,主系統恢復後同步回稽核紀錄。更新中心的更新紀錄表每一列都顯示操作人。
D13 客戶機上由 guidantai upgrade 換版,主系統不碰 docker
主系統跑在容器裡,換不掉自己。換版的是裝機時已經放在主機上的 guidantai 指令(bash,/usr/local/bin/guidantai,隨出貨包更新),用 root 跑。主系統容器沒有 docker 權限,只負責通知、下載、驗簽名、顯示、寫一張更新單;能停容器、換版本的權限等於整台主機的最高權限,不交給對外的網頁服務。
D19 跑到一半的工作:等它做完,永遠不強制停
更新開始先停止接新工作,等進行中的證據分類、檢測派送做完。超過 30 分鐘還沒完,通知管理員「還有 N 筆在跑」,繼續等。等到維護時段結束還沒完,這次就不更新:什麼都沒動,恢復接新工作,通知管理員改約時間。理由:等待發生在服務停掉之前,放棄更新等於什麼事都沒發生,最壞只是延期;而半夜排程沒人能回答「要不要強制停」,延期是系統唯一不會傷到東西的決定。
D20 不做救援頁,不開第二道門
一個能退回、能重啟容器的網頁,等於拿著主機最高權限,又開在另一個埠、用另一組裝機時產生的固定密碼——對外就是多一道門,而管理埠正是攻擊者最愛掃的目標。它原本要解的情況,幾乎都已經由「時限+自動退回+開機檢查」自動處理;剩下真的要人出手的,走客戶本來就有的主機登入(guidantai recover)。詳見 §7。
D21 不做常駐的更新助手;執行者就是 guidantai upgrade,由 systemd 觸發
不新增容器、不新增獨立執行檔、不給任何容器 docker 權限。主系統寫更新單,主機上一個 systemd 檔案觸發器(兩個十行的設定檔)看到更新單幾秒內就呼叫 guidantai upgrade,排程的話到點才叫——Debian 的自動安全更新就是這個形狀。install.sh 本來就要求主機有 systemd、本來就在主機上裝 guidantai,所以這不是新成本。GitLab Runner、Ansible 這類現成工具能代勞的只有「等到時間、把腳本跑起來」這件小事,代價卻是客戶主機上多一條我們能下任意指令的通道,不划算。
D25 服務停掉之後不給取消鈕,靠時限自動判失敗、自動退回
服務停掉之前(下載中、等工作做完)隨時能取消,什麼都沒動。服務停掉之後(複製資料庫、升級、健康檢查)每一步各有時限(見 §5),任何一條觸發就自動退回,管理員不用按任何東西,最多等到時限就有結果。不給取消鈕的理由:那幾分鐘資料庫是停的,網頁沒辦法確認按的人是不是管理員,要做就得另發票或另開一道門;為了提早幾分鐘退回不值得。真要在這幾分鐘人工介入,IT 在主機敲 guidantai recover。
D26 沒有「敲指令」這件事:有「系統更新」權限的人在畫面上按
權限系統加一個「系統更新」能力,預設給系統管理員,可以另外授權。按「立即更新」或選排程時間後,主系統只做一件事:在主機上寫一張更新單(版本、時間、誰按的)。主機上的 systemd 檔案觸發器看到更新單就呼叫 guidantai upgrade,排程的到點才叫。主系統拿到的權限只有「寫一個檔」。指令本身仍在,IT 想手動跑也可以,當備援。
D14 失敗處理:開放使用前一律退回;開放使用後不退回,發修正版
開放使用那一刻是唯一的「不可退回點」。在那之前資料沒有變過,退回不掉資料;在那之後退回一定會丟掉這段時間的新資料。
D15 下載要管理員確認;更新要有「系統更新」權限的人按,可立即也可排程;任何版本都不自動裝
更新會停機、會改功能,時間由客戶挑;客戶不更新是客戶的事。D28 說明為什麼連安全修正也不自動裝。
D16 Agent 先推 1 台,正常再推其餘
Agent 失敗只影響那 1 台,主系統不受影響。Agent 版本永遠不比主系統新。
D17 必經版本只有三種情況才設
資料要分兩步搬、基線重整、升級流程本身改了。寫 migration 的人在 manifest 標記,封包時自動算出「從哪版可直升」寫進更新包;CI 用比必經版本更舊的庫試升必須被擋、用必經版本的庫試升必須成功。
D18 資料不掉的做法:在複本上升級
詳見 §6。
D2 更新站與授權中心分開
授權中心可能不對客戶開放;更新來源與簽發站合在一起,攻破一個等於兩個都破。客戶出示本機簽好的授權檔,更新站用授權中心的公鑰驗,不連授權中心、不存客戶名單。
D7 授權過期的客戶仍可升到同一個主版號的最新版;換主版號(2.0)要重新授權
授權擋的是「能用什麼」,不是「能裝什麼」:產品現在就有這道門,授權過期系統進唯讀、模組照授權檔鎖。過期客戶升到最新版,拿到的是一套唯讀的最新版,新功能照樣鎖著,我們沒有少收錢;而過期客戶的系統還在跑、還連著網路,有洞不補對雙方都是風險。所以更新站不看授權到期日,只看兩件事:這是不是一台合法安裝的機器(本機授權檔由授權中心簽過)、有沒有在拒絕名單。主版號是唯一的範圍限制:授權檔簽的是「1.x」,出 2.0 時過期客戶就停在 1.x 的最後一版,這是買斷軟體「含同代升級、換代另購」的常見做法(JetBrains、Sublime、Beyond Compare)。現在只有 1.x,這條規則目前零成本,先寫下來免得出 2.0 再吵。詳見 §10 CRA。
D27 只維護最新版;安全修正就是發下一版;五年支援靠「永遠可以免費升到最新版」達成;要找懂 CRA 的人確認兩題
我們每一版都是修正加功能混著出,從沒出過「只修安全」的版,以後也不打算分:有洞就修、修完出下一個版號,release note 寫明是安全修正。舊版的洞靠「免費升到最新版、升級保證資料不掉」解決,不另外維護舊版本線。「五年支援」寫成「五年內永遠有一條免費、不掉資料的路徑升到有修正的版本」。要拿去問懂 CRA 的人的兩題:①我們到底受不受 CRA 管(客戶在台灣、產品沒賣進歐盟的話整節 §10 可以拆掉);②若受管,「單一版本線、安全修正與功能一起出、新功能由授權鎖」這種寫法是否滿足「安全更新與功能更新分開提供」那條(CRA 原文有「技術上可行時」的但書)。CRA 主要義務 2027 年 12 月適用,design 定案前先問。
D28 一律人按,不做自動安裝;安全修正也不例外
決策者裁示:「絕對不要自動裝,客戶不更新是他的事。」理由站得住:落地企業軟體更新要停機、客戶有自己的維護時段、裝了出事是客戶的 IT 要收拾,GitLab、Jira、Sentry 這些自架產品也都不自動裝;而且我們沒有「只修安全、不動資料庫」的小版(D27),每一版都可能帶 migration,自動裝的風險跟功能版一樣大。CRA 那條原文是「適用時」才要預設自動裝,CRA 文件寫一段「本類產品不適用自動裝」的理由即可(併進 D27 第②題一起問)。代價:有洞的客戶拖著不升,我們只能在更新中心一直提醒,不能替他按。
D29 每一版的更新包至少保留 10 年
CRA 第 13 條第 9 項要求安全更新至少 10 年可下載;我們不分安全版,所以每一版都留。放 R2,一年十幾版也才幾十 GB,費用可以忽略。
D30 升級指令開跑第一步先換成新版的自己,再跑剩下的步驟;畫面上不顯示指令版本
主機上的 guidantai 是裝機時從出貨包複製到 /usr/local/bin/ 的,客戶跑 1.24.1 時那支指令也是 1.24.1 版。新版的升級流程(複本升級、每步時限、自動退回)寫在新版指令裡,舊指令不會做。所以 guidantai upgrade 第一件事是從已驗過簽名與雜湊的新更新包取出新版指令、換掉 /usr/local/bin/guidantai、再以新版接著跑;之後每次升級跑的都是「目標版本的升級流程」,舊版指令只要會「驗包、換自己」就夠(GitLab omnibus、Docker installer 都這樣)。代價:第一期出去那版指令要把「換自己」這一步做對,之後才不會再卡;換自己失敗就當預檢不過,什麼都不動。這是指令內部的事,更新中心頁不顯示「指令版本」「相容」之類的欄位,客戶不需要知道有這支指令。
D31 發布清單 90 天有效,過期重新蓋章;不做「回應 24 小時內」
更新站手上沒有私鑰(D3),簽章是決策者發版時離線蓋的,更新站只是原樣轉交,沒辦法對每次回應重新簽時間。所以「回應超過 24 小時拒收」做不到。改成清單本身帶簽發時間與有效期 90 天,客戶端超過就拒收;一版發出去 90 天內沒下一版,決策者跑一次 guidant-release publish 重新蓋章延長。這是 TUF 的 timestamp 角色的簡化版:防的是有人把過期的舊清單一直餵給客戶讓他升不上來,90 天是「被卡的最長時間」。
D1 更新包放在 S3 相容的私有空間,用 Cloudflare R2
程式只認 S3 介面,換供應商改一個網址。R2 對外下載流量不收費:客戶每次更新抓 2.9GB,放別家每出一版都要付一次流量費。
D2a 更新站跑在 Cloudflare Worker,先用免費方案
更新站是我們這邊唯一對客戶開放的服務:回答「有沒有新版、你能不能拿」(合法安裝、不在拒絕名單、同主版號)、產短效下載網址、收客戶回報的版號。對外服務最花的是守機器的人力,不是寫程式的時間;R2、弱點資料網域、文件站都已在 Cloudflare,多開一個功能不多一家帳務。先用免費方案(每天 10 萬次請求,我們一天不到 100 次),跑一段時間再看要不要升付費方案(每月 5 美元)或改成自己養一台。代價:驗章要改呼叫 Cloudflare 的加密函式,多一份驗章實作,用授權中心真簽出來的檔當守衛測試。排除:跟授權中心同一台(D2)、自己養一台對外主機(長期要守)、完全不寫程式只在 R2 放清單檔(做不到 D6、D9、D12)。
D3 發版的最後一步在決策者電腦蓋章;發布印章一正一備,跟授權中心的印章分開
更新包要帶一個只有我們蓋得出來的印章(Ed25519 簽章),客戶系統用程式裡的印樣(公鑰)對章,對不上整包丟掉。CI 在 188 出包、上傳 R2、在更新站登記成「草稿」,客戶看不到;決策者在自己電腦跑 guidant-release publish <版> 蓋章,才變「已發布」。印章(私鑰)只在決策者電腦,188 被攻破的人出得了包、發不了版——這是「上版要決策者明示放行」鐵律的程式化。
印章怎麼保護、怎麼換(業界標準做法,TUF、apt、Android 系統更新都這樣):
PUBLIC_KEYS 以 kid 認公鑰),照搬。D6 不做頻道;草稿可以指定給幾台內部機器先裝
本意是 STG、POC 比客戶早拿到新版當白老鼠。頻道是給客戶看的概念,但客戶永遠只該看到正式版;改成草稿在後台指定「哪幾台機器看得到」,一個欄位解決,後台也少一欄。
D8 不做差異更新;D9 客戶只回報授權編號、機器指紋、現行版號、最近一次升級結果;D10 每天檢查一次、管理員可關;D11 客戶出示本機授權檔、更新站用授權中心公鑰驗
四項在流程表(§5)、安全原則(§11)與 D2 已寫定,這裡只登記。D10 補一句:每日檢查是唯一的開關;關了會在更新中心一直顯示提醒。
D12 拒絕名單,第一版決策者在更新站後台手動加
授權過期也能升同主版號最新版(D7)之後,拒絕名單只剩「我們主動要切斷某客戶」這一種情況,不常用,不值得自動化。
客製化版本的客戶怎麼走更新,這輪先不談。
每一步都有「失敗或卡住怎麼辦」和「能不能取消」。技術選擇掛在步驟底下,不單獨討論。
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
flowchart TB
A0["① 發現新版<br/>每日檢查・只拿版本資訊"] --> B["② 管理員按下載<br/>記錄是誰"]
U["離線:管理員上傳更新包"] --> V
B --> V["③ 下載・驗簽名<br/>服務照常"]
V --> T["④ 有權限的人按更新<br/>立即或排程・寫更新單<br/>systemd 呼叫 guidantai upgrade・預檢"]
T --> D["⑤ 停止接新工作<br/>等進行中的做完・不強制停"]
D --> E["⑥ 停服務・複製資料庫"]
E --> F["⑦ 在複本上升級<br/>載入映像・套 migration"]
F --> G["⑧ 啟動新版・健康檢查"]
G --> H["⑨ 開放使用<br/>不可退回點"]
H --> I["⑩ Agent 跟進<br/>先 1 台再其餘"]
V -. 驗不過 .-> X0["停在更新前<br/>列出原因・什麼都沒動"]
T -. 預檢不過 .-> X0
D -. 取消・等不完 .-> X0
E -. 失敗・斷電 .-> R
F -. 失敗・超時・斷電 .-> R
G -. 不過・斷電 .-> R
R["自動退回<br/>改接現在那份資料庫<br/>啟動舊版映像"] --> X1["回到更新前<br/>資料完整・通知管理員"]
style H fill:#F6EBD5,stroke:#9C6B12,stroke-width:2px
style R fill:#FDECEC,stroke:#B42318
style X1 fill:#EEF6EE,stroke:#3F7D45
| 步驟 | 做什麼 | 失敗或卡住時 | 能取消嗎 | 技術選擇 |
|---|---|---|---|---|
| ① 發現新版 | 每天向更新站出示本機授權問一次,拿回簽好的版本資訊(幾 KB),通知管理員。不下載 | 連不到更新站就安靜略過,更新中心顯示「最近一次檢查失敗」 | — | 照弱點資料每日同步的形狀做;管理員也可按「立即檢查」 |
| ② 管理員按下載 | 看更新說明、停機時間、要不要先升必經版本;按「下載」。記錄是誰按的。任何版本都不自動下載 | — | 能 | 主系統的更新中心頁 |
| ③ 下載、驗簽名 | 確認後才下載 2.9GB,服務照常;斷線續傳;下載完驗簽名與雜湊,通過後顯示「已就緒」並通知管理員。離線客戶改成管理員上傳,從驗簽名開始同一條路 | 驗不過就丟掉重下;沒下載完就不顯示就緒,更新鈕不出現 | 能,取消下載 | 包就是現在出貨的 guidant-ai-<版>.tar.gz,不另開格式;更新站只給短效下載網址,檔案直接從私有倉庫拿 |
| ④ 按更新、預檢 | 有「系統更新」權限的人按「立即更新」或選排程時間,任何版本都不自動裝(D28)。主系統寫更新單;systemd 觸發器呼叫 guidantai upgrade(排程的到點才叫);先提醒在線使用者,再預檢:空間夠不夠放複本與新映像、資料庫連得上、版本路徑合法;先從已驗過簽名的新包裡取出新版 guidantai 換掉自己,再用新版跑後面每一步(D30) |
列出不過的項目,什麼都不動 | 能 | 就緒時更新中心先跑一次預檢,執行時再跑一次;空間不足時提示可清掉多少舊映像;指令仍可由 IT 手動執行當備援 |
| ⑤ 停止接新工作 | 不再派新的證據分類、檢測工作給 Agent;等進行中的做完。不強制停(D19) | 超過 30 分鐘通知管理員,繼續等;到維護時段結束還沒完就這次不更新,恢復接新工作 | 能:更新中心按「取消」,什麼都沒動 | 背景工作現有的租約機制 |
| ⑥ 停服務、複製資料庫 | 停 API、背景工作、資料庫;把整個資料庫資料夾複製一份 | 複製出錯或超過預估時間三倍:刪掉複本,啟動舊版 | 沒有取消鈕(D25) | 先停 API 是 POC 踩過的教訓(不停的話 Agent 心跳會讓 migration 鎖死);複製時間跟資料庫大小成正比,預檢要把預估停機時間算給管理員看 |
| ⑦ 在複本上升級 | 載入新映像、驗雜湊、對複本套 migration,逐支回報進度 | 載入出錯或超過 5 分鐘、任一支 migration 超過 10 分鐘沒有新進度:自動退回 | 沒有取消鈕 | 現有 migrate.sh 已經逐支套用,只要每支寫進度檔 |
| ⑧ 啟動新版、健康檢查 | 新版接複本啟動,跑健康檢查 | 180 秒內沒變健康、或登入與主要 API 打不通:自動退回 | 沒有取消鈕 | 現有 180 秒健康檢查沿用,加上登入與幾支主要 API 的實打 |
| ⑨ 開放使用 | 換上新版前門、解除維護頁、讓使用者進來 | — | 不能,這是不可退回點 | 之後有問題發修正版 |
| ⑩ Agent 跟進 | 主系統正常後,Agent 目標版本改成新版;先 1 台,正常再推其餘 | 只影響那台 Agent | 個別 Agent 可暫停 | 見 §9 |
整段總上限 60 分鐘:⑥~⑧ 加起來超過一律判失敗退回,不會永遠卡著。
進度檔:一個放在主機上的純文字檔(upgrade-progress.json),只有 guidantai upgrade 寫,記錄這次更新的編號、目前第幾步、每步開始時間、時限、結果、錯誤訊息、誰觸發的。更新中心頁、維護頁、開機檢查都讀這一份,所以三邊看到的永遠一樣。
更新中途主系統和資料庫是停的,畫面不能靠它。分三段:
| 階段 | 畫面誰給 | 資料從哪來 |
|---|---|---|
| 服務停掉之前(下載、預檢、等工作做完) | 主系統的更新中心,跟平常一樣 | 後端讀進度檔 |
| 服務停掉之後(複製資料庫、升級、健康檢查) | 前門(nginx)直接給一張維護頁,後端、資料庫都不在 | 維護頁每 3 秒讀一次進度檔 |
| 開放使用之後 | 主系統的更新中心 | 後端讀進度檔,顯示結果、寫進更新紀錄 |
install.sh --upgrade 是整組一起重建,那樣維護頁會跟著消失。改成舊版 nginx 從頭留到尾負責顯示,第 ⑨ 步才換成新版;使用者只在「開放使用」那一刻感覺到幾秒斷線。| 現有做法 | 本案做法 | |
|---|---|---|
| 升級對象 | 直接在現在那份資料庫上套 migration | 先複製一份,在複本上套 |
| 失敗時 | 從升級前的備份還原 | 改接回現在那份,啟動舊版映像 |
| 退回要多久 | 還原一次,資料越多越久,而且是在最慌的時候做 | 幾秒 |
| 退回本身會不會失敗 | 會:還原是寫入動作 | 幾乎不會:沒有寫入,只是換接 |
中間沒有新資料:從第 ⑥ 步停服務到第 ⑨ 步開放使用,服務是關著的,沒有任何新資料寫進來。所以退回到現在那份等於一筆都沒掉。
開放使用之後:新資料寫進新的那份。舊的那份保留到下一次更新成功為止,標示為「升級前快照」,只用於災難救援,不是回滾——拿它還原會丟掉開放後的資料,要由我們判斷才用。
| 地方 | 風險 | 處理 |
|---|---|---|
| **證據檔案(SeaweedFS,POC 約 10GB) | 太大,每次複製太慢 | 不複製**。服務停著時沒有新檔案寫進來,檔案不會變。規則:升級過程不准改寫既有檔案;真有一版需要改檔案,就把那版標成特殊版,那次才連檔案一起複製 |
| 設定檔與憑證 | migration 或新版可能改設定 | 第 ⑥ 步一起複製一份,退回時一起換回 |
| Agent 跑完但送不出的結果 | 現在 Agent 回報失敗只記 log,結果直接丟掉(evidence-agent core/task_executor.py 的 _result) |
第一期補:Agent 送不出的結果先存在本機,主系統恢復後補送。第 ⑤ 步停止派新工作也能減少這種情況 |
| 跑到一半的背景工作 | 停服務時被中斷 | 不會發生:第 ⑤ 步等它做完才停,不強制停(D19);等不完就這次不更新 |
| 項目 | 實際值 | 對設計的意義 |
|---|---|---|
| 資料庫 | 533MB,資料夾約 650MB | 複製一份幾十秒,可行。客戶用兩年到 50GB 時可能要十幾分鐘,預檢要把預估停機時間算出來 |
| 證據檔案 | 約 10GB | 不複製的理由 |
| 硬碟剩餘 | 20GB,已用 90% | 預檢一定要先算空間 |
| 主機上的映像與 build 殘留 | 映像 35GB、build 快取 21GB | POC 自己的殘留,不是產品問題;但預檢要提示可清掉多少 |
所有「回來」都由升級指令自己完成,不需要人在旁邊,也不需要任何新的對外入口:
guidantai 隨 docker 啟動時先讀進度檔,上次更新停在 ⑥~⑧ 沒做完,就自動退回。真的要人出手的情況只剩「連自動退回都沒跑」(例如 docker 本身當掉),那時走客戶本來就有的主機登入:IT 用 SSH 進主機執行 sudo guidantai recover。我們沒有多開任何門,誰能進主機、怎麼管,是客戶自己的政策。
| 情況 | 結果 | 要人嗎 |
|---|---|---|
| migration 卡住不動 | 超時自動退回 | 不用 |
| 新版起不來 | 健康檢查不過,自動退回 | 不用 |
| 更新中斷電或主機重開 | 開機檢查讀進度檔,自動退回 | 不用 |
| 管理員反悔(服務停掉之前) | 更新中心按「取消」,恢復接新工作,什麼都沒動 | 按一下 |
| 管理員反悔(服務停掉之後) | 沒有取消鈕;最多跑滿時限就有結果(D25) | 不用 |
| 退回後想再試 | 更新包還在主機上,不用重下載;更新中心按「重試」,從預檢重跑 | 按一下 |
| docker 本身當掉、自動退回沒跑 | 唯一要登入主機的情況:IT 執行 sudo guidantai recover,效果等同自動退回 |
敲一行 |
| 硬碟滿了 | 預檢就擋下,不會開始 | 不用 |
| 要我們遠端判斷原因 | 更新中心「下載診斷包」或 IT 執行 sudo guidantai diag:進度檔、各容器 log、主機狀態;自動遮掉密碼與金鑰。寄給我們就能判斷 |
寄個檔 |
任何情況的終點只有兩個:新版正常開放,或退回更新前、資料完整。不會停在中間。
不做「從中斷的那一步接著跑」:斷在哪一步沒人說得準,從複本重新升一次比接著跑穩,而且複本是整份重做,不會留半套的狀態。
每版出一整包,可以一次跨好幾版。 包裡帶著從第一支到最新一支的全部 migration,migrate.sh 拿客戶庫已套的清單比對,缺哪些就依序補哪些。所以 1.24.1 直升 1.27.0,就是把中間三版的 migration 一次套完。不做差異包:要維護每兩版之間的差異,是另一套要顧的東西。
必經版本(D17 已定):只有三種情況才設——資料要分兩步搬(中間那版的程式負責搬資料)、基線重整(新包不再帶舊 migration)、升級流程本身改了(舊的指令看不懂新包)。由 manifest 標記自動算出「從哪版可直升」寫進更新包,現在寫死的 1.0.0 改成自動產生。
跨版排練:每次封正式包前,CI 拿真實的舊庫(不是乾淨新裝)試升,這是 1.21.0 抓到 fr093-4d 順序問題的做法。STG 與 POC 現在都是 1.26.0、跟主線同版,自然升級測不到跨版,要另外在測試機用 189 留著的 1.23.1 映像加 POC 資料庫備份排練。全程在測試機做,不碰 STG、POC。
| 項目 | 做法 |
|---|---|
| 誰告訴 Agent 要升級 | 主系統。主系統升完並開放後,把 Agent 目標版本改成新版;Agent 下次心跳看到就去主系統拿包 |
| Agent 拿包從哪來 | 主系統(主系統的更新包裡就帶 Agent 的包)。Agent 不連外網、不連更新站 |
| 誰在 Agent 主機上換版 | Agent 自己的升級指令,跟主系統同一套做法:複本、時限、自動退回 |
| 分批 | 先 1 台,正常再推其餘(D16);任何一台失敗就暫停推送,通知管理員 |
| 版本約束 | Agent 永遠不比主系統新;主系統退回時,還沒升的 Agent 不會被推 |
| 只升 Agent,主系統不動 | 可以,同一套機制。兩種情況:①主系統已經升了、某幾台 Agent 落後(新加的、上次暫停的、當時失敗的)——更新中心 Agent 區選那幾台按「推送」,走一樣的先一台再其餘;②我們只修 Agent(例如 Agent 的檢測工具有洞)——更新站發 Agent 單獨修正版(只有 Agent 的包,幾十 MB),主系統照一樣的路下載、驗簽名、存起來,但不停服務、不升自己,直接推給 Agent。版本約束不變:Agent 修正版的主線版號必須等於主系統現行版 |
| 作業系統更新 | 不在本案(Flatcar 那條線另案) |
版本不分「安全修補版」與「功能版」:我們每一版都是修正加功能混著出,安全修正就是發下一版(D27)。授權管的是能用什麼與能升到哪一代,不管能不能裝:
| 客戶狀態 | 能不能升 | 升上去拿到什麼 |
|---|---|---|
| 授權有效 | 同主版號內任何新版 | 完整功能,照授權檔的模組 |
| 授權過期 | 同主版號(1.x)內任何新版,含安全修正 | 唯讀的最新版;新功能照樣鎖(產品現有的授權門) |
| 換主版號(2.0) | 要重新授權 | — |
| 在拒絕名單 | 更新站不回應 | — |
更新站因此只判三件事:本機授權檔是不是授權中心簽的、有沒有在拒絕名單、目標版本跟授權檔簽的主版號一不一樣。不看授權到期日。
CRA 對更新的要求怎麼對:
| 條文 | 要求 | 本案怎麼落 |
|---|---|---|
| 第 13 條第 8 項 | 支援期至少 5 年,到期年月賣的時候寫明 | 「五年內永遠可以免費升到同主版號最新版」(D27);要問顧問 |
| 附件一第二部分第 8 點 | 安全更新免費 | 過期客戶也能升到含修正的最新版(D7) |
| 附件一第二部分第 2 點 | 技術上可行時,安全更新與功能更新分開提供 | 不分。寫「單一版本線、新功能由授權鎖、分開提供技術上不可行」的理由;要問顧問(D27 第②題) |
| 第 13 條第 9 項 | 每個安全更新至少 10 年可下載 | 每一版都留 10 年(D29) |
| 附件一第一部分第 2 點 c 款 | 適用時安全更新預設自動裝 | 不自動裝,寫「本類產品不適用」理由(D28);要問顧問 |
| 附件一第二部分第 8 點 | 安全更新附說明,告訴使用者該做什麼 | 含安全修正的版本,更新說明固定多「影響範圍」「建議動作」兩段 |
例子:客戶 C 跑 1.25.1,授權 2026-09 到期沒續,系統進唯讀。2026-11 我們修一個洞,出 1.27.0。客戶 C 的更新中心照樣收到 1.27.0,管理員自己決定要不要按;升上去之後還是唯讀、新功能鎖著,但洞補了。2028 我們出 2.0,客戶 C 看不到,要續約才有。
更新站回答流程第 ① 步的「有沒有新版」,並在第 ③ 步給短效下載網址。離線客戶不經過它,自己上傳,兩條路驗同一套簽名。
| 角色 | 管什麼 | 手上有什麼 | 被攻破會怎樣 |
|---|---|---|---|
| 授權中心(現有,內網) | 簽發授權 | 簽發私鑰、客戶名單 | 能偽造授權,不能發更新 |
| 更新站(新,對客戶開放) | 回答「有沒有新版、你能不能拿」(合法安裝、不在拒絕名單、同主版號)、產短效下載網址、記客戶在哪版 | 只有公鑰、倉庫讀權、幾張表 | 能「不給」,不能「給假的」:客戶端驗簽名與雜湊全擋 |
| 私有倉庫 | 存檔、傳檔 | 更新包 | 檔被換掉驗不過 |
| 發布金鑰 | 簽發布清單 | 離線,在決策者電腦 | 不在任何伺服器上 |
發版流程:CI 出包後上傳倉庫、在更新站登記為「草稿」,客戶看不到;決策者在自己電腦跑 guidant-release publish <版>,用離線發布金鑰簽名後才對外。這是「上版要決策者明示放行」鐵律的程式化。
| 原則 | 本案怎麼落 |
|---|---|
| 發布金鑰獨立、離線 | 跟授權簽發鑰是兩把;只在決策者電腦;CI 只能登記草稿;一正一備,備用封存,換鑰用舊鑰背書(D3) |
| 兩個系統互不持有對方憑證 | 攻破一邊,另一邊的能力拿不到 |
| 客戶端不信任更新站 | 只信兩把公鑰,更新站回的每份清單都要過發布公鑰驗章 |
| 不能被卡在舊版 | 發布清單帶簽發時間與 90 天有效期,過期的清單客戶端拒收;要延長就重新蓋章(D31) |
| 拒絕降版 | 只接受版號大於現行版的清單 |
| 只回報必要資訊 | 授權編號、機器指紋、現行版號、升級結果;不送使用資料、不送客戶名稱 |
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
flowchart LR
Q1["對外更新站<br/>客戶每天打,最常被敲"] --> Q2["和授權中心同一台?"]
Q2 -- 是 --> X1["✗ 入口與簽發私鑰同機<br/>破一個等於破兩個"]
Q2 -- 否 --> Q3["自己養一台?"]
Q3 -- 是 --> X2["✗ OS・備份・憑證・DDoS<br/>對外機器長期要守"]
Q3 -- 否,無伺服器 --> Q4["哪一家?"]
Q4 -- 新的一家 --> X3["✗ 多一家帳務與除錯"]
Q4 -- Cloudflare --> Q5["R2・vulndb・文件站都在<br/>同一家多開一功能<br/>用什麼寫?"]
Q5 -- Python --> X4["✗ Python Worker 仍是測試版<br/>cryptography 進不去"]
Q5 -- TypeScript --> OK["✓ WebCrypto・D1・R2 原生<br/>真簽章當守衛測試"]
OK --> R["TS Worker+D1+R2<br/>零機器・同一家・正路"]
選 Worker 的理由:零機器(對外服務最花人力的是守,不是寫)、同一家(R2、弱點資料網域、文件站都已在 Cloudflare)、語言選 TypeScript(決策者 2026-10-29 改判:Python Worker 仍是測試版,TS 是 Cloudflare 正路,WebCrypto/D1/R2 都原生;發布 CLI 同 repo 用 TS,Node 內建 Ed25519,一個 repo 一套工具鏈)。費用:Worker 免費方案每天 10 萬次請求,50 家客戶每天問一次是 50 次,先用免費方案;付費方案每月 5 美元含 1,000 萬次。R2 每 GB 每月 0.015 美元、前 10GB 免費、下載流量不收費,三條線各出修補留 10 年估 300GB 約 4.5 美元。自己養一台對外小機器最便宜也是每月 5 到 10 美元,錢差不多,多的是要守。代價:cryptography 套件目前進不去 Worker,驗章要改呼叫 WebCrypto,等於多一份驗章實作;用授權中心真簽出來的檔當測試資料,Worker 驗不過就擋部署,兩份實作被同一組真資料綁住。
| 零件 | 現況 | 本案動作 |
|---|---|---|
| 升級指令 | install.sh --upgrade 五步:檢查版本、備份(失敗即中止)、載入映像並驗雜湊、套 migration 並斷言待套 0 支、換版加 180 秒健康檢查;維運子命令已獨立成 /usr/local/bin/guidantai(bash、root、隨出貨包更新) |
extend。guidantai upgrade 呼叫的就是它;補停接新工作、先停服務、複本升級、每步時限、進度檔、自動退回、開機檢查、保留上一版映像、前門最後才換。不另寫一套,否則手動與線上兩條路會各自演化 |
| migration 執行 | scripts/init/migrate.sh 比對已套清單、依序補套 |
extend。逐支寫進度 |
| 出貨包 | guidant-ai-<版>.tar.gz + .sha256,內含映像、VERSION、manifest、bundle-info;1.26.0 壓縮後 2.9GB,其中後端映像佔一半以上,第三方映像(資料庫、快取、檔案庫、分類器、錯誤追蹤)佔其餘 |
reuse。bundle-info 的「適用起始版號」改成自動算 |
| 簽章 | 授權中心 Ed25519 簽章函式、主產品 PUBLIC_KEYS 以 kid 認公鑰 |
reuse 函式。發布金鑰另一把 |
| 主機端 CLI | guidantai 子命令(status/logs/diag…) |
extend。加 upgrade、recover;diag 的輸出併進診斷包 |
| 前門 | nginx 容器,有 healthcheck;後端掛掉時回 502 | extend。加維護頁:後端連不上時改送靜態頁,頁面讀唯讀掛進來的進度檔 |
| 主機 | install.sh 要求 systemd(取機器識別碼);容器 restart: unless-stopped 隨 docker 開機起 |
reuse。更新單觸發器(D26)就是兩個 systemd 設定檔,隨 install.sh 裝 |
| 權限 | common/authz/ 的 capability 軸,主體域守門可用 route decorator |
extend。加「系統更新」能力,更新中心的下載、更新、取消端點用它守門 |
| Agent 回報 | 送不出的結果只記 log、直接丟掉 | extend。先存本機、恢復後補送 |
| 背景工作 | 工作單在資料庫、有租約與寬限秒數 | reuse。第 ⑤ 步「停接新工作、等做完」用它 |
| 弱點資料每日同步 | 心跳 observer 每日拉、驗章、連不上靜默略過 | reuse 形狀(第 ① 步每日檢查照它做) |
| 缺 | 主系統沒有更新中心頁 | new:FE 一頁+BE 的檢查、下載、驗簽名、讀進度檔端點 |
| 缺 | 對客戶開放的更新站 | new(第二期) |
沒有待裁項。CRA 兩題決策者 2026-10-29 裁先不管(有答案再回頭看 §15 反悔條件)——原題留著:①我們受不受 CRA 管;②若受管,「不分安全版、不自動裝、過期仍可免費升同主版號最新版」這種寫法過不過。這卡 design 定案、不卡討論稿。
| 誰 | 更新怎麼跑 | 本案借了什麼 |
|---|---|---|
| GitLab、Sentry、Mattermost、Jira Data Center(企業自架) | IT 在主機上跑一道指令;產品負責讓那道指令安全:先備份、套 migration、檢查、出事還原;GitLab 公開升級路徑表列出必經版本 | 第一期的形狀:一道絕不出事的指令;必經版本規則 |
| Debian unattended-upgrades | systemd 定時器到點自動裝安全更新;管理員可關、可延後 | 只借「排程到點由 systemd 跑」這個形狀(D26);不借自動裝(D28) |
| vCenter(VMware 落地版) | 管理頁「暫存」與「安裝」分開;升級前先備份 | 下載與安裝分開 |
| TrueNAS、Synology | 升級前拍檔案系統快照,失敗退回快照 | 升級前留一份能退回的副本。我們管不了客戶主機的檔案系統,所以用複製資料庫代替快照 |
| Android、ChromeOS | 新版裝在另一個分割區,起不來自動退回舊分割區 | 在複本上升級、健康檢查不過自動退回 |
| Nextcloud、WordPress | 網頁按一下就升級;Nextcloud 官方對正式環境不建議用,WordPress 出事率高 | 反面教材:主系統不自己升級自己 |
| Wazuh、Elastic Fleet | 管理端推 Agent 升級包;Agent 版本不可比管理端新;分批、可暫停、失敗自動退回 | Agent 跟主系統走、先推一台 |
| Replicated(幫軟體廠商做落地版的平台) | 授權序號拿更新;線上離線同一份包 | 憑授權拿更新。不買:只支援 Kubernetes,我們是 docker compose;Business 方案每月 3,000 美元起再加每授權費 |
| TUF(CNCF 畢業的更新安全規範) | 簽章在發布端、驗章在客戶端;金鑰分開離線;清單帶時效;拒絕降版 | 更新站的安全原則 |
業界共識:下載與安裝分開、升級前留副本、失敗自動退回、Agent 跟著主系統走。企業市場多半由 IT 敲指令;本案把「敲指令」換成「寫更新單讓 systemd 叫」,主系統拿到的權限沒有變多。本案多做的一件是「在複本上升級」,讓退回不必還原。
| 方案 | 為什麼不選 |
|---|---|
| 直接在現在那份資料庫上升級、失敗再還原 | 還原是寫入動作,本身會失敗、會卡;資料越大越久,而且發生在最慌的時候 |
| 升完之後提供回滾 | 回滾一定丟掉開放後的新資料,等於把風險交給客戶。改發修正版 |
| 自動安裝(任何版本) | 會停機、可能帶 migration;客戶的維護時段與風險由客戶自己定,客戶不更新是客戶的事(D28) |
| 分「安全修補版」與「功能版」兩種 | 我們每版都是修正加功能混著出,分開等於多維護一套發版規則與舊版本線;過期客戶要拿修正改用「可升同主版號最新版」解(D7、D27)。若 CRA 顧問說非分不可再回頭,更新站只多一個「種類」欄 |
| 每年一個長期支援版、支援五年 | 每個資安修補要補五次,人力撐不住(D27) |
| 主系統容器直接操作 docker 換自己 | 容器要拿到 docker 的完整權限,被打穿等於整台主機淪陷;而且主系統壞了就沒人能救 |
| 救援頁(另一個埠+專用密碼) | 等於多開一道門,固定密碼不跟著客戶帳號政策走;它要解的情況幾乎都已由自動退回涵蓋(D20) |
| 常駐的更新助手(容器拿 docker 權限,或 Nuitka 獨立執行檔) | 多一個要維護、要資安審查的常駐程式;它做的四件事(到點跑、寫進度、退回、開機檢查)用現有的 guidantai 加 systemd 觸發器就有(D21) |
| 服務停掉之後的取消鈕(短效票) | 資料庫停了認不了人,要另發票或另開門;那幾分鐘靠時限自動退回就夠(D25) |
| 裝 GitLab Runner、Ansible agent 當執行者 | 能代勞的只有「等到時間、跑腳本」;代價是客戶主機上多一條我們能下任意指令的通道,資安審查過不了 |
| 中心推送進客戶機(我們用 SSH 連進去裝) | 要客戶開入站或 VPN,資安合規客戶不會接受 |
| 從中斷的那一步接著跑 | 斷在哪沒人說得準;從複本整份重做比接著跑穩 |
| 差異更新包 | 要維護每兩版之間的差異;整包跨版靠 migration 補套就夠 |
| 只換有洞的那顆容器 | 技術上做得到(包裡只放改到的映像、清單寫以哪版為底、升級指令只改那一行),但多一種包的規則要維護與測試;企業自架產品(GitLab、Sentry、Mattermost)安全修補也是整包。客戶數上百或客戶抱怨流量時再回頭做,做法不變 |
| 把更新做進授權中心 | 授權中心可能不對客戶開放;合在一起攻破一個等於兩個(D2) |
| Harbor 開給客戶 docker pull | 多養一個對外關鍵服務、每客戶發帳號;BE 映像每版層全變,省不到流量 |
| 買 Replicated | 只支援 Kubernetes,不適用 docker compose 部署;費用高 |
主機上的檔(都在資料目錄,主系統容器唯讀掛載):
| 檔 | 誰寫 | 內容 |
|---|---|---|
進度檔 upgrade-progress.json |
只有 guidantai upgrade |
這次更新的編號、從哪版到哪版、路徑(含必經版本的分段)、目前步驟、每步開始時間、時限、結果、錯誤訊息、執行人(主機帳號)、現在那份資料庫與複本的位置、上一版映像標籤 |
更新單 upgrade-order.json |
主系統(有權限的人按更新時) | 版本、立即或排程時間、誰按的;systemd 觸發器看到就呼叫 guidantai upgrade(排程的到點才叫),跑完刪掉。主系統對這個目錄只有寫權 |
| 更新包 | 主系統下載或管理員上傳 | guidant-ai-<版>.tar.gz 與簽名;驗過才留 |
主系統資料表(主線 migration):
| 表 | 欄位要點 | 用途 |
|---|---|---|
product_update_jobs |
從版、到版、狀態(downloading/ready/scheduled/running/succeeded/rolled_back/skipped/cancelled)、下載人、按更新的人(帳號+名稱+時間、立即或排程)、取消人、來源(線上/離線上傳)、排程時間、開始與結束時間、失敗原因 | 更新中心頁的更新紀錄;結束後從進度檔同步結果 |
agent_update_rollouts |
Agent、目標版本、批次、狀態、時間、來源(跟著主系統/單獨推送/Agent 修正版) | 第四期 Agent 分批,含只升 Agent |
更新站的表:product_releases(版本、主版號、可直升起始版、雜湊、大小、狀態、草稿先開給哪幾台內部機器、是否含安全修正、簽好的清單與印章編號、保留到期)、instance_version_reports(授權編號、指紋、現行版本、最近回報、最近升級結果;沒有客戶名稱)、release_download_log、license_denylist、signing_keys(印章編號、印樣、狀態:主力/備用/作廢)。
prototype/index.html:有新版通知、更新中心(下載、離線上傳)、下載中、已就緒(立即或排程)、維護頁進度、自動退回、完成與 Agent、跨版兩段、例外、更新站後台(草稿先開給內部機器、印章)。guidantai、install.sh、migrate.sh、出包、nginx 維護頁)與 evidence-agent(結果暫存補送);第二期新 repo update-center(已建、已登記工作坊)與 BE 的 CI;第三期 BE(更新中心端點、系統更新權限、更新單、systemd 觸發器)、FE;第四期 BE、evidence-agent(含只升 Agent 與 Agent 修正版)。