# Plan: 設定驅動的多主機正式部署

> Status: `draft-for-review`  
> Source: `requirements.md`, `spec.md`  
> Last updated: `2026-07-18`  
> Implementation: not started; this plan does not authorize a live deployment

## 1. 交付策略

1. 先建立設定 schema、preflight 與空 VM bootstrap，再碰正式服務。
2. 先完成 WireGuard 與 Registry，確保後續 image 只需本機 build、遠端 pull。
3. 資料層先行：Atlas connectivity、Redis persistence、MinIO persistence/bucket/backup。
4. 將現有共同 release 拆成 frontend、backend、worker、crawler、ops images。
5. 先單節點驗證，再做 frontend/backend rolling pool 與 Edge 動態 upstream。
6. first deploy、一般 deploy、config-only deploy、rollback 必須是不同明確路徑。
7. 所有 live 操作前先在全新 Ubuntu 24.04 VM 的 staging inventory 演練。

## 2. 里程碑

### M0 - 文件、設定契約與安全邊界

- 建立 `deployment.yml.example` 與 Vault schema。
- 定義 host role、覆寫規則、WireGuard 自動配號、image tag 與目錄契約。
- 實作 `doctor`/`preflight`，拒絕缺 IP、重複 IP、DNS 未指向、SSH/sudo 不可用與 unsupported OS。
- 定義正式環境防火牆矩陣、Atlas 固定出口與 TLS 前置條件。

完成定義：不連線部署即可驗證完整設定；錯誤訊息能指出欄位與 host。

### M1 - 空 VM bootstrap

- 用 Ansible raw task 支援無 Python 的 Ubuntu 24.04。
- 安裝 Python、Docker/Compose、WireGuard、UFW、chrony、fail2ban、unattended-upgrades。
- 建立 `/opt/haixun`、`/etc/haixun`、`/var/lib/haixun`、服務帳號與權限。
- 設定 Docker log rotation、daemon 啟動、Registry trust/login hook。
- 防火牆先保留 SSH，再按 role 套規則。
- bootstrap 可重跑，且不修改/清除 data volume。

完成定義：七種角色的空 VM 都可從只有 SSH/sudo 到 bootstrap ready；第二次執行為 no-op 或安全更新。

### M2 - WireGuard、Registry 與本機 build pipeline

- 依 inventory 自動分配 WireGuard IP，建立 peer 並驗證連通。
- 配置固定出口節點供 Atlas 連線。
- 部署帶 TLS/auth/persistent volume 的自架 Registry。
- Edge/Registry DNS 檢查與 Let's Encrypt 自動申請/續期。
- 新增 frontend/backend/worker/crawler/ops Dockerfile 與 Buildx build/push 指令。
- image 使用 git SHA；保存上一個成功 tag 供 rollback。

完成定義：遠端 VM 不具備 source tree 也能 pull 並啟動測試 image；Registry 重啟後 image 仍存在。

### M3 - Atlas、Redis、MinIO 與 first data initialization

- production 移除本機 Mongo 假設，所有 runtime 讀取 Atlas URI。
- Redis 獨立 Compose：auth、AOF、volume、health、backup/restore。
- MinIO 獨立 Compose：volume、health、bucket/policy idempotent init、backup/restore。
- ops image 提供 migration/init/seeder。
- seeder 改為由 Vault 設定 admin email/display name/password，既有資料不覆寫。
- 將現有 backup、monitoring、reset 腳本從本機 Mongo 假設拆開；Atlas 備份交由 Atlas Backup/PITR。

完成定義：空 Atlas database 可按 migration -> init -> seed 建立；重跑不重複資料、不重設密碼；Redis/MinIO restart 後資料保留。

### M4 - 應用角色拆分與無狀態修正

- frontend image 只含 production static build。
- backend image 含 gateway 與固定 extension ZIP path。
- worker image 含 Go worker、Node profile scraper、相容 Chromium。
- crawler sidecar 可設定 bind address、有 health endpoint，只走 private Docker network。
- role-specific env/template 只下發所需 secret。
- gateway readiness 補 Mongo + Redis；frontend 提供 build version；worker 啟動檢查 identity/connectivity。

完成定義：frontend/backend/worker 可分別 build、push、deploy、restart；刪除任一 app container 後可從 image/config 重建且無業務資料遺失。

### M5 - 多節點、Edge 與一般部署

- Edge 依 inventory 產生 frontend/backend upstream。
- backend/frontend 採 `serial: 1` rolling recreate，readiness 後才加入流量。
- worker 支援指定 host 或整組新增/更新，worker ID 唯一。
- config-only deploy 原子同步設定並只 recreate 指定角色。
- status/logs/restart/rollback 以 role/host 過濾。
- migration `run_once` 且先於 backend recreate；失敗立即停止。

完成定義：增加一個 IP 可加入新 worker；增加 frontend/backend IP 可健康註冊；部署期間其他健康節點持續服務。

### M6 - First deploy orchestrator、備份與演練

- `make first-deploy` 串接 M0-M5 checkpoint，但每一步仍可單獨重跑。
- 建立 Redis/MinIO/Registry restore runbook 與 Atlas restore 責任邊界。
- 提供首次部署報告：host、role、WG IP、image tag、health、TLS、backup 與單點警告。
- 在全新 staging VM 完成安裝、失敗續跑、config-only、加節點、rollback、重啟持久性與公網封鎖測試。
- live deployment 必須由使用者另行確認，不因完成程式碼而自動執行。

完成定義：從設定檔與空 VM 到完整 staging 可用，不登入遠端手動安裝或改設定。

## 3. 預計檔案落點

```text
apps/web/Dockerfile
apps/backend/Dockerfile.gateway
apps/backend/Dockerfile.worker
apps/backend/Dockerfile.ops
apps/backend/crawler/Dockerfile

deploy/prod/
  Makefile
  ansible.cfg
  deployment.yml.example
  secrets.vault.yml.example
  inventory/
  playbooks/
    bootstrap.yml
    first-deploy.yml
    deploy.yml
    database.yml
    status.yml
  roles/
    common/
    wireguard/
    registry/
    edge/
    frontend/
    backend/
    worker/
    redis/
    minio/
  templates/
  scripts/
    doctor.sh
    preflight.sh
    build.sh
    push.sh
    deploy.sh
```

現有單機 production scripts 在新流程驗收前不直接刪除；實作時先標示 legacy，確認沒有現役依賴後再決定移除，避免中斷既有環境。

## 4. 驗證矩陣

| ID | 驗證 |
|----|------|
| DEP-01 | 空 Ubuntu 24.04 無 Python VM 可 bootstrap |
| DEP-02 | bootstrap 重跑不清資料、不重建 secret |
| DEP-03 | 只填 SSH IP 即可自動配置 WireGuard IP |
| DEP-04 | Registry TLS/auth/persistence 正常 |
| DEP-05 | 應用 image 全由本機 build，遠端無 source/build |
| DEP-06 | Atlas migration/init/seed 順序正確且可重跑 |
| DEP-07 | seed 不覆寫既有管理員密碼 |
| DEP-08 | Redis/MinIO restart 後資料保留 |
| DEP-09 | config-only deploy 不產生新 image且新設定生效 |
| DEP-10 | backend/frontend rolling deploy 保留健康服務 |
| DEP-11 | 新增 worker IP 後只有新 host 被 bootstrap/deploy |
| DEP-12 | 新增 backend/frontend 後 Edge 只加入健康節點 |
| DEP-13 | migration 失敗時 backend 保持舊版 |
| DEP-14 | rollback 可切回上一個成功 image tag |
| DEP-15 | 公網無法直連 Redis/MinIO private API/backend/crawler |
| DEP-16 | crawler sidecar 可用且 host 無公開 crawler port |
| DEP-17 | Atlas 連線顯示固定出口 IP |
| DEP-18 | 備份與還原演練有可驗證結果 |

## 5. 風險與後續

| 風險 | 第一版處理 |
|------|------------|
| Redis/MinIO/Registry/Edge 單點 | 明示風險、持久化、備份、restore runbook；HA 後續里程碑 |
| 全 mesh 新增節點需更新 peers | 由 Ansible inventory 統一生成並分批套用 |
| Atlas 固定出口節點故障 | 第一版接受單點；文件化切換出口與 allowlist 流程 |
| Outbox 全域 lock 限制 worker 擴縮 | 部署不誤稱 Outbox 已水平擴充；另立應用 task 改 due query/claim/concurrency |
| 外部發文 at-least-once 重複 | 不在部署層假裝解決，保留既有 lease 風險並另案處理 |
| seeder 目前寫死管理員資料 | M3 先修成 Vault 可設定，再允許 first deploy seed |
| health 目前只檢查 Redis | M4 補 Mongo readiness，避免壞節點進 upstream |
| 舊單機腳本仍可能在使用 | 新流程先並存並標 legacy，不自動刪除或改動現役服務 |

## 6. 實作前仍需填入的實際值

- 所有 VM SSH IP、user、port、private key。
- Edge、Registry 網域與已完成的 DNS A/AAAA record。
- Atlas SRV URI、database user 與固定出口 allowlist。
- Registry、Redis、MinIO、JWT、加密、Crawler、Provider、SMTP 等 Vault secret。
- 初始管理員 email、display name、password。
- 各 VM CPU architecture、磁碟掛載與備份目的地。

上述值未備齊時可完成程式與 staging 測試，但不得執行正式 first deploy。
