管理員在主系統就能拿到 Agent 安裝包與 .sha256 兩個檔的下載連結,不必再向原廠要。連結限時(預設 30 分鐘,可調)、可複製給裝機的人。
📑 本頁是 v1.0(定稿,已被推翻)。 2026-11-10 決策者裁改為 R2 直連、時限改由原廠在更新站後台設定,見 設計 v2.1(現行;v2.0 為中間版)。本頁內容保留定稿原樣,不回頭改。
整案一句話:主系統更新中心已經會把 Agent 安裝包下載到本機(agent-bundles/<版>/,旁邊有 .sha256 與簽好章的清單),但人拿不到;這案開一條「登入者產生短效連結 → 任何人憑連結在時限內下載」的路,兩個入口共用。
| 批 | 卡 | 做什麼 | 產出 | 完成判定 |
|---|---|---|---|---|
| 1 | BE | 發連結端點、兩個供檔端點、時限參數讀寫、稽核事件、error code | api/product_update/routes/agent_bundle_link_route.py、app/product_update/service/agent_bundle_link_service.py |
curl 拿到的票在時限內能下載整包與 .sha256,過期 401,sha256sum 對得上 |
| 2 | FE | 共用元件「安裝包下載」+更新中心 Agent 區按鈕+註冊 token 對話框內嵌+時限欄位 | AgentBundleLinks.vue 等 |
兩個入口都看得到兩列連結、複製鈕在 http+IP 下也能複製 |
| 3 | 文件 | FR-189 快速版與完整版手冊「出貨包」來源改成「從主系統下載」;FR-191 README 收口 | docs/user-manual/agent/、FR-189 site/index.html |
手冊 grep 不到「向原廠取得出貨包」 |
依賴:批 2 等批 1 合回(要打真端點);批 3 等批 2(手冊要截新畫面)。
要裝一台新的 Agent,裝機的人需要兩個檔:安裝包 guidant-agent-compose-<版>.tar.gz 和同名 .sha256。現在這兩個檔只有原廠手上有,每次都靠我們跑 scripts/r2_presign.py 產 24 小時連結寄過去(FR-189 給 190 就是這樣)。
做完之後:管理員在主系統按一下,就拿到兩條下載連結;自己按下載也行,複製貼給機房同事用 curl -O 也行。連結預設 30 分鐘有效,時限可以在更新中心改。
<GUIDANT_UPGRADE_DIR>/agent-bundles/<版>/,三個檔:guidant-agent-compose-<版>.tar.gz、.tar.gz.sha256(內容 <sha256> <檔名>,sha256sum -c 直接吃)、.release.json(簽好章的清單)。路徑算法 app/product_update/service/package_downloader.py:87-99(agent_package_paths),版號格式不對會丟 ValueError。GET /agents/update/bundle(api/remote_agent/routes/agent_update_bundle_route.py)只給 Agent 容器用通行證拿;人拿不到。它用 send_file(..., conditional=True) 做續傳、回 X-Bundle-Sha256 等標頭,供檔邏輯可照抄。AgentReleaseService.current_bundle()/bundle(version)(agent_release_service.py:127-146、165),回 AgentBundle(path, version, sha256, size, min_host_version)。更新中心 Agent 區 API GET /system/product-update/agents 回的 agent_release.bundle_version/bundle_ready 就是這份。common.authz 的 issue_signed_token(scope, resource_uid) 與 signed_token_required(scope, bind_uid_arg),實作在 jedi_iam.authz.signed_token;驗證時 verify_signed_token(token, scope, ttl) 的 ttl 是參數,預設 120 秒。既有用法:框架版本 OSCAL 下載(api/oscal/routes/framework/oscal_framework_version_route.py:108-157):登入打發放端點換 st,再開 ?st= 網址。system_configs,group PRODUCT_UPDATE(product_update_flow.py:43-46),讀寫走 infra/system_config/system_config_root_reader.py 的 read_root_config_value/write_root_config_value。/v1/download-url 預簽網址寫死 15 分鐘(update-center src/releases.ts:13),而且客戶主機多半沒外網,不適合當給人的連結。src/components/remote-agent/AgentEnrollTokenCreateDialog.vue(120 行)建完顯示 token 與主系統端點、各有複製鈕,複製直接呼叫 navigator.clipboard,http+IP 下會是 undefined。src/components/common/ProtectedDownloadDialog.vue:30-56 已有 execCommand('copy') 退路寫法。更新中心 Agent 區 src/views/product-update/AgentRolloutPanel.vue(499 行)有「預先下載」次要鈕(第 364 行附近動作列)。簽票下載工具 src/utils/signedDownloadUtil.js。agent_release_service.py 目前被 CM-3728、CM-3711 兩個工作區同時改動,本案不碰它,只呼叫。| # | 決策 | 理由 | 排除的做法 |
|---|---|---|---|
| D1 | 包從主系統本機 agent-bundles/ 供檔 |
檔已經在那裡且驗過章;客戶內網不需外網;與 Agent 拉包同一份檔 | 給更新站 R2 預簽連結:寫死 15 分鐘、要外網、改時限得動 update-center |
| D2 | 一條下載路徑、兩個入口:更新中心 Agent 區「取得安裝包」鈕開對話框;註冊 token 對話框建完 token 後下方內嵌同一個元件 | 包住在更新中心,裝機的人在金鑰頁;不做兩套 | 只放一邊:裝機流程得跳頁 |
| D3 | 連結=簽章票:scope agent-bundle-download,resource_uid = <版>:<tarball|sha256>,一次發兩枚 |
重用既有軸⑤機制,不另立 token 表;綁死版號與檔種,拿到票只能下那一個檔 | 自建一次性連結表:多一張表、多一套清理 |
| D4 | 時限預設 30 分鐘,存 PRODUCT_UPDATE.agent_bundle_link_ttl_minutes(ROOT system_configs),範圍 1~1440;驗票時以當下設定值為 ttl |
「可調參數」用既有設定存法;一個數字欄位,不做下拉(下拉要進 system_menus,沒必要) | 寫死 30 分;或放安全政策頁(跟更新中心無關的頁不好找) |
| D5 | 產生連結要 system.product-update.read;改時限要 system.product-update.update |
下載不改系統狀態,讀權限夠;改設定是寫 | 一律要 update:看得到版號的人卻拿不到包 |
| D6 | 供檔端點 GET /system/product-update/agents/bundle/<version>/<kind>?st=,kind ∈ tarball/sha256;只掛 signed_token_required,不要 JWT |
純瀏覽器原生 GET 與 curl 都帶不了 bearer | 雙軌 signed_token_or_jwt:這端點沒有 axios 呼叫方,雙軌多一條沒人走的路 |
| D7 | 絕對網址由 FE 以 window.location.origin 組;BE 只回相對路徑、st、expires_at、sha256、大小 |
主系統不知道自己對外網址(註冊端點那條是 Agent 用的 cloud_endpoint,不一定等於管理員瀏覽器看到的) |
BE 組絕對網址:要新 env var,違反「新增 env 前先查既有」且多一個會填錯的值 |
| D8 | 產生連結寫稽核:reuse 既有事件碼 AGENT_BUNDLE_DOWNLOAD(6251)、action 字串 AGENT_BUNDLE_LINK_ISSUED、欄位 kind=link_issued(誰、哪版、時限);不新增事件碼,因為 FR-188 M-1/SEC-6 同時在改 event_code.py;供檔成功不另寫(檔不含祕密) |
有人拿連結出去要查得到是誰產的 | 供檔也寫:curl 續傳每段一筆,噪音 |
| D9 | 沒有包時對話框顯示「主系統尚未下載 Agent X,請先到更新中心預先下載」+跳轉連結 | 下載包要外網,是主系統連、不是人連;既有「預先下載」已經做好 | 對話框內直接觸發下載:又一條觸發下載的路徑 |
| D10 | FE 複製鈕抽共用 src/utils/clipboardUtil.js(navigator.clipboard → execCommand 退路),ProtectedDownloadDialog 與 AgentEnrollTokenCreateDialog 一起換用 |
http+IP 開站複製鈕按了沒反應(FR-169 踩過);兩處既有寫法不一致 | 各自再寫一次 |
| D11 | 擋連點與灌下載:FE 下載鈕按後停用 10 秒並先探測(HEAD/Range 0-0)再開;BE 同一枚票同時最多 2 條串流、全站人工下載同時最多 3 條(Redis 計數,call_on_close 釋放,超限 429+Retry-After);發票每人每分鐘 10 次。上限值存同一 system_configs group,不開畫面 |
一次下載 1.7 GB 佔住一條 gunicorn 執行緒(預設共 64 條整站共用);瀏覽器下載默默開始,使用者會重按;curl 可在時限內無限重下。限「同時幾條」不限「總次數」,續傳不受影響 | 前門 nginx limit_conn:/api/1.0 是單一 location 會連 Agent 拉包一起限;nginx X-Accel 直送:要把包目錄掛進前門容器,是基礎建設改動,頻寬真被吃滿再議 |
POST /system/product-update/agents/bundle-links(讀權限)Request:{"version": "1.7.4"}(可省,省略=agent_release.bundle_version)。
Response data:
{
"version": "1.7.4", "size": 1834567890, "sha256": "b771e3…8a4716",
"expires_at": "2026-11-09T06:35:00+00:00", "ttl_minutes": 30,
"files": [
{"kind": "tarball", "name": "guidant-agent-compose-1.7.4.tar.gz", "path": "/api/system/product-update/agents/bundle/1.7.4/tarball?st=…"},
{"kind": "sha256", "name": "guidant-agent-compose-1.7.4.tar.gz.sha256", "path": "/api/system/product-update/agents/bundle/1.7.4/sha256?st=…"}
]
}錯誤:版號格式不對 400(沿用 package_downloader 的 ValueError);本機沒這版 404 PRODUCT_UPDATE_404005。
GET /system/product-update/agents/bundle/<version>/<kind>?st=(只驗票)signed_token_required(scope="agent-bundle-download"),驗完再比 payload 的 resource_uid == f"{version}:{kind}",不符 401。ttl 取 D4 設定值換成秒。send_file(conditional=True, as_attachment=True),標頭 X-Bundle-Version/X-Bundle-Sha256/X-Bundle-Size 同既有 Agent 端點。kind 不在兩值內 404。
GET /system/product-update/agents/bundle-link-settings(讀權限)回 {"ttl_minutes": 30};PUT 同路徑 {"ttl_minutes": 1..1440}(更新權限),範圍外 400 PRODUCT_UPDATE_400013。獨立端點而不併進 agents overview,是為了不碰 FR-188 SEC-1 正在改的 product_update_app_service.py。
AgentBundleLinks.vue:載入時打 agents 拿 bundle_version;有包→顯示版號、大小、sha256(可複製)、兩列檔名各帶〔下載〕〔複製連結〕、一行「連結 30 分鐘內有效,到 HH:MM」,連結在第一次顯示時就打 bundle-links 產好;過期後列變灰並出現「重新產生」。沒包→D9 提示。hint-bar。FR-188(母卡 CM-3728)同時在跑。本案不碰 product_update_route.py、product_update_app_service.py(SEC-1)、event_code.py、error_code.py(M-1、SEC-6)、_agent_update.py(SEC-5)、agent_release_service.py(SEC-6);BE 三端點全在新檔。FE 卡等 FR-188 F-1(CM-3741)合回才動 AgentRolloutPanel.vue 與 api.js。
agent_release_service.py(兩個工作區在動)。<版>:<kind>,過期即失效;供檔只在 agent-bundles/ 底下用驗過格式的版號取檔,不接受路徑。