FR-066 · Agent Native Installer — 需求討論稿 · 2026-08-18(v1 定案版)

讓檢測 Agent 貼著宿主跑:非 docker、systemd 服務、一顆 tarball 裝完

檢測 Agent(evidence-agent)目前唯一交付形式是 docker image+三容器 compose。但 agent 的本職是量測宿主機——機器指紋要讀 /sys/class/dmi/id/product_uuid/etc/machine-id、主網卡 MAC,這些在容器裡讀不到或會漂移,已經靠 collect-host-id.sh workaround 硬撐。FR-065 期間決策者裁定:agent 安裝包另起新 FR、非 docker。本案把 agent 做成 Nuitka 編譯的 tarball 安裝包install.sh 一鍵裝成 systemd 服務,先做 Linux;Windows 只出評估文件。九項關鍵決策(D1–D9)已全數拍板,本稿記錄定案理由與被排除方案,供 design.md 深化。

決策 D1–D9 全數定案 開放查證項 Q1–Q4 前作:FR-063(Nuitka)/ FR-064(簽章)/ FR-065(installer) Windows 只出評估文件 tarball 全包 ~1GB+ 已接受
§0

分工概述(30 秒版)

整案一句話:把檢測 Agent 從「docker 三容器出貨」改成「Nuitka 編譯+外部工具全包的一顆 tarball,install.sh 一鍵裝成 Linux systemd 服務」,宿主指紋直讀、SQLite 取代 PG、agent 自起 TLS 取代 nginx sidecar、build 期 manifest 簽章+啟動驗章。

階段 一行說明
FR-066.1 Nuitka build 管線 agent 版 build_release.sh、dependency-injector 私房 wheel、version bake、resource_path() 改造、SQLite 化
FR-066.2 tarball 打包+工具 bundle 全包一顆(CINC / sonar-scanner / LibreOffice / xsltproc / git)+ manifest 簽章
FR-066.3 install.sh+systemd 七步安裝、互動問答、upgrade/rollback(symlink 切換)、維運子命令、自起 TLS
FR-066.4 驗收與文件 乾淨機真機驗收、封閉網路驗證、客戶手冊、Windows 評估文件

依賴一句:.1 → .2 → .3 串行(build 產物是打包的輸入、打包產物是安裝的輸入),.4 收口。

§1

需求背景:容器裡的 Agent 量不到宿主

1.1 現況交付形式

檢測 Agent(evidence-agent,獨立 repo ~/Projects/Billows/Audit-Manager/evidence-agent,v0.2.27,Flask + dependency-injector)目前唯一的交付形式是:

  • docker imagepython:3.11-slim base)
  • docker compose 三容器agent 本體 + agent-db(postgres:16)+ nginx mTLS sidecar
  • 出貨走 docker save / docker load

1.2 為什麼必須脫離 docker

FR-065(產品落地 installer)期間,決策者裁定:agent 安裝包另起新 FR、非 docker。核心理由是 agent 的本職與容器化根本衝突:

機器指紋讀不到/會漂移

指紋來源是 /sys/class/dmi/id/product_uuid/etc/machine-id、主網卡 MAC——容器內讀不到或會漂移。已經發生過 machine-id 空檔導致指紋漂移的事故(Debian base image 的 /etc/machine-id 是空檔,退回隨機 MAC),現行靠 deploy/collect-host-id.sh workaround 從宿主抽值塞進容器硬撐。

業界同類產品皆為原生交付

Nessus Agent、Qualys Cloud Agent、Wazuh Agent、Elastic Agent、Datadog Agent——全部以原生套件(deb/rpm/tarball)+ systemd 服務交付,沒有一家把主機檢測 agent 包成 docker 出貨。貼宿主量測的產品,原生安裝就是行規。

1.3 本案目標

  • Agent 做成 Nuitka standalone 編譯的 tarball 安裝包
  • install.sh 一鍵安裝為 Linux systemd 服務
  • 外部檢測工具全包在同一顆 tarball,離線(封閉網路)完全自足
  • Windows 不實作,只出評估文件與前提清單

1.4 前作可複用資產

本案不是從零開始——三個前作已把大部分地基打好:

前作 可直接複用
FR-063 Nuitka 落地版打包 scripts/build/build_release.sh build 管線、dependency-injector 私房 wheel rebuild_wheels.shresource_path() 慣例、DI 靜態清單處理手法
FR-064 防竄改偵測 簽章泛用層 sign_payload / verify_payload(帶 type 欄)、gen_integrity_manifest.pysign_manifest.sh、License Center 簽發
FR-065 產品落地 installer install.sh 七步結構、--check-only、安裝落檔慣例、digest 驗證、維運子命令(status / logs / uninstall / fingerprint)
§2

決策定案(D1–D9)

以下九項全部已由決策者拍板,每項記定案內容、理由與被排除方案。

D1 打包形式=Nuitka standalone → tarball → systemd 服務

✅ 定案:Nuitka standalone 編譯,打成 tarball,裝成 systemd 服務

standalone 模式同樣是機器碼編譯——自家 code 不可見,保護強度與 FR-063 的 BE 打包相同。第三方套件以原始 .py 附帶(standalone 的 EXCLUDE 名單機制),但那些是公開開源套件、非自家 code,不構成洩漏。

排除:onefile 模式——①啟動要先解壓到暫存目錄,有啟動延遲;②FR-064 逐檔 manifest 驗章在單一自解壓檔上難做;③外部工具(CINC / LibreOffice 等)反正要目錄形式存在,onefile 省不了目錄。

D2 本地 DB=SQLite 化

✅ 定案: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):掃 jedi-file-upload 的 model 與查詢確認無 PG 方言依賴(jsonb / ON CONFLICT 等)。

排除:①要求宿主裝 PG——加重客戶前置需求,違背一鍵安裝目標;②bundle PG 進 tarball——包更肥、多一個服務要維運(起停/備份/密碼),為一張表不值得。

D3 外部工具交付=全包一顆 tarball,離線完全自足

✅ 定案:所有外部檢測工具 bundle 進同一顆 tarball,install.sh 自動安裝,離線完全自足

決策者裁定理由:封閉網路情境(FR-059.3 已驗證為既定支援情境)下,全包才不存在漏帶/版本錯配的現場事故。體積 ~1GB+ 已明示接受。

bundle 清單

工具 體積 備註
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
xsltproc + assets/nmap.xsl nmap 報告轉換
git

不 bundle:ZAP / OpenVAS(客戶自備 daemon)、OpenSCAP / Nmap 引擎(裝在目標主機、agent SSH 過去跑,不在 agent 機上)。

排除:方案 B「核心包+工具附加包分層」——升級只傳 core 較省流量,但多檔交付有漏帶風險,封閉網路現場出事沒有網路可補救;方案 C「宿主前置需求清單」——把安裝品質丟給客戶,且客戶自裝 InSpec 會踩 CINC 授權紅線。

D4 mTLS 承接=agent 自起 TLS,砍掉 nginx sidecar

✅ 定案:agent 以 gunicorn/ssl 自行承接 8443 資料面 TLS,nginx sidecar 退役

mTLS 的驗證邏輯本來就在 agent code 內core/data_plane_auth.py),nginx 只是轉發層——砍掉它不損失任何安全能力。8443 的 server 憑證由 enrollment 時 CA 簽出(CSR SAN=base_url host),客戶不需自備憑證

D5 設定模型=systemd EnvironmentFile,客戶側 mTLS 全自動

✅ 定案:/etc/guidant-agent/agent.env 作為 systemd EnvironmentFile

沿用現有全環境變數設計(config/config.py,agent 沒有 agent.json 這種設定檔)——零 code 改動install.sh 互動問答三題(雲端位址/註冊 token/本機對外位址)生成該檔。

客戶側 mTLS 全自動:agent 自我註冊流程已存在(core/enroll.py:產 keypair+CSR → 拿 token 註冊 → 憑證簽回存 cert_dir → 心跳走 mTLS → 404 自動重註冊),客戶零 openssl 操作。雲端側 PKI 由 FR-065 產品 install.sh 已涵蓋(install.sh:1936 AGENT_CERT_DIR)。

D6 簽章/防竄改=本 FR 一起做,瘦身版

✅ 定案:build 期 manifest 簽章+啟動時驗章;不做 FR-064 runtime 全家桶

複用 FR-064 泛用層:簽章加一個 agent_manifest type、gen_integrity_manifest.py 改常數、sign_manifest.sh 近零改——複用成本僅 1–2 張小卡

為什麼值得做:agent 以 root 服務跑在客戶機、握 mTLS 憑證,是整條信任鏈最容易被摸的一端。

不做:FR-064 的 runtime 抽查/lockdown/unlock token 全家桶——對 agent 過重。

✅ 定案:/opt/guidant-agent/versions/<ver>/current symlink

升級=解新版本目錄、切 symlink、重啟服務;回滾=切回舊 symlink。SQLite 檔與 mTLS 憑證放資料目錄、不隨版本目錄走。agent 無 DB migration 負擔(D2 單表自建),所以不需要 FR-065 那套 pg_dump 備份模型——這條升級模型比產品端輕得多。

D8 Windows=只出評估文件+前提清單,不實作不承諾

✅ 定案:本 FR 對 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 依然存在,是評估文件的重點。

D9 指紋=只做 Linux 原生直讀,不預抽 OS 抽象層

✅ 定案:原生化後直讀系統路徑,廢除 collect-host-id.sh;不為 Windows 預留抽象層

直讀 /sys/class/dmi/id/product_uuid/etc/machine-id、主網卡 MAC——core/fingerprint.py 已有伏筆註記(「原生封裝後直接讀系統路徑,模型不變」)。deploy/collect-host-id.sh workaround 隨之廢除。

排除:先抽 OS 抽象層——Windows 需求未定案(D8 只出評估文件),為未定需求預先抽象是過度設計;等 Windows 真要做再抽,重構成本可控。

§3

現況接入點盤點

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)
config/config.py 版本上報 pyproject.toml 取版號 Nuitka 後檔案不存在 → 改 version bake(同 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 私房 wheelrebuild_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 打包。

§4

開放項(實作期查證,Q1–Q4)

Q1 LibreOffice 離線化形式

自帶 deb 組 dpkg -i vs portable / AppImage——兩條路都存在,實作期(.2)實測選定。判準:離線可裝、字型(fonts-noto-cjk)能一起落、體積可接受。

Q2 jedi-file-upload PG 方言依賴掃描(D2 前置)

掃 model 定義與所有查詢,確認無 jsonb / ON CONFLICT 等 PG 方言。有的話評估改寫成本再定 SQLite 落地細節。.1 開工先做。

Q3 Nuitka 編 agent 的 EXCLUDE_COMPILE_PKGS 名單重評

agent 依賴集合與 BE 不同(paramiko / python-gvm / zaproxy / cryptography 等),FR-063 的名單不能照抄。agent 沒有 pandas 級的編譯大戶,冷 build 預期遠低於 BE 的 56 分鐘

Q4 image 內 openscap-utils / sshpass 是否實際已無用

OpenSCAP connector 已改 paramiko 直連、不用官方 oscap-ssh 腳本——這兩個套件可能是殘留。查證結果決定要不要跟著搬進 tarball(不搬=包更瘦)。

§5

階段拆分草案

供後續 design.md 深化。建議拆 4 個子需求:

子需求 內容 依賴
FR-066.1 Nuitka build 管線 agent 版 build_release.sh、dependency-injector 私房 wheel 複用、version bake、resource_path() 改造三處 __file__assets/ include、SQLite 化(含 Q2 查證) 起點
FR-066.2 tarball 打包+工具 bundle 全包一顆(D3 清單)、Q1/Q4 查證落地、manifest 簽章(D6:agent_manifest type、gen/sign 腳本改造) 依賴 .1(build 產物是輸入)
FR-066.3 install.sh+systemd 七步安裝結構、互動問答三題生成 agent.env(D5)、自起 TLS(D4)、upgrade/rollback symlink 模型(D7)、維運子命令(status / logs / uninstall / fingerprint) 依賴 .2(tarball 是輸入)
FR-066.4 驗收與文件 乾淨機真機驗收、封閉網路(離線)驗證、客戶安裝手冊、Windows 評估文件(D8) 收口,依賴 .1–.3

依賴鏈:.1 → .2 → .3 串行,.4 收口。與 FR-063/064/065 不同,本案各階段產物互為輸入,無平行空間。

§6

交付物結構

%%{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-&lt;ver&gt;.tar.gz<br/>~1GB+(D3 已接受)"]
  T --> A["agent/<br/>Nuitka standalone 產物<br/>自家 code 已編機器碼"]
  T --> B["tools/<br/>外部工具 bundle"]
  T --> C["install.sh<br/>七步安裝+互動問答<br/>upgrade / rollback / 維運子命令"]
  T --> D["manifest.json + manifest.sig<br/>FR-064 泛用層簽章<br/>type=agent_manifest"]
  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"]
圖 1 — tarball 交付物結構(D1/D3/D6 落地形狀)
§7

端到端安裝時序

%%{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/&lt;ver&gt;→裝 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
圖 2 — 客戶側一鍵安裝到心跳上線(D4/D5 全自動 mTLS)
§8

Build 管線

%%{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-&lt;ver&gt;.tar.gz<br/>出貨"]
圖 3 — 出貨側 build 管線(FR-063/064 資產複用點標示)
§9

附註

  • 本稿為討論稿定案版,D1–D9 已拍板;下一步是 design.md 深化(含各子需求的卡片拆分)與 Notion 開卡。
  • Q1–Q4 為實作期查證項,不阻擋 design.md 撰寫,但 Q2(PG 方言)是 D2 落地的前置守門。
  • 本案標的 repo 是 evidence-agent,主專案(compliance-manager-be)只承載文件與簽章工具鏈的複用來源。