# FR-039 evidence-agent 安裝精靈（install.sh）— 設計文件

| 項目 | 內容 |
|------|------|
| FR 編號 | FR-039（子題:客戶端部署體驗優化）|
| 提出日期 | 2026-06-19 |
| 關聯 | 母設計 `design.md`(分散式檔案 agent)、`design-agent-auth.md`(mTLS 認證 v2)、`deploy/README.md`(現行手動部署手冊)|
| 實作 repo | `evidence-agent`(`deploy/install.sh` 等),**非** compliance-manager-be |
| 狀態 | 設計定案(2026-06-19 brainstorm 完成,user 核可)|
| 範圍 | 把客戶端 8 步有序、有暗坑的手動部署,收斂成「解壓一包 → `./install.sh` → 回答 4 題 → 完成」|
| 出 scope | 不換掉 Docker 架構;不做 SQLite / in-app mTLS(後續 B 方向);不做 `.deb`/單一執行檔(後續 C,已評估勸退);不碰雲端側 PKI / token 產生 |

---

## 1. 問題與動機

### 1.1 現況痛點

現行客戶端部署(見 `deploy/README.md`)要在客戶 Linux server 做 8 個有先後依賴、有暗坑的手動步驟:

1. 裝 Docker + docker compose
2. `docker save | ssh docker load` 搬 image
3. 手填 `deploy/.env` 約 12 個變數
4. 跑 `collect-host-id.sh` 抓 host MAC
5. **先**起 `file-agent + agent-db` 等註冊拿憑證
6. 確認 `certs/` 有檔
7. **再**用 `--profile full` 起 nginx(順序顛倒就起不來)
8. 回雲端 FE 設 storage-config 指向這台

痛點集中三處:**步驟有先後依賴**(憑證沒簽回 nginx 起不來)、**手填一堆有暗坑的 env**(`AGENT_BASE_URL` 必須 `https://...:8443`、MinIO 要填 LAN IP 不能用 service 名、`PDF_CACHE_DIR` 要同裝置)、**moving parts 多**(3 容器 + host-id 腳本)。

### 1.2 方向決策(已選定)

評估過三個方向:

| 方向 | 做法 | 結論 |
|------|------|------|
| **A(本設計)** | install.sh 安裝精靈,Docker 架構不動 | **採用**:風險最低、最快見效 |
| B | 砍 moving parts(SQLite + in-app mTLS,3 容器→1) | 後續再評估;要動 agent 程式 |
| C | 原生安裝檔(`.deb` / PyInstaller 單一執行檔)| 勸退:LibreOffice(約 400MB 原生)+ DB 兩個硬依賴,單一執行檔/套件會一直跟原生依賴打架,失去 Docker 打包環境一致的價值 |

> 為何 Docker 是資產不是負擔:agent 硬依賴 LibreOffice(預覽轉檔,設計 §3.4 刻意放客戶端不可拿掉),Docker 幫你把它跟環境一致地打包。問題從來不是「用 Docker」,而是部署步驟太碎。精靈解的是步驟,不換架構。

---

## 2. 出貨形態

一個壓縮包 `evidence-agent-deploy-<version>.tar.gz`,內含:

```
evidence-agent-deploy-0.1.0/
├── install.sh                      ← 安裝精靈(本設計主體)
├── docker-compose.yml              ← 沿用既有
├── nginx/agent.conf                ← 沿用既有
├── collect-host-id.sh              ← 沿用既有(精靈自動呼叫)
├── .env.example                    ← 沿用既有
├── answers.conf.example            ← 新增:精簡必填清單(無人值守用)
├── README.md                       ← 部署手冊(精簡版,指回精靈)
└── evidence-agent-0.1.0.image.tar  ← image tar(精靈偵測缺 image 時自動 load)
```

部署人員體驗:**解壓一包 → `./install.sh` → 回答 4 題 → 完成**。

---

## 3. 精靈四階段

### ① Preflight 檢查(缺什麼清楚報錯停下,不自動修)

| 檢查項 | 缺了怎麼辦 |
|--------|-----------|
| Docker + compose plugin | 報「請先安裝 Docker,參考 <連結>」並停(**不自動裝** — 跨發行版坑多、企業客戶常有自己的安裝政策/離線環境)|
| 是 Linux(`/sys/class/dmi/id/product_uuid`、`/etc/machine-id` 可讀)| 報錯停(指紋算不出來)|
| 8443 port 未被占用 | 報「8443 已被占用」並停 |
| image 在不在 | 不在 → 自動 `docker load` 同目錄 `*.image.tar`;tar 也找不到才報錯 |

### ② 設定收集(互動為主 + 無人值守接口)

**模式**:
- 預設互動式,即時問即時填
- `--config answers.conf` / `--non-interactive`:讀設定檔跑完,適合大量複製部署 / 部署文件
- 互動模式跑完會把答案存成 `answers.conf`(不含密碼),方便下次重灌或複製到別台

**要問的(精簡到 4 類)**:
1. 儲存模式 `minio` / `local`(minio 才續問 `MINIO_ENDPOINT`/`ACCESS_KEY`/`SECRET_KEY`/`BUCKET`)
2. `CLOUD_ENDPOINT`(雲端 base,不含 `/api/1.0`)
3. `REGISTRATION_TOKEN`(雲端管理頁產生的該 tenant token)
4. DB 密碼(可選「**自動產一組強密碼**」,免部署人員自己想)

**自動推導,不用問**:
- `AGENT_BASE_URL` ← 偵測預設路由網卡 IP 組成 `https://<ip>:8443`,**顯示並請部署人員確認「雲端能用這 IP 連到這台嗎?」可覆寫**。這是手冊裡最常踩的暗坑(填成 `http:8080` 會讓雲端報 `FILE_AGENT_500005`),精靈幫擋。
- `AGENT_AUTH_MODE=full`(正式一律 full)、`AGENT_IMAGE`、`AGENT_VERSION` 固定值

**產出**:
- `.env`(含密碼,`chmod 600`,不入版控)
- `answers.conf`(**不含任何密碼**,可版控 / 複製到別台)

**重跑**:偵測到既有 `.env` / `answers.conf` 就載入當預設,部署人員只改要改的。

### ③ 啟動編排(精靈最大價值 — 自動解掉憑證兩段式)

1. 自動跑 `collect-host-id.sh` 抓 host MAC
2. `docker compose up -d file-agent agent-db`
3. **輪詢等待**:等 file-agent `/health` 200 **且** `certs/agent.crt` 出現(= enroll 成功);有 timeout;失敗就 `docker compose logs --tail` file-agent 把錯誤印出來停下
4. 憑證就緒才 `docker compose --profile full up -d nginx`

> 部署人員完全不用知道「先 agent 後 nginx」這個順序陷阱。

### ④ 自我驗證 + 交棒(本機這側驗到位,資料面交棒雲端)

- file-agent `/health` 200、`certs/` 憑證齊、agent log 出現 `agent enrolled` + `heartbeat`
- 本機 `curl -sk https://localhost:8443`(預期被 **400** 擋 → 證明 TLS + port 8443 通、nginx mTLS 起得來)
- 印出明確下一步:
  - 回雲端 FE storage-config 指向本台(`base_url = https://<ip>:8443`)
  - 從**雲端那台**(有 client 憑證)跑 README §9-1 的三層認證驗證(400 / 200 / 401)

> 完整三層認證驗證天然在雲端側做(client 憑證在雲端),不綁進客戶端精靈;客戶端精靈確認「本機這側都 OK + 8443 TLS 通」即交棒,分工乾淨。

---

## 4. 安全 / 可重跑(守 evidence-agent 硬規則)

- **絕不刪** `pgdata` / `filedata` / `certs`(關鍵狀態,host bind-mount);精靈全程冪等、可安全重跑。
- `.env` 一律 `chmod 600`;`answers.conf` 不寫任何密碼;不把任何 secret 印進 stdout / log。
- **full 模式下不對外 publish 8080**(用 compose override / profile 收掉對外 8080),避免繞過 mTLS。現行手冊靠人工提醒(README §7 結尾),精靈直接幫做。
- 憑證 / 密碼禁入版控(專案硬規則),精靈產生的檔都落在 `deploy/` 由 `.gitignore` 擋。

---

## 5. 元件邊界

| 單元 | 職責 | 依賴 |
|------|------|------|
| `install.sh` | 整支精靈進入點,串四階段 | docker / compose、同目錄各檔 |
| preflight 函式群 | 純檢查、回報、停 | host 環境 |
| 設定收集函式群 | 互動 prompt / 讀 answers.conf / 寫 `.env`+`answers.conf` / 自動推導 IP | — |
| 啟動編排函式群 | 依序拉起 + 輪詢憑證就緒 + 失敗 tail log | docker compose、`collect-host-id.sh` |
| 自我驗證函式群 | 健康/憑證/log/本機 8443 檢查 + 印交棒提示 | curl、docker compose logs |
| `answers.conf.example` | 無人值守必填清單範本 | — |
| compose override(full 收 8080)| full 模式不對外 publish 8080 | docker-compose.yml |

各單元以 shell 函式隔離,可單獨讀懂、單獨測(可用 `--dry-run` 或分階段旗標驗證)。

---

## 6. 不做的(YAGNI / 出 scope)

- 不裝 Docker、不裝 LibreOffice(在 image 內)
- 不碰雲端側(PKI 設定、產 registration token 都在雲端那台 / FE)
- 不做 B 階段的 SQLite / in-app mTLS、不做 `.deb` / 單一執行檔(C)
- 不做 agent 升級 / 滾動更新流程(重跑精靈換 image tag 即可,複雜版本管理列後續)

---

## 7. 不變式(implementation 時要守住)

- 精靈**不得刪除** `pgdata` / `filedata` / `certs`,任何情況都不行。
- 精靈**不得**把密碼 / token / MinIO key 印到 stdout 或寫進 `answers.conf`。
- 精靈**不自動安裝 Docker**,缺就報錯停。
- 啟動順序一律「先 file-agent enroll 拿憑證 → 再 nginx」,nginx 不得在憑證就緒前啟動。
- full 模式對外只留 nginx:8443,不對外 publish file-agent:8080。
- `.env` 權限一律 `chmod 600`。
