FR-066 · Agent Native Installer — 設計文件 · 2026-08-18
把檢測 Agent(evidence-agent)從 docker 三容器出貨,改為 Nuitka 編譯+外部工具全包的一顆 tarball,install.sh 一鍵安裝為 Linux systemd 服務。本文件承接同資料夾需求討論稿的 D1–D9 定案,轉為正式設計:build 管線、tarball 打包與工具 bundle、install.sh 與 systemd、驗收與文件,共 4 子需求 × 11 子任務。
狀態:設計定案・待實作(D1–D9 已拍板;Q1–Q4 為實作期查證項)|建立日期:2026-08-18 前作:FR-063 Nuitka 落地版打包 design(build 管線/私房 wheel/resource_path 慣例)/FR-064 防竄改偵測 design(manifest 簽章泛用層)/FR-065 落地版 Installer design(install.sh 七步結構/維運子命令) 討論稿(唯一內容真相源,D1–D9 拍板脈絡與被排除方案全文):
discussion.html
這節不需要任何技術背景就讀得懂。工程細節從下一節起。
這案在做什麼:檢測 Agent 是我們派駐在客戶機房、替我們去做掃描的小程式。它原本包在 docker 容器裡出貨(容器=一個跟外界隔開的獨立小房間)。但 Agent 的工作就是量測它所在 的那台機器,隔著這層隔離會讀不到硬體身分、或每次重啟讀到的值都不一樣——這件事已經 真的出過事(系統認不得同一台機器)。本案把它改成直接裝進機器裡,就像同業的掃描 Agent (Nessus、Qualys、Wazuh)一貫的做法。
為什麼值得做(三個實際好處):
順帶做掉的:客戶端少一個資料庫服務、少一個網路轉發服務要維運(都省成 Agent 自己承接), 以及防偽章——出貨前逐個檔案蓋章,有人動過包裡任何檔案,Agent 就拒絕啟動。
影響哪些畫面/操作:產品畫面完全不變。這是交付形態的改造,客戶端的變化是「裝的 方式不同了」;雲端後台看到的 Agent 清單、派工、報告全部照舊。唯一要注意的是:改了機器 身分的算法,既有已開通的 Agent 指紋會變、需要重新開通一次(決策 D11,當時存量最少、 最便宜)。
拍板了什麼(決策 D1–D19 的白話版,完整理由見 §3):
| 題目 | 定案 |
|---|---|
| 程式怎麼給 | 編譯成機器碼,客戶看不到我們的原始碼 |
| 工具怎麼帶 | 全部塞進同一顆包,不做「核心包+工具包」分層——分開給就有漏帶的風險,封閉機房現場出事沒網路可補救 |
| 本地資料怎麼存 | 從「另起一個資料庫服務」縮成單一檔案(它只服務一張表,養一個服務不值得) |
| 防竄改要不要做 | 要——Agent 以最高權限跑在客戶機器上、還握著加密憑證,是整條信任鏈最容易被摸的一端 |
| 怎麼升級 | 新版裝到旁邊、切換指向即完成;出錯切回去就是回滾 |
| Windows 版 | 凍結(D19,PM 拍板)。兩條路線實測都判死(Docker Desktop 使用者一登出整組停掉;另一條方案閒置一分鐘就被系統凍結)。現階段要掃 Windows 主機的解法是:Linux Agent 走遠端連線去掃,這是既有功能 |
| 出貨哪一版 | docker compose 版(D16)。原生安裝版開發完成但暫不出貨,等真的出現封閉網路/不能裝 docker 的客戶再解凍 |
現在到哪了:母卡 CM-1284 已於 2026-08-23 收案,compose 版已隨產品出貨。 各子需求「怎麼親手驗它做完了」見下一節的分工概述表。
| 日期 | 變更 | 對應 |
|---|---|---|
| 2026-08-18 | 初版設計定案:D1–D9 定案轉入、現況接入點盤點、4 子需求 × 11 子任務拆分、Q1–Q4 歸屬標注、端到端驗收 | FR-066 |
| 2026-08-18 | 補 Windows 可行性初判入 D8(附錄:分層難度表+工具鏈盤點+SmartScreen 風險,T-4.3 起點素材) | FR-066 D8 |
| 2026-08-20 | §2 分工概述升級:加「完成怎麼判定(決策者檢查法)」欄+非工程讀者導讀(決策者反饋:原版太工程師、不知道每階段怎麼檢查) | FR-066 |
| 2026-08-20 | §5.2.3 落地調整:manifest 由單份改雙層(agent 層給啟動 gate、bundle 層給交付完整性),圖 2 同步更新;T-2.3(CM-1294)實作時發現單份與啟動 gate 掃描錨點衝突 | FR-066.2 D6 |
| 2026-08-20 | 新增 FR-066.5 EL(Rocky/RHEL 9)支援:D10 決策+T-5.1~5.3 拆分(方案 A 低 glibc 容器編譯,單一 tarball 不變) | FR-066.5 |
| 2026-08-21 | 新增 FR-066.6 docker compose 形態+SeaweedFS 內建儲存:D11 指紋二源化(全域)/D12 雙形態矩陣/D13 沿用 FR-039 既有 Agent 儲存鏈;T-6.1~6.4 拆分。另記 FR-066.5 PM 決議暫緩 | FR-066.6 |
| 2026-08-21 | T-6.1 落地:D11 補「撞號守門的豁免條件=憑 uid 認出」與 --upgrade 須更新維運指令副本兩則實作教訓(皆為實測抓到的錯,見 D11 註) |
FR-066.6 D11 |
| 2026-08-21 | FR-066.6 增 T-6.5 configure-storage 子命令(D14 裁示:本地 CLI 先做、web 下發另開評估卡給 PM) | FR-066.6 |
| 2026-08-22 | FR-066.6 增 T-6.6 compose 版互動安裝程式(D15:交付形態對齊主產品 installer 模式——問答引導,客戶不碰 docker 指令;T-6.3 產物為其積木) | FR-066.6 |
| 2026-08-22 | 增 T-6.7 恢復系統內建儲存(出廠快照+configure-storage 選項;主產品對應功能另卡處理) | FR-066.6 |
| 2026-08-22 | D16 出貨形態收斂:原生 tarball 暫不出貨、現階段一律 compose 版(D12 出貨面凍結,開發成果保留);T-4.1 隨之暫緩、T-4.2 範圍改 compose 為主。另收 CM-1335 裁示:compose 交付包廢除 install/upgrade 兩包制、收斂一顆通吃 | FR-066 D16 |
| 2026-08-22 | 新增 FR-066.7 Windows(Docker Desktop)支援:D17 裁示(docker 路線先行、原生版不討論;Docker Desktop 承載;install.ps1+薄指引;setup.exe 記 PM 反饋觸發升級項);T-7.1~7.3 拆分。另記 D16 補充:手冊(T-4.2)解凍條件=FR-065 產品本體 Windows 與本案 .7 皆收口後兩本一起寫 | FR-066.7 |
| 2026-08-23 | 新增 FR-066.8 原生 Windows 版:D18 裁示(Docker Desktop 判死於出貨〔T-7.1 實測登出全滅 P0〕、B〔WSL+engine 常駐〕否決、原生服務為 Windows 唯一出貨路線;code signing 階段策略=現階段不買憑證走客戶 IT 白名單、第一個正式 Windows 客戶在望時買 OV、救火買 EV);T-8.1~8.5 拆分。FR-066.7 收斂為 demo 限定(T-7.2/T-7.3 降級,見 D18 註) | FR-066.8 |
| 2026-08-23 | D19 Windows 線全面凍結(PM 拍板):先以 Linux 為主,FR-065/FR-066 不再探討 Windows 安裝,客戶真有需求再開卡。T-7.4(CM-1346)B 案生死驗證判死收錄(WSL VM 閒置 60 秒凍結 userspace、四種挽救全滅、外部 watchdog=每分鐘重開機非常駐);.7/.8 全卡樹暫緩、探路資產保留。T-4.2 手冊解凍改為 Linux 版立即撰寫(主產品對齊卡 CM-1347+agent 手冊 CM-1299) | FR-066 D19 |
檢測 Agent(evidence-agent,獨立 repo ~/Projects/Billows/Audit-Manager/evidence-agent,v0.2.27,Flask + dependency-injector)目前唯一交付形式是 docker image(python:3.11-slim)+三容器 compose(agent 本體+agent-db postgres:16+nginx mTLS sidecar),出貨走 docker save / docker load。
但 agent 的本職是量測宿主機——機器指紋要讀 /sys/class/dmi/id/product_uuid、/etc/machine-id、主網卡 MAC,這些在容器裡讀不到或會漂移(已發生 Debian base image /etc/machine-id 空檔退回隨機 MAC 的指紋漂移事故),現行靠 deploy/collect-host-id.sh workaround 從宿主抽值塞進容器硬撐。業界同類產品(Nessus / Qualys / Wazuh / Elastic / Datadog Agent)全部以原生套件(deb/rpm/tarball)+ systemd 服務交付——貼宿主量測的產品,原生安裝就是行規。
FR-065(產品落地 installer)期間決策者裁定:agent 安裝包另起新 FR、非 docker,即本案。
install.sh 一鍵安裝為 Linux systemd 服務| 前作 | 可直接複用 |
|---|---|
| FR-063 Nuitka 落地版打包 | scripts/build/build_release.sh build 管線、dependency-injector 私房 wheel rebuild_wheels.sh、resource_path() 慣例、DI 靜態清單處理手法 |
| FR-064 防竄改偵測 | 簽章泛用層 sign_payload / verify_payload(帶 type 欄)、gen_integrity_manifest.py、sign_manifest.sh、License Center 簽發 |
| FR-065 產品落地 installer | install.sh 七步結構、--check-only、安裝落檔慣例、digest 驗證、維運子命令(status / logs / uninstall / fingerprint) |
客戶側從解 tarball 到心跳上線,mTLS(雙向 TLS 認證)全自動、客戶零 openssl 操作:
%%{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'}}}%%
sequenceDiagram
participant OP as 客戶操作者
participant IS as install.sh
participant SD as systemd
participant AG as guidant-agent 服務
participant CL as 雲端產品(FR-065 已含 PKI)
OP->>IS: 解 tarball、執行 install.sh
IS->>IS: 七步:前置檢查→驗 manifest 簽章→落檔 versions/<ver>→裝 bundle 工具→建 symlink→裝 systemd unit→啟動
IS->>OP: 互動問答三題(雲端位址/註冊 token/本機對外位址)
IS->>IS: 生成 /etc/guidant-agent/agent.env
IS->>SD: systemctl enable --now guidant-agent
SD->>AG: 啟動(啟動時驗 manifest 簽章)
AG->>AG: 直讀宿主指紋(product_uuid / machine-id / MAC)
AG->>CL: enroll:產 keypair+CSR(SAN=本機位址)+token 註冊
CL-->>AG: CA 簽回 client 憑證+8443 server 憑證
AG->>AG: 憑證存 cert_dir,gunicorn/ssl 自起 8443 資料面
loop 心跳
AG->>CL: mTLS 心跳(404 → 自動重註冊)
end
本節寫給決策者與非工程讀者:每個大項在做什麼、產出是什麼、以及你要怎麼親手判定它完成了。工程細節從 §3 起。
整案一句話:把檢測 Agent 從「docker 三容器出貨」改成「Nuitka 編譯+外部工具全包的一顆 tarball,install.sh 一鍵裝成 Linux systemd 服務」——宿主指紋直讀、SQLite 取代 PG、agent 自起 TLS 取代 nginx sidecar、build 期 manifest 簽章+啟動驗章。
| 子需求 | 做什麼(白話) | 產出 | 完成怎麼判定(決策者檢查法) |
|---|---|---|---|
| FR-066.1 Nuitka build 管線與程式改造 | 把 agent 的 Python 程式編成機器碼,順手把「只有在 docker 裡才成立」的地方改掉:資料庫從要另起一個 PG 容器改成單檔 SQLite、版號不再讀 pyproject.toml、檔案定位改編譯相容寫法、指紋直接讀宿主 | agent 版 build_release.sh+可跑的 Nuitka standalone 產物 |
在 build 機(188)跑 smoke_agent_release.sh 看 5/5 全綠——產物翻不到 .py 原始碼、服務起得來、健康檢查有回應。✅ 已達成(2026-08-20,決策者親跑) |
| FR-066.2 tarball 打包與工具 bundle | 把掃描工具(CINC / sonar-scanner / LibreOffice / 中文字型…)全部塞進同一個壓縮包,客戶不用連網也裝得起來;出貨前逐檔算 hash 簽章,啟動時驗章防竄改 | guidant-agent-<ver>.tar.gz(含 manifest.sig)+build_bundle 打包腳本 |
一鍵產出 guidant-agent-<ver>.tar.gz+校驗碼、解包目錄結構與 §5.2.3 圖一致;且親手改包內任一個檔案 → agent 拒絕啟動(防偽章生效)、斷網容器內工具裝得起 |
| FR-066.3 install.sh 與 systemd | 客戶拿到 tarball 後的一鍵安裝腳本:問三個問題就裝成開機自起的系統服務,升級換版切個 symlink、出錯切回去就回滾,還附 status / logs / uninstall 等維運子命令 | install.sh(安裝/升級/回滾/維運)+systemd unit |
找一台沒裝過的乾淨 Linux:解包 → sudo ./install.sh 回答三題 → systemctl status guidant-agent 顯示 active;升一版再退回,服務都活著 |
| FR-066.4 驗收與文件 | 找一台乾淨 Linux 機從頭裝一遍、斷網環境也裝一遍,確認真的能跑檢測任務;寫客戶安裝手冊;Windows 版可不可行寫成評估文件 | 真機驗收紀錄+客戶手冊+Windows 評估文件 | 斷網真機從安裝到跑完一個檢測任務、報告回到雲端全程通;手冊給沒參與過的人照著做能裝成功;Windows 評估文件能支撐「做/不做」的商務判斷 |
| FR-066.5 EL(Rocky/RHEL 9)生態支援(暫緩) | 讓同一顆安裝包也能在政府金融常用的 Rocky Linux / RHEL 9 上裝起來、跑得動:agent 編譯搬進低版本函式庫容器(debian:11)做,一份產物通吃三家族;安裝腳本認得 EL 的 SELinux/firewalld 並給指引(只檢測+輸出指令,不自動改客戶機);最後在真的 Rocky 9 機器驗一輪 | 容器化編譯的 build 管線+EL 適配的 install.sh+EL9 真機驗收紀錄(tarball 仍是同一顆,不分家) | 找一台 Rocky Linux 9 機器(SELinux 開啟狀態)照同一份 tarball 裝:sudo ./install.sh 裝完 systemctl status guidant-agent 顯示 active、雲端後台看得到 agent 上線、派一單 CINC 檢測任務跑完報告回到雲端 |
| FR-066.6 docker compose 形態+SeaweedFS 內建儲存 | 出一版 docker compose 形態的 agent 安裝包:同一顆編譯產物裝進容器、SeaweedFS 證據儲存服務一起出貨,agent 收到的證據預設存進 SeaweedFS;客戶自有 SeaweedFS/MinIO 也能設定指過去;機器指紋改用兩個掛載可得的來源 | compose 交付包+agent seaweedfs 儲存支援+指紋二源化+configure-storage 設定命令+compose 版互動安裝程式 | 找一台有 docker 的 Linux 機:docker compose up -d 起來後雲端後台看得到 agent 上線;到儲存設備設定頁選「使用 Agent 服務」存檔,上傳一個證據檔,能在 SeaweedFS 的管理網頁看到那個檔、產品端能正常預覽下載 |
| FR-066.7 Windows(Docker Desktop)支援 | 讓 Windows 客戶也能裝檢測 Agent——不重編程式,客戶在 Windows 上裝 Docker Desktop(圖形介面的容器執行軟體),拿同一顆 compose 安裝包,用 Windows 版互動安裝程式(install.ps1,PowerShell 腳本)問答三題裝起來;網路連入、機器指紋在 Windows 環境的差異由安裝程式與指引文件處理 | install.ps1(Windows 版互動安裝程式)+Windows 安裝前置指引文件+160 真機驗收紀錄 | 找一台裝好 Docker Desktop 的 Windows 機(192.168.50.160):解開同一顆安裝包 → 跑 install.ps1 回答三題 → Docker Desktop 看到兩個容器綠燈 → 雲端後台看得到 agent 上線 → 派一單檢測任務跑完報告回雲端;重置 Docker Desktop 後照指引重新開通能接回 |
| FR-066.8 原生 Windows 版 | Windows 正式出貨版——agent 與內建儲存(SeaweedFS)編譯/打包成 Windows 原生程式,裝成兩個開機自起的 Windows 系統服務,不經 docker;問答式安裝程式(install.ps1)、升級回滾、防竄改簽章對齊 Linux 版。機器指紋改讀 Windows 本機識別碼(重灌才變,不受容器/登出影響) | Windows build 管線+agent.exe/weed.exe 雙服務+install.ps1+乾淨機驗收紀錄 | 找一台沒裝過的 Windows 機:跑 install.ps1 回答三題 → 服務清單看到兩個 Guidant 服務「執行中」→ 登出、重開機後服務自動回來、雲端 agent 仍在線(這是 docker 版做不到的關鍵差異)→ 派一單檢測任務跑完報告回雲端 → 改包內任一檔案後服務拒絕啟動 |
依賴一句:.1 → .2 → .3 串行(build 產物是打包的輸入、打包產物是安裝的輸入),.4 收口;與 FR-063/064/065 不同,本案各階段產物互為輸入,無平行空間。.6 依賴 .1 編譯產物,與 .4 收口平行;.5 暫緩最後做。
九項全部已由決策者拍板;每項含定案理由與被排除方案(全文脈絡見討論稿 §2)。
| # | 決策 | 定案與理由 | 被排除方案 |
|---|---|---|---|
| D1 | 打包形式 | Nuitka standalone 編譯 → tarball → systemd 服務。standalone 同為機器碼編譯,自家 code 不可見,保護強度與 FR-063 BE 打包相同;第三方套件以原始 .py 附帶(EXCLUDE 名單機制),皆為公開開源套件、不構成洩漏 |
onefile 模式:①啟動先解壓暫存目錄有延遲;②FR-064 逐檔 manifest 驗章在單一自解壓檔上難做;③外部工具反正要目錄形式,onefile 省不了目錄 |
| D2 | 本地 DB | agent-db(postgres:16)退役,換 SQLite。現況 agent-db 只服務 jedi-file-upload 的 upload_files 一張表(core/app_factory.py:37 註解明寫;create(checkfirst=True) 自建、無 migration 體系);單表、單進程寫入、無並發、無 RLS,正是 SQLite 甜蜜點;init_db() 吃 SQLAlchemy URL,換 sqlite:/// 即可。前置守門=Q2(PG 方言掃描) |
①要求宿主裝 PG:加重客戶前置需求,違背一鍵安裝;②bundle PG 進 tarball:包更肥、多一個服務要維運(起停/備份/密碼),為一張表不值得 |
| D3 | 外部工具交付 | 全包一顆 tarball,install.sh 自動安裝,離線完全自足。封閉網路情境(FR-059.3 已驗證為既定支援情境)下,全包才不存在漏帶/版本錯配的現場事故;體積 ~1GB+ 已明示接受。bundle 清單見 §5.2 | 方案 B「核心包+工具附加包分層」:多檔交付有漏帶風險,封閉網路現場出事無網路可補救;方案 C「宿主前置需求清單」:把安裝品質丟給客戶,且客戶自裝 InSpec 會踩 CINC 授權紅線 |
| D4 | mTLS 承接 | agent 以 gunicorn/ssl 自行承接 8443 資料面 TLS,nginx sidecar 退役。mTLS 驗證邏輯本來就在 agent code 內(core/data_plane_auth.py),nginx 只是轉發層,砍掉不損失任何安全能力;8443 server 憑證由 enrollment(agent 向雲端自我註冊)時 CA 簽出(CSR SAN=base_url host),客戶不需自備憑證 |
—(保留 nginx 意味多一個要 bundle 與維運的服務,無對價) |
| D5 | 設定模型 | /etc/guidant-agent/agent.env 作為 systemd EnvironmentFile。沿用現有全環境變數設計(config/config.py,agent 無設定檔)——零 code 改動;install.sh 互動問答三題(雲端位址/註冊 token/本機對外位址)生成該檔。客戶側 mTLS 全自動:自我註冊流程已存在(core/enroll.py:產 keypair+CSR → token 註冊 → 憑證簽回存 cert_dir → 心跳走 mTLS → 404 自動重註冊),客戶零 openssl 操作;雲端側 PKI 由 FR-065 產品 install.sh 已涵蓋(install.sh:1936 AGENT_CERT_DIR) |
—(引入新設定檔格式=無謂 code 改動) |
| D6 | 簽章/防竄改 | 本 FR 一起做,瘦身版:build 期 manifest(逐檔 hash 清單)簽章+啟動時驗章。複用 FR-064 泛用層:簽章加一個 agent_manifest type、gen_integrity_manifest.py 改常數、sign_manifest.sh 近零改,複用成本僅 1–2 張小卡。值得做的理由:agent 以 root 服務跑在客戶機、握 mTLS 憑證,是整條信任鏈最容易被摸的一端 |
FR-064 runtime 全家桶(抽查/lockdown/unlock token):對 agent 過重,不做 |
| D7 | 升級/回滾 | /opt/guidant-agent/versions/<ver>/ + current symlink 切換。升級=解新版本目錄、切 symlink、重啟;回滾=切回舊 symlink。SQLite 檔與 mTLS 憑證放資料目錄、不隨版本目錄走;agent 無 DB migration 負擔(D2 單表自建),不需 FR-065 那套 pg_dump 備份模型,升級模型比產品端輕得多 |
—(複製 FR-065 備份模型=為不存在的 migration 負擔付維運成本) |
| D8 | Windows | 只出評估文件+前提清單,不實作不承諾(比照 FR-065 D9;FR-065 對應工作 T-4.3 於 2026-08-18 裁定跳過、評估文件未產出——agent 版評估文件從零寫,不假設有前稿可抄)。評估三題:①指紋來源(WMI / registry)②服務形式(Windows Service / NSSM)③工具鏈可用性(CINC / sonar-scanner 等在 Windows 的形態)。脫離 docker 後 Docker Desktop 商業授權疑慮消失;但 machine-id 等指紋問題在 Windows 依然存在,是評估重點 | 實作 Windows 版:需求未定案,不投 |
| D9 | 指紋 | 只做 Linux 原生直讀,不預抽 OS 抽象層。直讀 /sys/class/dmi/id/product_uuid、/etc/machine-id、主網卡 MAC——core/fingerprint.py 已有伏筆註記(「原生封裝後直接讀系統路徑,模型不變」);deploy/collect-host-id.sh workaround 隨之廢除 |
先抽 OS 抽象層:Windows 需求未定案(D8 只出評估文件),為未定需求預先抽象是過度設計;等 Windows 真要做再抽,重構成本可控 |
| D10 | EL(Rocky/RHEL)支援方式 | 方案 A:agent 編譯搬進 debian:11(glibc 2.31)容器,與 syslibs 採集同一基準邏輯——低 glibc 編譯向上相容,一份產物通吃 Debian 11+/Ubuntu 20.04+/EL9;工具 bundle 層已於 T-2.1 三家族實測通過,故只補編譯層+安裝腳本 EL 適配+EL 真機驗收;firewalld/SELinux 只檢測與指引、不自動改 | 方案 B 雙 build lane 出兩顆 tarball:QA/出貨負擔雙倍、違背 D3 單包精神;方案 C 宣告不支援 EL:Rocky/RHEL 是政府金融常態需求,把需求推走 |
| D11 | 指紋二源化(全域) | device_uuid 由三源 sha256(product_uuid|machine-id|mac) 改二源 sha256(product_uuid|machine-id),全域生效(原生版一起改)。docker 形態下 MAC 量不到宿主(bridge 網路虛擬網卡),machine-id 與 product_uuid 可唯讀掛載解決;只改 docker 版會讓同機兩種裝法算出兩個身分,故全域統一。代價:既有已 enroll agent(123 真機+DEV 測試筆)指紋全變需重 enroll——現在存量最小最便宜。VM 模板複製撞號風險(machine-id 整批相同)改由 BE enroll 端撞號偵測守門(同指紋再註冊→拒絕並提示)承接,冗餘從演算法搬到檢核 |
①僅 docker 版二源:同機雙身分;②保留三源+collect-host-id.sh 人工抄 MAC:已被 D9 廢除的 workaround,會過期、靜默錯 |
D11 實作註(T-6.1 落地,2026-08-21)——兩則都是實測才現形、且都「看起來已經做對了」:
① 撞號守門的豁免條件必須是「憑 agent_uid 認出來的」,不是「resolve 到同一列」。 BE
_resolve_agent在 agent 沒帶 uid 時會退用指紋去重,於是複製出來的機器必然 resolve 到被撞的那一列、uid 自然相等——守門若比對resolved.uid就會判定「就是它 自己」而放行。首版即如此,實測(DEV 真打 register)回 HTTP 200 且原機那一列的 base_url 被複製品覆寫,正是本守門要防的情境。指紋 fallback 這條路徑就是複製品會走 的,拿它當「同一台」的證據等於把守門前提交給攻擊面本身。已改為_resolve_agent回傳(entity, matched_by_uid),只豁免前者。② 指紋演算法一改,
--upgrade必須一併更新guidant-agent-ctl。 該檔是安裝當下 複製出去的獨立腳本(讓客戶刪掉 2GB 安裝包後仍能用 status/logs),升級只換 /opt 下的 程式、不會動它。其fingerprint子命令是 agent 演算法的 shell 複刻,版本不一致 就印出另一個值——而那正是客戶要抄去雲端開通的那串。123 真機實撞:服務報二源值、舊 ctl 報三源值。原有的「與執行中服務對帳」機制有出聲(正是這樣抓到的),但它在服務沒 跑時(開通前,恰是 fingerprint 的主要使用時機)無從對帳,故不能當解法。共同教訓:凡是「同一套邏輯存在兩份實作」(Python↔︎shell 複刻、演算法↔︎守門豁免), 改一邊就要指名另一邊,且要對真端點/真機各跑一次——單元層 fixture 會繞過真實 解析路徑(本例即因 fixture 直餵
resolved=None而漏掉 ①)。 | D12 | 雙形態支援矩陣 | compose 版=Linux 有 docker 環境首選(含 SeaweedFS 全套);原生 tarball=封閉網路/無 docker/未來 Windows 路線。兩版共用同一顆 Nuitka 產物與同一套簽章,build_all 加 compose 打包出口,不開第二條產線 | —(雙產線=雙倍 QA/出貨負擔,違背單一產物精神) | | D13 | 產品端「使用 Agent 服務」 | =FR-039 既有功能,BE/FE 零新功能。雲端儲存設備設定頁本就有該選項(storage config 指 agent base_url/agent_uid → RemoteAgentAdapter → agent blob API → agent 依自身 STORAGE_TYPE 落地)。本案只做 agent 端:內建 SeaweedFS 預設接上+自有後端 env 設定+驗收既有鏈在新編譯 agent 上仍通 | —(在 BE/FE 另做新儲存選項=重複造輪子,鏈路早已存在) | | D14 | 自有儲存後端的設定介面 | 本地 CLI 先做(T-6.5):agent 加configure-storage維運子命令——secret 不離機、連通驗證當場做、量級小且是 web 下發的必要兜底(下發失敗/無雲端連線時的現場設定路徑)。web 設定下發開獨立評估卡給 PM 排優先度(前置資安題:客戶儲存憑證入雲端 DB 是否可接受) | 直接做 web 下發(三端成本+secret 治理題未決,不該擋住立即可用的 CLI) | | D15 | compose 版交付形態 | 配互動安裝程式(T-6.6),照主產品 FR-065 installer 同款模式:環境檢查→載 image→問答→生成設定→起服務→維運子命令。受眾是一線工程師/業務,不假設會 docker;SeaweedFS 憑證自動生成不讓客戶發明密碼 | 手填 .env+docker compose up(T-6.3 首版形態——僅適合熟 docker 者,不符落地版交付受眾) | | D16 | 出貨形態收斂(2026-08-22) | 原生 tarball 版暫不出貨——現階段對客戶出貨一律 compose 版。原生開發成果(.1~.3 全部產物與腳本)保留不廢,何時解凍等商務需求(封閉網路/無 docker 客戶出現時)。連動:T-4.1(原生乾淨機+斷網驗收)暫緩;T-4.2 手冊改 compose 為主、原生降附錄。同日另一裁示(CM-1335):compose 交付包廢除 install/upgrade 兩包制、收斂一顆通吃(兩包只差 82MB seaweedfs image,維護兩條路不值;客戶端以「本機已有該 image」為升級放行條件)。⚠️ 遺留:斷網封閉網路驗證至今無人跑過(D3 立案根基)——若 compose 版也出給封閉網路客戶,此驗證應轉移到 compose 版做,不隨原生凍結 | 雙形態同時出貨(D12 原案):原生版每出一版都要 QA 兩形態,現階段無原生需求客戶,QA 成本無對價 | | D17 | Windows 支援路線(2026-08-22) | docker(Docker Desktop)路線先行,原生 Windows 版暫不討論(T-4.3 評估為據)。承載選 Docker Desktop(決策者定案):安裝體驗是「裝一個 Windows 軟體」,客戶不碰 Linux 層;其商用授權(>250 人或年營收 >$10M 組織需付費訂閱)為客戶側前置條件,寫入指引與售前須知。指紋風險接受度(決策者裁示):WSL/Docker Desktop 重置後指紋漂移=可接受的維運事件,處理=log 記錄+「重置後重新開通」SOP,不做 host 值注入 workaround(維持 D9/D11 立場)。安裝程式出 install.ps1(PowerShell 互動版,與 D15「不假設客戶會 docker」同一受眾邏輯——也不假設會 Linux 終端);交付物=同一顆 onepack 包(一個位元組不改)+install.ps1+薄前置指引。setup.exe 安裝精靈記為 PM 反饋觸發的升級項(不做的理由:SmartScreen 攔無簽章 exe,消警告需買 OV/EV code signing 憑證——docker 路線本已繞開此成本,不為包裝形式請回來)⚠️ 2026-08-23 D18 修訂:Docker Desktop 降 demo/PoC 限定(T-7.1 實測登出全滅),出貨路線改 FR-066.8 原生版;T-7.2/T-7.3 降級為 demo 支援用途。 | ①WSL2+docker engine(免授權費但客戶要自管 WSL,違背交付受眾設定)②路線甲「客戶在 WSL 終端跑現有 install.sh」(零開發但要求客戶進 Linux 世界,與 D15 受眾邏輯矛盾)③首版就做 setup.exe(SmartScreen+憑證成本) | | D18 | Windows 出貨路線定案(2026-08-23) | 原生 Windows 服務版(FR-066.8)為 Windows 唯一出貨路線。依據:T-7.1 實測證實 Docker Desktop 登出後 engine 全滅、SYSTEM 排程與 com.docker.service 兩條無登入自起路徑皆失敗(probe §8,P0)——「常駐」與「綁登入 session」本質矛盾,Docker Desktop 降為 demo/PoC 限定(有人值守場景仍可用,T-7.1 已驗全鏈通)。B 案(WSL2+docker engine+排程器常駐)否決:常駐機制未驗、四層架構(Windows→排程器→WSL→docker→容器)查修成本高、指紋仍是 VM 值、產出對原生版零複用——demo 用不到(A 夠)、出貨不如 C,兩頭不沾。Code signing 階段策略(行規=B2B agent 類幾乎全簽,但按階段投入):現階段(Windows 客戶數 0)不買憑證——manifest 簽章(自有鑰)照做、SmartScreen 靠客戶 IT 白名單(GPO/AppLocker)指引承接,與客群(封閉網路、IT 管控)相容;第一個正式 Windows 客戶在望即買 OV(年費數百美金級,含數週申請+信譽爬升期,不可等簽約後);大客戶臨時需求買 EV(免爬升)。年費列為 Windows 版固定成本讓售前/PM 知情 | ①Docker Desktop 出貨(登出全滅 P0 無解)②B 案(見上)③現在就買憑證(為未驗證市場預付訂閱)④Azure Trusted Signing(台灣組織不可申請) | | D19 | Windows 線凍結(2026-08-23,PM 拍板) | FR-065/FR-066 不再探討 Windows 安裝,先以 Linux 為主;客戶真有需求再開卡。決策鏈收束:A(Docker Desktop)=demo/有人值守可用但出貨不可(T-7.1 登出全滅);B(WSL+engine 常駐)=T-7.4 實測判死——WSL VM 閒置約 60 秒凍結整個 distro userspace(SYSTEM 帳戶被硬拒、S4U 可跑但照凍、vmIdleTimeout=-1 無效、distro 內 anchor 連坐被凍、外部 watchdog 只能做到「每分鐘重開機」會腰斬檢測任務),死因=WSL VM 生命週期需要 Windows 側永不退出的進程握持,而該進程的正規形態就是 Windows 服務——推導出唯一活路 C(原生版);C 需第二個編譯出口(Windows build 機+MSVC),PM 裁示此投資等客戶訊號。凍結時保留資產:T-4.3 評估、T-7.1/T-7.4 兩份實測紀錄、FR-066.8 卡樹(CM-1340~1345 暫緩);解凍首步=build 機佈建。Windows 掃描需求的現行解答=Linux agent 走 CINC WinRM 遠端掃(既有功能) | ①續投 B 唯一殘存路線(WinSW 握 wsl.exe——工同 C 的 T-8.3、換到六層架構、對 C 零複用)②Hyper-V appliance(等同要求客戶出一台 Linux,非解法)③立即投 C(PM:build 產線投資等客戶訊號) |
性質:決策者 2026-08-18 口頭討論確認的初判,非定案——定案要等 T-4.3 正式查證。此節作為 T-4.3 評估文件的起點素材。
核心結論:本案技術路線(Nuitka+SQLite+paramiko+環境變數設定)每一項都恰好沒堵死 Windows 的路;原生路線在 Windows 反而比 docker 路線順(免 Docker Desktop 企業授權)。粗估 Windows 版工作量約 Linux 版 40–60%(源碼層幾乎共用)。
分層可行性表:
| 層 | Windows 對應 | 難度 |
|---|---|---|
| Nuitka 編譯 | 原生支援,編成 .exe+standalone 目錄(MSVC),同套 build 邏輯換 Windows build 機 | 低 |
| 常駐服務 | Windows Service:NSSM 包裝(業界標準,Wazuh 等同款)或 pywin32 原生 service | 低 |
| SQLite | 完全跨平台(D2 的額外紅利;PG 在 Windows 會很痛) | 零成本 |
| 機器指紋 | WMI Win32_ComputerSystemProduct.UUID+registry MachineGuid+MAC——即 D9 延後的抽象層,屆時再抽 |
中低 |
| mTLS/enrollment | 純 Python(cryptography/httpx),跨平台 | 零改 |
| 安裝腳本 | PowerShell 或 MSI/Inno Setup 重寫,流程邏輯(問三題→佈檔→裝服務)照搬 | 中 |
| 外部工具 bundle | 真正瓶頸所在,見下 | 高 |
工具鏈逐項:CINC Auditor 有 Windows msi ✅/sonar-scanner 有 windows-x64 zip(自帶 JRE)✅/git 有 portable 版 ✅/LibreOffice 有 Windows 版(離線佈署形式待查)🟡/xsltproc 無原生、可改用 Python lxml 取代(反而更乾淨)🟡/SSH 掃描走 paramiko 純 Python ✅。工具面無死路,但每項都要重做一次離線化整備。
新識別風險(Linux 版沒有的)
Windows SmartScreen/Defender 會攔沒有 code signing 憑證的陌生 exe——可能需要購買 Windows code signing 憑證,屬 T-4.3 必查項。
待 T-4.3 正式查證項:LibreOffice Windows 離線佈署形式、NSSM vs pywin32 原生 service 取捨、code signing 憑證需求與成本、Windows build 機規格。
evidence-agent repo 內每個會被本案觸動的元件,逐項列現況與本案動作:
| 元件 | 現況 | 本案動作 |
|---|---|---|
Dockerfile / rebuild.sh |
docker image build 入口 | 退役,換 agent 版 Nuitka build 腳本(.1) |
deploy/docker-compose.yml |
agent + agent-db + nginx 三服務編排 | 退役,換 systemd unit(.3) |
| agent-db(postgres:16) | 只服務 jedi-file-upload upload_files 一張表 |
→ SQLite(D2,.1) |
| nginx mTLS sidecar | 8443 TLS 終結+轉發 | 退役,agent 自起 TLS(D4,.3) |
deploy/collect-host-id.sh |
容器讀不到宿主指紋的 workaround | 廢除(D9,.1) |
config/config.py 版本上報 |
讀 pyproject.toml 取版號 |
Nuitka 後檔案不存在 → 改 version bake(build 期把版號寫死進程式,同 FR-063 D6 手法,.1) |
| connector 硬編工具路徑 | /usr/bin/* 寫死(image 內裝好) |
配置化或 install.sh 建 symlink 指向 bundle 工具(.2/.3) |
__file__ 定位三處 |
inspec.py:145 / nmap.py:101 / config.py:85 |
改 resource_path() 慣例(FR-063 同款,.1) |
assets/ 資料檔 |
nmap.xsl 等隨 repo | Nuitka --include-data-dir 帶入(.1) |
| dependency-injector | C extension,Nuitka 需特製 wheel | 複用 FR-063 私房 wheel(rebuild_wheels.sh,.1) |
| jedi-common / jedi-file-upload | 私服(Nexus)套件 | 編進 binary;build 機需可達 Nexus(.1) |
| DI wiring | 本來就是硬編靜態清單(無動態掃描) | 免改——FR-063 的 DI 掃描雷不會踩 |
| eventlet / WeasyPrint | agent 沒有這兩個依賴 | 免處理——FR-063 另兩大雷也不會踩 |
| 心跳排程 | threading.Thread(非 APScheduler) |
免改,Nuitka 相容 |
好消息:FR-063 的三大 Nuitka 雷(eventlet monkey patch / WeasyPrint 動態載入 / DI 動態掃描)agent 一個都不會踩
agent 沒有 eventlet、沒有 WeasyPrint,DI wiring 本來就是硬編靜態清單。.1 的風險面遠小於當初 BE 打包(冷 build 亦預期遠低於 BE 的 56 分鐘,見 Q3)。
白話:這階段做「讓 agent 能被編成機器碼、且編出來在裸機上跑得起來」的所有程式面工作——它是整案起點,後面的打包(.2)與安裝(.3)都以這裡的 build 產物為輸入。docker 時代靠容器環境撐著的假設(旁邊有一個 PG、版號讀得到 pyproject.toml、__file__ 指得到源碼樹、指紋由 collect-host-id.sh 餵進來)在這一棒全部改掉。
ON CONFLICT 等 PG 方言;有的話評估改寫成本再定落地細節。init_db() 的 SQLAlchemy URL 換 sqlite:///<資料目錄>/agent.db;upload_files 表沿用 create(checkfirst=True) 自建,無 migration 體系要搬。config/config.py 讀 pyproject.toml 取版號的邏輯,改為 build 期把版號 bake 進常數(FR-063 D6 同款手法)——Nuitka 產物內沒有 pyproject.toml。resource_path() 三處:inspec.py:145 / nmap.py:101 / config.py:85 的 __file__ 定位改 resource_path() 慣例(FR-063 同款),配合 --include-data-dir 帶入 assets/(nmap.xsl 等)。core/fingerprint.py 直讀 /sys/class/dmi/id/product_uuid、/etc/machine-id、主網卡 MAC;廢除 deploy/collect-host-id.sh 與其在部署流程的掛點。/usr/bin/* 硬編路徑改為可由環境變數/設定覆寫(預設值指向 install.sh 建立的 bundle 工具 symlink,見 .3)。scripts/build/build_release.sh 為藍本開 agent 版:dependency-injector 私房 wheel 直接複用 rebuild_wheels.sh;jedi-common / jedi-file-upload 從 Nexus 拉入編進 binary(build 機需可達 Nexus)。白話:這階段把「.1 編好的 agent」跟「它工作時要呼叫的所有外部工具」組成一顆客戶拿了就能離線安裝的壓縮包,並在出貨前蓋上防竄改簽章。沒有這一棒,客戶機上要自己裝 CINC、LibreOffice、中文字型……封閉網路根本裝不了;有了它,一顆 tarball 就是完整交付物。
| 工具 | 體積 | 備註 |
|---|---|---|
| CINC Auditor(Ruby omnibus,全家桶自足安裝形式) | ~275MB | ⚖️ 授權紅線:絕不可換成官方 InSpec 商業 binary |
| sonar-scanner(含 JRE) | ~147MB | amd64 專屬 |
| LibreOffice + fonts-noto-cjk | ~400MB+ | 檔案預覽轉 PDF;⚠️ 字型家族名是 Noto Sans CJK TC 不是 Noto Sans TC;離線化形式=Q1(自帶 deb 組 dpkg -i vs portable / AppImage,實測選定,判準:離線可裝、字型能一起落、體積可接受) |
xsltproc + assets/nmap.xsl |
小 | nmap 報告轉換 |
| git | 小 |
oscap-ssh 腳本)——確認殘留就不搬進 tarball,包更瘦。agent_manifest type;gen_integrity_manifest.py 改常數(掃描根目錄/輸出路徑)產逐檔 SHA-256 manifest;sign_manifest.sh 近零改,走 License Center 簽發。manifest.sig,逐檔比對;驗章失敗拒絕啟動(瘦身版:不做 runtime 抽查/lockdown)。agent/(Nuitka 產物)+tools/(bundle 工具)+install.sh+systemd/+manifest 簽章(雙層,見下)。guidant-agent-<ver>.tar.gz 並附 sha256 檔(交付完整性驗證,FR-065 digest 慣例同款)。scripts/build/build_agent_bundle.sh(打包六步)+smoke_agent_bundle.sh(六項交付驗證,含 gate 正反例突變測試)。🔄 落地調整(2026-08-20,T-2.3/CM-1294):manifest 由單份改雙層。 初版設計是骨架根一份 manifest 涵蓋全部;實作時發現它與啟動 gate 的掃描錨點衝突——gate(T-2.2)讀 binary 所在目錄(agent/)的 manifest.sig 並逐檔比對該樹,而工具樹安裝後散落 /opt/cinc-auditor、/opt/libreoffice25.8、/usr/share/fonts,不在版本目錄、gate 掃不到也不該掃(那些路徑不歸版本目錄管)。單份 manifest 的結果是:gate 在 agent/ 下找不到 sig、路徑也對不上 → 正常包每次啟動必拒。拆成雙層後各管各的:
| 層 | 落點 | 用途 | 驗證時機 |
|---|---|---|---|
| agent 層 | agent/manifest.json+agent/manifest.sig |
runtime 啟動 gate(掃 agent/ dist:binary、.so、assets) | 每次啟動全驗 |
| bundle 層 | 骨架根 manifest.json+manifest.sig |
交付完整性(掃整個骨架,含 agent 層 sig 形成互鎖) | install.sh 安裝前驗一次 |
簽章順序:agent 層先簽(它是交付檔的一部分)→ 骨架全組完 → bundle 層才產 manifest 送簽 → tar。打包腳本有可執行斷言擋順序倒置(bundle manifest 必含 agent/manifest.sig)。兩層同走 LC agent_manifest type、同一把鑰。
連帶兩個配套調整(同 commit a11ccf2):
gen_agent_manifest.py 加 --external-symlinks {abort,skip}:工具樹帶指向安裝後路徑的 symlink(CINC binstub、LibreOffice desktop 檔),屬 omnibus/deb 打包形式,tar 原樣保留出貨;bundle 掃描以 skip 略過不收錄(對 build 機上恰好同名的檔算 hash 既無意義也不穩定),dist 掃描維持 abort 原判準。startup_gate.py 錨點與 resource_root() 分家(_manifest_root()):打包模式固定=binary 所在目錄、不吃 AGENT_RESOURCE_ROOT——驗證錨點可被環境變數改指等於自廢,且 smoke 第 4c 項(拿該變數做 resource_path 反證)會讓簽章產物在 gate 假紅。源碼模式保留覆寫(fake-root 突變測試靠它)。%%{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
T["guidant-agent-<ver>.tar.gz<br/>~1GB+(D3 已接受)"]
T --> A["agent/<br/>Nuitka standalone 產物<br/>自家 code 已編機器碼<br/>+manifest.json/.sig(agent 層:<br/>啟動 gate 每次啟動全驗)"]
T --> B["tools/<br/>外部工具 bundle"]
T --> C["install.sh<br/>七步安裝+互動問答<br/>upgrade / rollback / 維運子命令"]
T --> D["manifest.json + manifest.sig<br/>(bundle 層:install.sh 安裝前驗整包,<br/>涵蓋 agent 層 sig 互鎖)<br/>FR-064 泛用層簽章 type=agent_manifest<br/>🔄 雙層拆分見 §5.2.3"]
T --> E["systemd/<br/>guidant-agent.service<br/>EnvironmentFile=/etc/guidant-agent/agent.env"]
B --> B1["cinc-auditor(~275MB)<br/>⚖️ 不可換官方 InSpec"]
B --> B2["sonar-scanner+JRE(~147MB)"]
B --> B3["LibreOffice+fonts-noto-cjk(~400MB+)<br/>形式待 Q1 實測"]
B --> B4["xsltproc+nmap.xsl / git"]
出貨側 build 管線(FR-063/064 資產複用點):
%%{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
S["evidence-agent 源碼<br/>+ jedi-common / jedi-file-upload<br/>(build 機需可達 Nexus)"] --> W["dependency-injector<br/>私房 wheel<br/>(複用 FR-063 rebuild_wheels.sh)"]
W --> N["Nuitka standalone 編譯<br/>version bake + resource_path()<br/>--include-data-dir assets/<br/>EXCLUDE 名單重評(Q3)"]
N --> P["組 tarball 骨架<br/>agent/ + install.sh + systemd/"]
TB["工具 bundle 下載/整備<br/>CINC / sonar-scanner /<br/>LibreOffice(Q1)/ xsltproc / git"] --> P
P --> M["gen_integrity_manifest.py<br/>(FR-064 改常數)逐檔 hash"]
M --> SG["sign_manifest.sh → License Center 簽章<br/>type=agent_manifest(D6)"]
SG --> OUT["guidant-agent-<ver>.tar.gz<br/>出貨"]
白話:這階段做客戶手上唯一會碰到的東西——安裝腳本。客戶解開 tarball、跑 install.sh、回答三個問題,agent 就變成一個開機自起、由 systemd 看管的系統服務並自動向雲端註冊上線;日後升級換版、出問題回滾、日常看狀態看 log,也全走同一支腳本的子命令。它吃 .2 的 tarball 當輸入,是 .1/.2 成果對客戶的「出口」。
七步(承 FR-065 七步結構):前置檢查(root / systemd / 磁碟空間 / amd64;支援 --check-only)→ 驗 manifest 簽章(D6 交付完整性)→ 落檔 /opt/guidant-agent/versions/<ver>/ → 裝 bundle 工具(含 Q1 定案的 LibreOffice 落地形式與 fonts-noto-cjk 字型)→ 建 symlink(current → 版本目錄;工具指令 symlink 供 connector 預設路徑取用)→ 裝 systemd unit(guidant-agent.service,EnvironmentFile=/etc/guidant-agent/agent.env)→ 啟動(systemctl enable --now)。
互動問答三題生成 agent.env(D5):①雲端位址 ②註冊 token ③本機對外位址(CSR SAN 用)。生成後 agent 首次啟動即走既有 core/enroll.py 自我註冊,並以 gunicorn/ssl 自起 8443 資料面 TLS(D4)——nginx unit/設定完全不存在於交付物。
install.sh --upgrade <新tarball>:驗章 → 解進 versions/<新ver>/ → 停服務 → 切 current symlink → 起服務;資料目錄(SQLite 檔、mTLS 憑證、agent.env)不動。install.sh --rollback:切回前一版 symlink、重啟。無 DB migration、無備份還原步驟——這是刻意的輕量模型(D7)。比照 FR-065:status(服務狀態+版本+心跳連線)/logs(journalctl 包裝)/uninstall(停服務、移除 unit 與版本目錄;資料目錄詢問後才刪)/fingerprint(印出本機三指紋值,供雲端綁定核對)。
白話:前三棒各自驗過自己,這棒證明「整條路對客戶真的成立」——拿一台從沒裝過任何東西的 Linux 機,只給它一顆 tarball,從安裝到跑完一輪真實檢測任務;再模擬客戶的封閉網路(斷網)環境重來一遍。最後把過程寫成客戶看得懂的安裝手冊,並把 Windows 可不可行的評估寫成文件收口 D8。
依賴鏈:FR-066.1 → .2 → .3 串行,.4 收口。
| # | 子任務 | 內容 | 驗收 | 依賴 |
|---|---|---|---|---|
| T-1.1 | SQLite 化 | Q2 掃描先行(jedi-file-upload PG 方言)→ init_db() 換 sqlite:///、SQLite 檔落資料目錄、agent-db 退場 |
Q2 掃描結果落文件;agent 以 SQLite 起動、檔案上傳/查詢功能綠;無任何 PG 連線需求 | — |
| T-1.2 | 程式改造 | version bake 取代讀 pyproject.toml;__file__ 三處(inspec.py:145 / nmap.py:101 / config.py:85)改 resource_path();指紋直讀+廢 collect-host-id.sh(D9);connector 工具路徑配置化 |
源碼樹不存在的環境下版號/資源定位正確;裸機直讀三指紋值與宿主實值一致;collect-host-id.sh 掛點全移除 | T-1.1 |
| T-1.3 | agent 版 build_release | 以 FR-063 管線為藍本:私房 wheel 複用、Nexus 套件編入、--include-data-dir assets/、EXCLUDE 名單重評(Q3)、smoke 驗證 |
無 Python 環境的乾淨機上產物可啟動、建 SQLite 表、心跳邏輯 dry-run 通過;build 腳本可重跑 | T-1.2 |
| # | 子任務 | 內容 | 驗收 | 依賴 |
|---|---|---|---|---|
| T-2.1 | 工具 bundle | CINC omnibus/sonar-scanner+JRE/LibreOffice 離線化(Q1 實測選定 deb 組 vs portable)+fonts-noto-cjk/xsltproc+nmap.xsl/git;Q4 openscap-utils・sshpass 殘留查證 | 斷網環境下工具全數可安裝且可執行(cinc-auditor version / sonar-scanner -v / libreoffice 轉檔+fc-match 'Noto Sans CJK TC' 正確);Q1/Q4 結論落文件 |
T-1.3 |
| T-2.2 | manifest 簽章+啟動驗章 | 簽章泛用層加 agent_manifest type;gen_integrity_manifest.py 改常數;sign_manifest.sh 接 License Center;agent 啟動驗章、失敗拒起 |
正常包啟動綠;任改一檔啟動即拒(突變驗證);簽驗流程可重跑 | T-1.3 |
| T-2.3 | build_bundle 打包腳本 | tarball 骨架組裝(agent/ + tools/ + install.sh + systemd/ + manifest)+sha256 產出 | 一鍵產出 guidant-agent-<ver>.tar.gz+sha256;解包結構與圖 2 一致 |
T-2.1, T-2.2 |
| # | 子任務 | 內容 | 驗收 | 依賴 |
|---|---|---|---|---|
| T-3.1 | install.sh 主流程 | 七步安裝+--check-only+互動問答三題生成 agent.env+systemd unit+自起 TLS 8443(D4/D5) |
乾淨機一鍵裝完 → 服務 active → enroll 成功 → mTLS 心跳上線 → 8443 憑證為 CA 簽發(SAN=填答位址) | T-2.3 |
| T-3.2 | upgrade / rollback | --upgrade(驗章→解新版→切 symlink→重啟)+--rollback(切回前版);資料目錄不動(D7) |
升級後版本上報為新版且既有資料(SQLite / 憑證)保留;回滾後服務以舊版正常心跳 | T-3.1 |
| T-3.3 | 維運子命令 | status / logs / uninstall / fingerprint(FR-065 同款體例) | 四子命令輸出正確;uninstall 後系統無殘留 unit;fingerprint 輸出與雲端綁定值一致 | T-3.1 |
| # | 子任務 | 內容 | 驗收 | 依賴 |
|---|---|---|---|---|
| T-4.1 | 乾淨機真機驗收+封閉網路驗證 | 乾淨 Linux 機端到端(§7 全清單)+斷網環境重跑安裝與任務 | §7 端到端驗收全數通過並留紀錄 | T-3.3 |
| T-4.2 | 客戶安裝手冊 | 安裝/開通/升級/回滾一本通(FR-065 手冊體例),含目標主機前提說明 | 依手冊可由非開發者完成安裝(以 T-4.1 過程校驗步驟完備) | T-4.1 |
| T-4.3 | Windows 評估文件 | 從零寫:指紋(WMI/registry)/服務形式(Windows Service/NSSM)/工具鏈可用性三題(D8) | 三題各有結論與前提清單;明確標注「不實作不承諾」 | — |
| # | 子任務 | 內容 | 驗收 | 依賴 |
|---|---|---|---|---|
| T-5.1 | 編譯環境降 glibc(容器化編譯) | build_agent_release 改於 debian:11(glibc 2.31)容器內編譯(Python 版本對齊、私房 wheel/Nexus 憑證進容器、build_all 接上);產物向上相容三家族 | smoke_agent_release 5/5 綠;rockylinux:9 容器內產物可啟動、無 GLIBC 缺符號;build_all 一鍵重出 tarball | — |
| T-5.2 | install.sh EL 適配 | --check-only 加發行版辨識(os-release)+SELinux 狀態(getenforce)+firewalld 8443 檢測;輸出對應指引指令,不自動改防火牆/安全政策;SELinux enforcing 下服務起不來的最小適配(runner 查證後定案,查證先於修) |
Rocky 9 上 --check-only 輸出正確指引;deb 家族輸出不回歸;enforcing 下服務可 active | T-5.1 |
| T-5.3 | Rocky 9 真機端到端驗收 | SELinux enforcing 的 EL9 機器走全鏈:install → TOFU → enroll → 心跳 → 8443 mTLS → 派一單 CINC 檢測任務 → 報告回雲端、LibreOffice 轉 PDF 中文不缺字(驗收機由決策者提供) | §7 驗收清單於 EL9 全數通過並留紀錄 | T-5.2 |
| # | 子任務 | 內容 | 驗收 | 依賴 |
|---|---|---|---|---|
| T-6.1 | 指紋二源化(D11,先行) | device_uuid 改 sha256(product_uuid|machine-id) 二源(agent 端演算法+原生/容器兩形態一致);BE enroll 端加同指紋撞號偵測(拒絕並提示,含 error code);既有 agent 重 enroll SOP 落文件;collect-host-id.sh 殘跡確認全清 | 二源指紋在原生與容器內算值一致且等於宿主直讀;BE 撞號註冊被拒且訊息可辨;123 重 enroll 成功 | — |
| T-6.2 | agent SeaweedFS 儲存支援 | config.py 補 seaweedfs 分支(複用套件 SeaweedfsUploadConfigDTO);env 契約定案(STORAGE_TYPE=seaweedfs 預設值與 SEAWEEDFS_* 變數,沿用既有 MINIO_* 命名精神、先 grep 全 codebase 確認無同義變數);自有 SeaweedFS/MinIO 設定文件;防篡改成立條件查證(SeaweedFS S3 Object Lock/WORM 支援度+威脅模型邊界,查證結論落文件、宣稱強度依結論定) | STORAGE_TYPE=seaweedfs 時檔案實際落 SeaweedFS 且 storage_type 記 seaweedfs;改 env 指自有 MinIO 亦通;查證結論落卡與文件 | — |
| T-6.3 | compose 交付包 | Nuitka 產物進薄 base image(非源碼版)+SeaweedFS service(官方 image,weed server -s3 -filer,admin UI 曝露走內網介面綁定)+指紋二源掛載(product_uuid/machine-id 唯讀)+資料 volume 佈局(D7 精神:資料與版本分離)+build_all 加 compose 打包出口(同一顆產物同一套簽章);舊 deploy/docker-compose.yml 汰換或改造 | 乾淨 docker 機 compose up 一鍵起(agent+SeaweedFS),agent 8443 mTLS 自起、enroll 上線;image 內翻不到 .py 源碼;build_all 一鍵出 compose 包 | T-6.1, T-6.2 |
| T-6.4 | 端到端驗收(含 D13 既有鏈路) | compose 版裝起 → 雲端儲存設備設定選「使用 Agent 服務」→ 上傳證據 → 檔案落 agent 側 SeaweedFS(admin UI 可見、DB storage_type 正確)→ 產品端預覽/下載通;升級(換 image tag)資料存活;驗收紀錄落文件 | 全鏈通並留紀錄;既有 FR-039 讀寫路徑無回歸 | T-6.3 |
| T-6.5 | configure-storage 子命令 | agent 維運子命令加 configure-storage:互動問答/帶參數設定儲存後端三型,寫 agent.env、實打端點驗連通、重啟生效;compose 形態對應操作路徑(exec 或 .env 說明);deploy README 同步 | 三型各設定一輪皆通(連通驗證擋得住錯 endpoint/憑證);設定後上傳實際落新後端;原生與 compose 皆有可操作路徑 | T-6.2 |
| T-6.6 | compose 版互動安裝程式 | 交付包補 install.sh(照主產品 FR-065 installer 模式):①環境檢查(docker/compose plugin/磁碟/machine-id/product_uuid 可讀)②docker load+digest 比對③互動問答(雲端位址/註冊 token/本機對外位址三題,SeaweedFS 憑證自動生成)④產 .env(600)⑤compose up 等 healthcheck⑥印上線結果;維運子命令 status/logs/restart/stop/start/uninstall+configure-storage 轉呼叫容器內 ctl;--check-only/--config 非互動模式;升級模式(load 新 image→換 tag→up -d,資料 volume 不動);README/INSTALL.txt 對齊 | 乾淨 docker 機解包跑 install.sh 問答三題→兩容器 healthy→enroll 上線,全程零 docker 指令;--check-only 在缺 docker 機器上正確擋下;維運子命令可用;upgrade 換版資料存活 | T-6.3 |
| T-6.7 | 恢復系統內建 SeaweedFS(出廠快照+configure-storage 選項) | 安裝生成 SEAWEEDFS 帳密時快照 factory-defaults.env 到資料目錄(600、只含 SEAWEEDFS_* 五項,D7 資料目錄語意跨版本存活);原生 scripts/install/install.sh 與 compose deploy/install.sh 兩處同步;configure-storage 第一題改四選項(1 系統內建恢復預設/2 自有 SeaweedFS/3 自有 MinIO/4 本機磁碟),選 1 從快照讀回→照舊連通驗證→寫入→重啟、user 零輸入;快照不存在時選項照顯但給明確指引(舊站點已改自有後端補不回,文件註明找原廠);upgrade 路徑補快照(現行設定仍為內建值時);「舊檔不搬家」紅字警告在恢復路徑同樣觸發(T-6.5 機制);deploy README 對齊。主產品對應功能另卡處理 | 原生與 compose 各走一輪「內建→自有→恢復內建」循環,恢復後上傳實落內建 SeaweedFS;快照檔 600;無快照場景指引正確;deb 家族不回歸 | T-6.5, T-6.6 |
⏸ D19(2026-08-23):本節全卡樹暫緩——PM 拍板 Windows 不再探討;T-7.1/T-7.4 已完成的探路結論保留(見 t71-windows-probe.md/t74-wsl-engine-probe.md)。
| # | 子任務 | 內容 | 驗收 | 依賴 |
|---|---|---|---|---|
| T-7.1 | 160 真機探路實測 | 在 192.168.50.160(Win11+Docker Desktop)手動走通全鏈並記錄實況:onepack 解包→(暫用 WSL 終端跑現有 install.sh 或手動等效步驟)→兩容器起→enroll 上線→派一單檢測任務。重點查證四項落文件:①指紋二源掛載在 Docker Desktop 的實際行為(product_uuid 有無/machine-id 是誰的/守門與 log 表現)②雲端連回 8443 的網路路徑(Docker Desktop 端口映射行為,需不需要額外設定)③安裝第三題 IP 自動偵測的錯法與正確填法④重置 Docker Desktop 後重新開通 SOP 實測。實測記錄=T-7.2 的規格輸入 | 全鏈通並留實測紀錄;四項查證各有結論;擋路項(若有)逐條列出 | — |
| T-7.2 | install.ps1 互動安裝程式 | 照 deploy/install.sh 的流程邏輯(環境檢查→load image→問答三題→生成 .env→compose up 等 healthy→印上線結果)出 PowerShell 版;環境檢查改查 Docker Desktop(裝了沒/跑了沒/版本);第三題預設值改 Windows 主機 IP 偵測;TOFU 憑證確認、SeaweedFS 帳密生成、factory-defaults.env 快照對等實作;維運子命令(status/logs/restart/stop/start/uninstall/configure-storage 轉呼叫容器內 ctl)對等;T-7.1 查證出的 Windows 專屬步驟(8443/防火牆等)內建進流程或明確指引 | 160 上解包→install.ps1 問答三題→兩容器 healthy→enroll 上線,全程不開 Linux 終端不手打 docker 指令;維運子命令可用;configure-storage 恢復循環通 | T-7.1 |
| T-7.3 | Windows 前置指引+驗收收口 | 薄指引文件(Docker Desktop 安裝與授權須知/機器前提/install.ps1 用法/重置後重新開通 SOP/已知限制含指紋漂移邊界);160 端到端終驗(含升級路徑:同 onepack 跑 upgrade 資料存活);build 側若需(onepack 內附 install.ps1)調整打包腳本 | 指引可由未參與者照做裝成;終驗全鏈+升級通留紀錄;PM 展示可用(setup.exe 升級項的反饋入口) | T-7.2 |
| T-7.4 | B 案生死驗證(已完成,判死) | WSL2+docker engine+排程器常駐可行性實測 | ✅ 完成(2026-08-23):不可行——閒置 60 秒凍結,詳 t74-wsl-engine-probe.md | — |
⏸ D19(2026-08-23):本節全卡樹暫緩——PM 拍板 Windows 不再探討;T-7.1/T-7.4 已完成的探路結論保留(見 t71-windows-probe.md/t74-wsl-engine-probe.md)。
| # | 子任務 | 內容 | 驗收 | 依賴 |
|---|---|---|---|---|
| T-8.1 | Windows build 管線 | Windows build 機佈建(MSVC Build Tools+Python 3.11+Nexus 可達;機器待決策者定案,160〔Server 2025, 8vCPU/24GB〕可兼任候選);dependency-injector 私房 wheel MSVC 重編;Nuitka standalone 編 agent.exe;build 腳本 PowerShell 載體;smoke 對應(產物翻不到 .py/可啟動/健康檢查)。參考 Linux 版教訓:1303~1309 七輪除錯,預留試錯棒數 | 無 Python 的乾淨 Windows 機上 agent.exe 可啟動、建 SQLite、心跳 dry-run 通過;build 可重跑 | build 機定案 |
| T-8.2 | 指紋 OS 抽象層+程式改造 | fingerprint provider 化(D9 延後項正式開工):Linux=讀檔(現行)、Windows=WMI Win32_ComputerSystemProduct.UUID+registry MachineGuid(winreg 標準庫);WMI 佔位值(FFFFFFFF…)處理策略;工具路徑預設值 Windows 化(AGENT_CINC_BIN 等);設定落點 C:\ProgramData\GuidantAgent\ | 二源指紋在 Windows 讀值與 WMI/registry 直查一致;佔位值場景有明確行為;Linux 版行為零回歸(既有測試綠) | T-8.1 |
| T-8.3 | 雙服務化+install.ps1 | WinSW(或同族 wrapper,實測選定)包 agent.exe+weed.exe(SeaweedFS Windows 版)成兩個 Windows 服務(開機自起/崩潰重啟/log 輪替/優雅停止);install.ps1 七步(環境檢查→驗章→落檔 versions\→裝服務→問答三題〔IP 偵測用 Find-NetRoute,T-7.1 已驗〕→防火牆規則自動加〔T-7.1 已驗一條 inbound 即通〕→啟動);TOFU 憑證確認 PowerShell 重寫;SeaweedFS 帳密生成+factory-defaults 快照+configure-storage 對等;升級回滾(versions\+junction 切換,D7 模型);維運子命令 | 乾淨 Windows 機 install.ps1 問答三題裝成→兩服務執行中→enroll 上線;登出+重開機後服務自起、雲端仍在線;升級回滾資料存活;維運子命令可用 | T-8.1(可與 T-8.2 平行起工) |
| T-8.4 | 工具鏈 Windows 離線整備 | CINC msi silent(msiexec /qn)+安裝後路徑;sonar-scanner windows-x64 zip(自帶 JRE);LibreOffice 選型實測(msi silent vs Portable,Q1-Windows)+Noto CJK 字型落地+headless 中文轉檔驗證;MinGit;weed.exe 釘版;Windows 版 tools-manifest(釘版+sha256+Nexus 第二來源)整套 | 斷網 Windows 機上工具全數可裝可執行(cinc-auditor version/sonar-scanner -v/LibreOffice 轉 PDF 中文不缺字);manifest 完整 | 可與 T-8.1~8.3 平行 |
| T-8.5 | 簽章+乾淨機端到端驗收 | manifest 簽章泛用層接 Windows 產物(agent_manifest type 沿用)+啟動驗章+突變測試;打包腳本出 Windows 交付包;乾淨機端到端:裝→enroll→跑 CINC+SSH 遠端型任務→證據落本機 SeaweedFS→登出/重開機存活→升級回滾→防竄改;SmartScreen 白名單指引寫入文件(D18 策略) | §7 對應項全綠留紀錄;改任一檔啟動被拒;白名單指引可操作 | T-8.2, T-8.3, T-8.4 |
| # | 查證項 | 歸屬子任務 | 性質 |
|---|---|---|---|
| Q1 | LibreOffice 離線化形式(deb 組 vs portable/AppImage) | T-2.1 | 實測選定,判準:離線可裝/字型一起落/體積可接受 |
| Q2 | jedi-file-upload PG 方言依賴掃描 | T-1.1 | D2 落地的前置守門,.1 開工先做 |
| Q3 | Nuitka EXCLUDE_COMPILE_PKGS 名單重評 | T-1.3 | agent 依賴集合與 BE 不同,不照抄 FR-063;冷 build 預期遠低於 BE 56 分鐘 |
| Q4 | openscap-utils / sshpass 是否已無用 | T-2.1 | 殘留即不搬進 tarball,包更瘦 |
install.sh 七步全綠 → 互動三題填答 → 服務 active、開機自起。--upgrade 新版心跳上報新版號、SQLite 資料與憑證保留;--rollback 後舊版正常服務。big-feature-workflow skill Step 4/5 進行,本文件拆分表即開卡依據。