FR-191 Agent 安裝包自助下載

管理員在主系統就能拿到 Agent 安裝包與 .sha256 兩個檔的下載連結,不必再向原廠要。連結限時(預設 30 分鐘,可調)、可複製給裝機的人。

設計 v1.0(定稿):Agent 安裝包自助下載

📑 本頁是 v1.0(定稿,已被推翻)。 2026-11-10 決策者裁改為 R2 直連、時限改由原廠在更新站後台設定,見 設計 v2.1(現行;v2.0 為中間版)。本頁內容保留定稿原樣,不回頭改。

分工概述(30 秒版)

整案一句話:主系統更新中心已經會把 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(手冊要截新畫面)。

1. 這案在做什麼(白話)

要裝一台新的 Agent,裝機的人需要兩個檔:安裝包 guidant-agent-compose-<版>.tar.gz 和同名 .sha256。現在這兩個檔只有原廠手上有,每次都靠我們跑 scripts/r2_presign.py 產 24 小時連結寄過去(FR-189 給 190 就是這樣)。

做完之後:管理員在主系統按一下,就拿到兩條下載連結;自己按下載也行,複製貼給機房同事用 curl -O 也行。連結預設 30 分鐘有效,時限可以在更新中心改。

2. 現況(首腦查過的事實)

  • 主系統更新中心 Agent 區按過「預先下載」或推過更新後,包就在 <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= 網址。
  • 更新中心的系統層設定存 ROOT 租戶 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),而且客戶主機多半沒外網,不適合當給人的連結。
  • FE:註冊 token 對話框 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 兩個工作區同時改動,本案不碰它,只呼叫。

3. 設計決策

# 決策 理由 排除的做法
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 直送:要把包目錄掛進前門容器,是基礎建設改動,頻寬真被吃滿再議

4. API

4.1 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。

4.2 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。

4.3 時限設定

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。

5. 畫面

  • 更新中心 Agent 區:動作列加次要鈕「取得安裝包」(有包才亮;沒包 tooltip 說先預先下載)。開 Dialog,內容=共用元件。Agent 區說明旁加「下載連結有效時間(分鐘)」數字欄位,有更新權限才可改。
  • 共用元件 AgentBundleLinks.vue:載入時打 agents 拿 bundle_version;有包→顯示版號、大小、sha256(可複製)、兩列檔名各帶〔下載〕〔複製連結〕、一行「連結 30 分鐘內有效,到 HH:MM」,連結在第一次顯示時就打 bundle-links 產好;過期後列變灰並出現「重新產生」。沒包→D9 提示。
  • 註冊 token 對話框:建完 token 的那一頁,在端點欄位下方加區塊「安裝包」,直接放共用元件。
  • 禁左側粗色邊框;提示用既有 hint-bar。

6. 與 FR-188 平行的避撞

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。

7. 不做

  • 不給 R2 連結;不另存包;不做時限下拉;供檔不寫稽核;不做「對話框內觸發下載」;不動前門 nginx、不做 X-Accel 直送。
  • 不改 agent_release_service.py(兩個工作區在動)。

8. 安全

  • 票綁 scope+<版>:<kind>,過期即失效;供檔只在 agent-bundles/ 底下用驗過格式的版號取檔,不接受路徑。
  • 包不含任何祕密(憑證、金鑰都是裝機後產生);外洩代價是「別人拿到產品安裝包」,30 分鐘可接受。
  • 產生連結留稽核(D8)。