# AWOOOI Ansible 運作模型

> 最後更新：2026-05-12（台北時間）
> 範圍：說明 Ansible 在 110 / 120 / 121 / 188 的運維、冷啟動恢復、監控與部署安全中扮演的角色。

## 產品架構定位

Ansible 是主機狀態收斂層，負責 Kubernetes 與 Docker 映像之外的主機狀態，包括檔案、套件、systemd units、cron、nginx 設定、node-exporter textfile monitor，以及主機層資源護欄。

Ansible 不取代下列系統：

- `k8s/` 之下的 Kubernetes manifests
- 各服務目錄自己管理的 Docker Compose application 定義
- 資料庫恢復決策
- AI 自動修復執行
- 緊急 console fsck

目標控制流程是：

```text
Git repo
  -> Ansible 驗證並收斂主機狀態
  -> Prometheus 觀測 host/app gate
  -> Alertmanager 發出告警
  -> AWOOOI/AwoooP AI 進行診斷與分流
  -> 涉及有狀態或高風險修復時交由人工批准
```

## 目前納管範圍

| 範圍 | 事實來源 | Runtime 目標 |
|---|---|---|
| 主機 inventory | `infra/ansible/inventory/hosts.yml` | 記錄 110 / 120 / 121 / 188 / 112 |
| 188 public nginx routes | `infra/ansible/roles/nginx/templates/*` + `playbooks/nginx-sync.yml` | `/etc/nginx/sites-enabled/*` |
| 110 Ollama proxy | `110-ollama-proxy.conf.j2` | `/etc/nginx/sites-enabled/110-ollama-proxy.conf` |
| 110 cold-start monitor | `roles/cold-start-monitor` | `/home/wooo/scripts`、cron、node-exporter textfile |
| 110 runner guardrails | `roles/runner-guardrails` | `actions.runner.*` systemd drop-ins |
| 110/188 Docker/systemd/storage/backup textfile exporters | `roles/host-textfile-exporters` | `/home/*/node_exporter_textfiles/docker_stats.prom`、`storage_health.prom`、`backup_health.prom`、110 `systemd_units.prom` |
| 110 Sentry backup / integrity drill | `110-devops.yml --tags backup_jobs` | `/backup/scripts/backup-sentry.sh`、`check-backup-integrity.sh`、weekly/monthly cron |
| 主機健康描述 | `110-devops.yml`、`188-ai-web.yml` | 只讀檢查與有限度主機狀態修復 |

## 必要流程

相關檔案變更後，Gitea workflow `.gitea/workflows/ansible-lint.yml` 會在 self-hosted runner 上執行 `scripts/ops/ansible-validate.sh` 與 `ansible-lint`。本地仍需先跑驗證，避免把明顯壞掉的 Ansible 變更推進 CI。

### 1. 本地驗證

任何 Ansible 變更前先執行：

```bash
bash scripts/ops/bootstrap-ansible-validation-env.sh --recreate
PATH="${ANSIBLE_VALIDATION_VENV:-/tmp/awoooi-ansible-venv}/bin:$PATH" \
  bash scripts/ops/ansible-validate.sh
```

`bootstrap-ansible-validation-env.sh` 會建立 pinned 驗證工具鏈：`ansible-core==2.17.14`、`ansible-lint==24.12.2`。如果本機沒有 `ansible-playbook`，`ansible-validate.sh` 仍會驗證 YAML 與 shell syntax，並明確提示已跳過 Ansible syntax-check；但重開機 SOP、CI 與接手稽核應使用 bootstrap venv，避免只做半套驗證。

若要稽核整個重開機恢復包是否齊全：

```bash
bash scripts/reboot-recovery/reboot-recovery-readiness-audit.sh --live --no-color
```

若要確認是否可以釋放 P3 高負載工作：

```bash
bash scripts/reboot-recovery/p3-controlled-release-gate.sh --no-color
```

### 2. 演練（`--check`）

從 repo root 執行：

```bash
ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbooks/site.yml --check
```

針對單一變更時：

```bash
ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbooks/nginx-sync.yml --tags 188 --check
ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbooks/110-devops.yml --tags cold_start_monitor --check
ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbooks/110-devops.yml --tags runner_guardrails --check
ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbooks/110-devops.yml --tags textfile_exporters --check
ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbooks/110-devops.yml --tags backup_jobs --check
ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbooks/188-ai-web.yml --tags textfile_exporters --check
```

### 3. 套用

只套用最小必要 tag：

```bash
ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbooks/nginx-sync.yml --tags 188
ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbooks/110-devops.yml --tags cold_start_monitor
ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbooks/110-devops.yml --tags runner_guardrails
ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbooks/110-devops.yml --tags textfile_exporters
ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbooks/110-devops.yml --tags backup_jobs
ansible-playbook -i infra/ansible/inventory/hosts.yml infra/ansible/playbooks/188-ai-web.yml --tags textfile_exporters
```

### 4. 事後驗證

Ansible apply 不等於完成；runtime gate 變綠才算完成：

```bash
SSH_BATCH_MODE=yes bash scripts/reboot-recovery/full-stack-cold-start-check.sh --send-alert-test
curl -kLsS -o /dev/null -w '%{http_code}\n' https://awoooi.wooo.work/api/v1/health
curl -kLsS -o /dev/null -w '%{http_code}\n' https://mo.wooo.work/health
```

## 冷啟動整合

重開機恢復時：

1. 主機卡在 initramfs 時，先用 console/fsck 讓主機乾淨開機。
2. 只在必要時人工恢復依賴鏈：188 data layer、110 registry/observability、K3s、public routes。
3. Stack 可達後，用 Ansible 把 live state 收回 repo/IaC。
4. 執行 cold-start gate。
5. Gate 變綠前，AI auto-repair 維持 observe-only。

Cold-start monitor 由下列 role/playbook 管理：

```text
infra/ansible/roles/cold-start-monitor
infra/ansible/playbooks/110-devops.yml --tags cold_start_monitor
```

它會寫入：

```text
/home/wooo/node_exporter_textfiles/cold_start_recovery.prom
/home/wooo/reboot-recovery/cold-start-last.log
```

## Dirty Reboot 與檔案系統防線

110 與 188 曾在重開機後停在 initramfs manual fsck，這一類問題不能只靠網站健康檢查發現。`roles/host-textfile-exporters` 現在也會部署 `storage-health-textfile-exporter.py`，每分鐘輸出：

```text
/home/wooo/node_exporter_textfiles/storage_health.prom
/home/ollama/node_exporter_textfiles/storage_health.prom
```

這個 exporter 只讀取 `/proc/mounts`、`/proc/stat`、`journalctl -k` 與 fsck logs，不會修復、不會重啟、不會寫資料庫。它提供 root filesystem 是否 read-only、目前 boot 是否有 storage/kernel error、上一個 boot 是否留下 dirty reboot/fsck 證據。Prometheus 的 `host_storage_health_alerts` 只告警與阻擋放量，所有 fsck/資料恢復仍需人工批准。

## 備份健康與設定檔備份

`roles/host-textfile-exporters` 也管理 `backup-health-textfile-exporter.py`。它每 10 分鐘輸出：

```text
/home/wooo/node_exporter_textfiles/backup_health.prom
/home/ollama/node_exporter_textfiles/backup_health.prom
```

這個 exporter 只讀取 cron、script path、restic snapshot metadata 與既有 textfile，不會執行備份或還原。它用來確認：

- 110 的 `/backup/scripts/backup-all.sh`、AWOOOI 高頻備份、`/backup/configs` 設定檔備份都存在且新鮮。
- 110 的 `/backup/sentry` 專屬資料層備份新鮮，並且 weekly `restic check` / monthly restore drill 有成功證據。
- 188 的 `backup-from-110` 與 momo PostgreSQL daily backup 都新鮮。
- 120 的 Velero schedule、latest Completed backup、`backup-restore-test` CronJob/Job 狀態可查。
- 預期 script 不缺、cron 不缺、最近 aggregate backup 沒有失敗項目。

設定檔備份由 `/backup/scripts/backup-configs.sh` 負責，納入每日 `backup-all.sh`。它會把 nginx、systemd、cron、Docker Compose、K3s manifests、K8s Secret/ConfigMap/RBAC、certs 與 runtime scripts 放進加密 restic repo `/backup/configs`。Secrets 只允許進加密備份，不得出現在 repo、log、Prometheus label 或告警訊息。

Sentry 資料層備份由 `/backup/scripts/backup-sentry.sh` 負責，納入每日 `backup-all.sh`。它會輸出 Sentry Postgres logical dump，並把 ClickHouse、Kafka、Redis、SeaweedFS、Taskbroker、Vroom、Symbolicator 等必要 state 放入加密 restic repo `/backup/sentry`。這是備份行為，不做 restore，也不停止 production stack。

備份可用性由 `/backup/scripts/check-backup-integrity.sh` 負責：

- 每週 `--mode check`：對預期 restic repos 執行 `restic check --read-data-subset=1%`。
- 每月 `--mode restore-drill`：從每個 repo 抽一個小檔案 `restic dump latest <sample>` 到 0700 暫存目錄，驗證 snapshot 可讀。
- 執行狀態寫入 `/backup/integrity/check.status` 與 `/backup/integrity/restore-drill.status`，由 `backup-health-textfile-exporter.py` 轉成 Prometheus metrics。

## 下一批納入 Ansible 的項目

| 優先級 | 項目 | 原因 |
|---|---|---|
| P0 | 110 runner guardrails | `roles/runner-guardrails` 已建立；下一步是在有 Ansible 的 ops host 做 live dry-run/apply 與 CI syntax-check |
| P0 | Sentry 專屬備份與 restic integrity drill | `backup_jobs` 已納入 110 playbook；下一步累積 nightly/weekly/monthly 成功證據 |
| P0 | 188 nginx HTTPS route ownership | 避免 public tool routes 在事故後或同步後再次漂移 |
| P1 | certbot/snap certbot 標準化 | 目前 apt certbot/OpenSSL 路徑脆弱，renewal 需要統一路徑 |
| P1 | 110/188 Docker/systemd/storage/backup textfile exporters | `roles/host-textfile-exporters` 已建立；下一步是在 ops host 上 dry-run/apply，並確認 `docker_stats.prom` / `storage_health.prom` / `backup_health.prom` / `systemd_units.prom` freshness |
| P1 | node-exporter/cAdvisor caps | 監控元件本身不能變成負載來源 |
| P2 | K3s diagnostic-only host tasks | 只驗證 containerd/kubelet 狀態，不做破壞性修復 |
| P2 | 112 Kali inventory only | 先記錄，不掃描、不修復 |

## 安全規則

- 預設先跑 `--check`。
- 用 tags 控制範圍；事故中避免直接套用完整 `site.yml`。
- 不把密碼寫進 repo、cron、inventory 或 group vars。
- 不讓 Ansible 執行 DB/ClickHouse/Kafka 的破壞性恢復。
- Ansible 只做可預期的主機狀態收斂，不處理未知資料修復。
- 任何有狀態 restart 或 quarantine 仍需人工批准。
- Runner guardrail role 預設不重啟 units；只有在計畫維護窗才設定 `runner_guardrails_restart_units=true`。

## 完成定義

Ansible 管理的變更必須全部符合下列條件，才算完成：

- `scripts/ops/ansible-validate.sh` 通過。
- 目標 playbook dry run 成功，或有文件化原因說明為何略過 dry run。
- 目標 apply 成功。
- 影響 runtime 的變更，`full-stack-cold-start-check.sh --send-alert-test` 必須變綠。
- 相關 public routes 或 service health endpoints 通過。
- `docs/LOGBOOK.md` 記錄套用範圍與驗證結果。
