Files
awoooi/docs/runbooks/FULL-STACK-COLD-START-SOP.md

1415 lines
81 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AWOOOI 全棧冷啟動與主機重啟 SOP
> Version: v1.20
> Last updated: 2026-06-14 Asia/Taipei
> Scope: 110 / 120 / 121 / 188 full-stack reboot recovery. 112 Kali is recorded as P3 optional and is not part of this recovery path.
---
## 0. 最新 Live Baseline 與釋出判定
本節是每次接手、開機、關機、重啟後的第一個判定錨點。若日期不是今天,必須先重跑 live check再更新本節與 `docs/workplans/2026-06-04-reboot-cold-start-backup-recovery-workplan.md`
| 項目 | 2026-06-14 18:15 Asia/Taipei live result | 判定 |
|------|-------------------------------------------|------|
| Overall recovery readiness | `97%` | `SERVICE_AVAILABLE_KM_VECTORIZE_FAILED_DR_ESCROW_BLOCKED` |
| P0 host / K3s recovery | `100%` | `DONE` |
| P1 backup / alert / escrow | `92%` | `BLOCKED_DR_ESCROW` |
| P2 service / data truth | `99%` | `VERIFIED_WORKLOAD_BALANCED_WITH_KM_WARN` |
| P3 docs / automation contracts | `100%` | `DONE_WITH_VALIDATION_GAP` |
| 110 host runtime | `fwupd-refresh.timer` intentionally disabled/inactive after non-runtime firmware metadata refresh failed units were classified; `systemctl --failed` returns `0 loaded units listed`; rollback is `sudo systemctl enable --now fwupd-refresh.timer` | `GREEN_WITH_FWUPD_TIMER_DISABLED` |
| 120 reachability | ping OK, SSH OK, boot around `2026-06-14 02:23`, K3s active, node `mon Ready` | `GREEN` |
| 121 reachability | ping OK, SSH OK, failed units `0` | `GREEN` |
| 188 host runtime | production services green; host degraded only by `certbot.service` and `snap.certbot.renew.service` | `GREEN_WITH_CERTBOT_DEBT` |
| K3s node state | `mon Ready control-plane`, `mon1 Ready control-plane`; latest readback keeps API/Web split across 120 / 121; bad pods `0`; retained failed Job `km-vectorize-29689620` is the only WARN | `GREEN_WORKLOAD_BALANCED_WITH_KM_WARN` |
| 110 -> 120 / 188 SSH trust | 00:33 cold-start exposed stale `known_hosts`; backup `/home/wooo/.ssh/known_hosts.before-120-188-refresh.20260613-003416`; final repair backup `/home/wooo/.ssh/known_hosts.before-120-188-final-refresh.20260613-011949`; CD fix `80e6ec1a` moves deploy trust to `/home/wooo/.ssh/deploy_known_hosts`; 01:28 global `known_hosts` still contains 120 / 188 and was not clobbered by deploy marker `e4a349bc` | `GREEN_WITH_GUARDRAIL` |
| Backup status | 18:14 status: 110 `13/13 fresh failed=0`, 188 `2/2 fresh failed=0`, `core_blockers=0`, `escrow_missing=5`, last aggregate `2026-06-14 02:40:22` | `GREEN_WITH_DR_ESCROW_WARNING` |
| Offsite sync / verify | 01:28 textfile: `awoooi_backup_offsite_remote_verify_ok=1`, `full_verify_fresh=1`, all 13 repos have `snapshot_count=1` and `snapshot_latest_only=1`; latest scheduled verifier log is 2026-06-12 07:20 | `GREEN` |
| Backup / cold-start alerts | 01:27 live visibility check confirms Prometheus and Alertmanager expose the 5 required credential escrow gap alerts; Prometheus rules API has all five required alert names healthy; label contract check loads 24 baseline backup alert rules | `GREEN_WITH_EXPECTED_REDLIGHTS` |
| Cold-start scorecard | 18:15 高價值配置 Owner Packet 前台同步後只讀 scorecard`PASS=82 WARN=1 BLOCKED=0`remaining WARN 仍是 K8s failed Job `km-vectorize-29689620`。Public routes / TLS、momo DB parity、backup exporters、120/121 K3s 與 AWOOOI API/Web 皆通過。 | `DEGRADED_NO_BLOCKERS` |
| momo DB parity | `4571|4571|2026-06-01|2026-06-07|2026-06-01|2026-06-07` | `GREEN` |
| momo scheduler | container healthy; scorecard reads `SCHEDULER_RECENT_ACTIVITY 1009`; detector widened and deployed to 110 | `GREEN` |
| ArgoCD app health | Latest live app source revision `0a4766ddc94b0690824ce3deba5c6b9a69764f94` remains `Synced / Degraded` because official Job `km-vectorize-29689620` failed and CronJob health reports `CronJob has not completed its last execution successfully`. Latest repo docs head is `0a4766dd`, but runtime image readback is from deploy marker `16c6b983` / feature `e999c16b`. CronJob `lastScheduleTime=2026-06-13T19:00:00Z` and `lastSuccessfulTime=2026-06-04T11:00:37Z`; failed Job is retained, but its failed Pod/log are not currently available, so the next official 03:00 run remains the required read-only evidence gate. | `WAITING_OFFICIAL_KM_VECTORIZE_RUN` |
| Workload balancing | Live API/Web/Worker/CronJob image is `e999c16b3435f197b78fe2adfeec1c4faa6c4675`; API/Web pods remain split across `mon` / `mon1`, Worker single replica remains healthy on `mon` | `GREEN` |
| Credential escrow | 5 non-secret evidence markers missing | `BLOCKED` |
Release rule:
```text
Do not declare full cold-start green unless the latest scorecard has `WARN=0` and `BLOCKED=0`.
Do not declare aggregate backup green unless latest `backup-status` has `core_blockers=0`.
Do not declare DR scorecard complete while credential escrow markers are missing.
```
2026-06-14 18:15 live rule:
```text
110 / 120 / 121 / 188 core service recovery remains available, but the latest 18:15 scorecard is DEGRADED because `WARN=1`.
GO for controlled runner/CD release; keep AI auto-remediation governed by normal gates.
NO-GO for "DR complete" while credential escrow evidence markers are missing.
Do not fake or silence credential escrow alerts; they are the remaining correct DR red light.
GO for "AWOOOI core workload balanced"; topology spread is in Gitea main / ArgoCD live and API/Web placement proves max skew <= 1.
NO-GO for "full cold-start green" until `km-vectorize` failed Job is cleared by an official successful run.
NO-GO for "ArgoCD fully healthy" until `km-vectorize` updates `lastSuccessfulTime` after an official scheduled Job, not a manual `UnexpectedJob`.
NO-GO for any CD workflow that writes deploy host keys into `/home/wooo/.ssh/known_hosts`; deploy jobs must use an isolated `UserKnownHostsFile=/home/wooo/.ssh/deploy_known_hosts`.
Current allowed wording: "core service and backup are available; 110 failed units are cleared after intentionally disabling `fwupd-refresh.timer`; high-value config Owner Packet 前台同步後 recovery readback shows no service regression; cold-start is degraded only by the `km-vectorize` official Job failure; DR complete still blocked by credential escrow; `km-vectorize` failed Job is retained but failed Pod/log are currently absent, so the next official 03:00 run remains the evidence gate."
```
After any future 120 recovery, rerun this exact chain from 110:
```bash
/backup/scripts/backup-configs.sh
/backup/scripts/backup-all.sh
/backup/scripts/sync-offsite-backups.sh --mode sync
/backup/scripts/verify-offsite-full-sync.sh --write-textfile --no-color
/home/wooo/scripts/full-stack-cold-start-check.sh --monitor-read-only --no-color --watch --interval 1 --max-attempts 1
```
## 0.1 When To Use This
Use this SOP when any of these happen:
- 110/120/121/188 reboot unexpectedly.
- All services are abnormal after a power/network event.
- K3s is stuck `activating`.
- Host load remains high during startup and service health is mixed.
- Monitoring, alerting, CD, AI auto-repair, and Docker Compose services disagree about the real state.
The rule is simple: **recover the dependency chain, not the loudest symptom.**
## 0.2 啟動判定分層
重啟後不能只用一個訊號宣稱完成。每台主機與整個平台都必須分四層判定:
| 層級 | 代表意義 | 最低證據 | 不代表 |
|------|----------|----------|--------|
| `HOST_POWERED` | 主機或 VM 看起來已通電 | console / hypervisor 顯示 running或 LAN ARP 開始出現 | OS 已完成開機 |
| `HOST_BOOTED` | OS 已進入可互動狀態 | ping OK、SSH port open、`who -b` 有本次 boot time | systemd / Docker / K3s 已健康 |
| `HOST_READY` | 主機基礎服務可承接下一層 | `systemctl is-system-running` 非 degradedfailed units 可解釋cron / docker / DB / K3s 依角色正常 | public route 或業務資料已正常 |
| `SERVICE_READY` | 主機承載服務可用 | 服務 health、port、container health、DB / Redis / K3s / Harbor / Alertmanager checks 通過 | 備份、排程、告警、資料一致性已驗證 |
| `FULL_STACK_GREEN` | 可以宣稱重啟恢復完成 | cold-start scorecard `WARN=0``BLOCKED=0`,備份/offsite/DB/告警/排程都綠 | 120 不可達時永遠不能宣稱 |
2026-06-12 的 110/120 事故收斂判定是:
```text
110 HOST_READY = yes
120 HOST_READY = yes
Core public services SERVICE_READY = yes
FULL_STACK_GREEN = yes, because cold-start scorecard is PASS=83 WARN=0 BLOCKED=0
DR_COMPLETE = no, because credential escrow evidence is incomplete
```
所有回報必須使用這組詞,避免把「服務面可用」誤報成「整體 DR 完成」。
---
## 1. Golden Startup Order
```text
0. Freeze automation and preserve evidence
1. Physical/network layer
2. 188 data layer
3. 110 registry/observability layer
4. 120/121 K3s layer
5. AWOOOI workload layer
6. Public routes and alert chain
7. High-load batch/consumer/crawler services
8. Runner/CD
9. AI auto-remediation
10. 112 Kali scanner, if needed
```
Never start runner/CD before 188 PostgreSQL, 110 Harbor, K3s nodes, and AWOOOI API are healthy.
### 1.1 Dependency Graph
```mermaid
flowchart TD
network["P0 network: LAN, ARP, SSH"] --> data188["188 data: PostgreSQL, Redis, momo DB, SignOz"]
network --> obs110["110 registry/observability: Harbor, Gitea, Prometheus, Alertmanager, Sentry"]
data188 --> k3s["120/121 K3s: server, agent, VIP, NodePorts"]
obs110 --> k3s
k3s --> workload["AWOOOI workload: API, Web, K8s Secrets"]
workload --> alertchain["Alert chain: Alertmanager webhook, Telegram"]
workload --> public["Public routes: awoooi.wooo.work, mo.wooo.work"]
public --> schedules["Schedules: cron, CronJobs, backups, exporters"]
schedules --> highload["High-load release: crawlers, Snuba, ClickHouse merges, runners/CD"]
highload --> ai["AI auto-remediation: limited execution"]
```
This is also captured in the machine-readable baseline:
```text
ops/reboot-recovery/full-stack-cold-start-baseline.yml
```
The YAML baseline is the source of truth for:
- hosts, roles, and SSH users
- phase ordering
- service startup dependencies
- endpoint success codes
- schedule freshness thresholds
- stateful-service protection boundaries
- AI automation release gates
### 1.2 Phase Gate Logic
Each phase has the same decision rule:
| Result | Meaning | Action |
|--------|---------|--------|
| `BLOCKED` | A dependency required by later phases is down. | Stop phase release and fix the first blocked gate. |
| `WARN` | Core dependency passed, but confidence is incomplete. | Continue diagnosis, but do not release runner/CD/AI full execution. |
| `GREEN` | All checks in scope passed. | Release the next phase only. |
The cold-start flow is intentionally conservative:
```text
P0 network green
-> P0 188 data green
-> P0 110 registry/observability green
-> P1 K3s green
-> P2 workload + alert chain green
-> P2 public routes green
-> P2 schedules green
-> P3 high-load services and runners/CD
-> AI auto-remediation limited execution
```
The final release condition is not "containers are running". It is:
```text
PASS > 0
WARN = 0
BLOCKED = 0
Result: GREEN
```
### 1.3 重啟 GO / NO-GO 決策樹
每次維護前先用這張表決定是否可以重啟,以及重啟後可以宣稱到哪個層級。
| 情境 | GO / NO-GO | 可做範圍 | 完成宣告上限 |
|------|------------|----------|--------------|
| 03:00 offsite sync 正在跑 | `NO-GO` | 只讀觀察,等待 sync 結束後 verifier | 不宣告維護完成 |
| 120 不可達,但只重啟 110 | `CONDITIONAL GO` | 只可宣稱 110 / public service recovery不可跑 120 backup fix | `SERVICE_READY`,不可 `FULL_STACK_GREEN` |
| 188 data layer 不健康 | `NO-GO` | 先修 PostgreSQL / Redis / Docker / SignOz / momo DB | 不釋出 K3s / runner / AI |
| 110 Harbor / Registry 不健康 | `NO-GO for K3s deploy` | 先修 registryK3s 可能 image pull 失敗 | 不釋出 CD / deploy |
| 120 / 121 都 Readyoffsite verifier 綠 | `GO` | 可做完整 cold-start release chain | 需 scorecard `WARN=0 BLOCKED=0` |
| credential escrow marker 缺失 | `GO for service reboot``NO-GO for DR complete` | 可恢復服務;不可宣稱 DR scorecard complete | `SERVICE_READY``BLOCKED_DR_ESCROW` |
| Alertmanager required rules 不可見 | `NO-GO for unattended window` | 先修 alert rules / drift guard | 不釋出 AI auto-remediation |
GO 只代表允許執行指定範圍,不代表完成。完成一定要回到 §15 Done Criteria。
---
## 2. Automation Freeze
Cold start creates noisy metrics and partial failures. During P0/P1, keep automation in observe-only mode.
| Item | Cold-start policy | Reason |
|------|-------------------|--------|
| Gitea/GitHub runners | Last | Build jobs can saturate 110 CPU/RAM. |
| momo-scheduler / crawlers | Last | Chrome and batch work can saturate 188. |
| Sentry/Snuba consumers | Controlled | Kafka backlog and ClickHouse merge can create temporary high load. |
| Alertmanager outbound notification | Gate | Avoid alert storms before API webhook and Telegram are verified. |
| AI auto-repair | Observe-only | Metrics, Redis, KM, and playbooks may be incomplete. |
| Stateful DB restart | Human approval | PostgreSQL, Redis, ClickHouse, Harbor DB, Sentry DB are not generic restart targets. |
### 2.1 Freeze 執行清單
進入維護窗口後,先把「會放大事故」的來源降到 observe-only 或延後釋出。若沒有做到這一步,後續負載和告警會很難判讀。
| 順序 | 對象 | 只讀確認 | 允許動作 | 禁止動作 |
|------|------|----------|----------|----------|
| 1 | runner / CD | `systemctl list-units "actions.runner.*"`、Gitea Actions running jobs | 暫停新 job、等待可完成 job 結束 | 重啟 Docker daemon 來中斷 job |
| 2 | AI auto-remediation | Prometheus / Alertmanager / cold-start monitor 狀態 | 切 observe-only、保留告警 | 自動 restart stateful service |
| 3 | momo scheduler / crawler | container health、recent logs、DB parity | 延後 heavy import、保留現有資料 | 在 DB 未綠時強行匯入 |
| 4 | Sentry / Snuba | ClickHouse / Kafka health、consumer restart loop | 控制 consumer 釋出順序 | generic compose down/up 全套重啟 |
| 5 | K3s workload | node readiness、pods、events | 依 node 狀態 cordon/drain | 120 不可達時宣稱 drain 成功 |
多個工作視窗同時處理事故時,第一優先是避免互相打斷:只要有人在收斂 Docker / Nginx / firewall / K3s 寫操作,其他視窗先只讀觀察,直到明確交接。
### 2.2 CD / SSH Trust Guardrail
2026-06-13 的冷啟動假紅燈顯示CD workflow 若用 `ssh-keyscan ... > /home/wooo/.ssh/known_hosts`,會覆蓋 110 使用者層的全域 SSH trust導致 110 到 120 / 188 的 strict SSH 檢查失敗。這會把實際已恢復的主機誤判成 blocked。
固定規則:
| 項目 | 正確做法 | 禁止 |
|------|----------|------|
| Deploy 專用 host key | 寫入 `/home/wooo/.ssh/deploy_known_hosts` | 寫入或覆蓋 `/home/wooo/.ssh/known_hosts` |
| Deploy SSH options | `-o UserKnownHostsFile=/home/wooo/.ssh/deploy_known_hosts` | 共用 operator / cold-start 的 `known_hosts` |
| 冷啟動 SSH trust | 保留 120 / 188 的已驗證 fingerprint修復前先備份 | 無 fingerprint 交叉驗證就 `ssh-keygen -R` 或重建全檔 |
| 驗證 | CD 後檢查 `known_hosts` mtime、120 / 188 entries、strict SSH | 只看 CD success badge |
2026-06-13 修復錨點:
- Source fixGitea main 包含 `80e6ec1a fix(ci): avoid clobbering runner known hosts`
- Deploy marker`e4a349bc chore(cd): deploy 414413a [skip ci]` 後,`/home/wooo/.ssh/known_hosts` mtime 仍停在 `2026-06-13 01:20:02 +0800`,未被 CD 覆蓋。
- Deploy isolated file`/home/wooo/.ssh/deploy_known_hosts` mtime `2026-06-13 01:24:05 +0800`
- Global strict entries120 ED25519 line 4、188 ED25519 line 5 仍存在strict SSH 到 `wooo@192.168.0.120``ollama@192.168.0.188` 必須通過。
---
## 3. P0 Evidence And Network
Run from any machine on the same LAN:
```bash
for h in 110 120 121 188; do
ping -c 2 -W 2 192.168.0.$h >/dev/null && echo "PING_OK 192.168.0.$h" || echo "PING_FAIL 192.168.0.$h"
done
arp -an | grep -E '192\.168\.0\.(110|120|121|188)'
for h in 110 120 121 188; do
nc -G 3 -z 192.168.0.$h 22 && echo "SSH_OK 192.168.0.$h" || echo "SSH_FAIL 192.168.0.$h"
done
```
Then capture reboot evidence:
```bash
ssh ollama@192.168.0.188 'hostname; date; uptime; who -b; last -x reboot shutdown | head -20'
ssh wooo@192.168.0.110 'hostname; date; uptime; who -b; last -x reboot shutdown | head -20'
ssh wooo@192.168.0.120 'hostname; date; uptime; who -b; last -x reboot shutdown | head -20'
ssh wooo@192.168.0.121 'hostname; date; uptime; who -b; last -x reboot shutdown | head -20'
```
If any host has ARP `incomplete` or SSH port down, stop here and fix physical/network first.
### 3.1 主機已啟動判定標準
每台主機重啟後先跑「四段式啟動判定」。只有全部符合角色期望,才進入服務恢復。
```bash
for h in 110 120 121 188; do
ip="192.168.0.$h"
echo "=== $ip ==="
ping -c 2 -W 2 "$ip" >/dev/null && echo "HOST_POWERED_OR_LAN_OK=1" || echo "HOST_POWERED_OR_LAN_OK=0"
arp -an | grep "$ip" || true
nc -G 3 -z "$ip" 22 && echo "SSH_PORT_OPEN=1" || echo "SSH_PORT_OPEN=0"
done
```
可 SSH 後:
```bash
ssh wooo@192.168.0.110 'hostname; date; who -b; uptime; systemctl is-system-running || true; systemctl --failed --no-pager --plain || true; free -h; swapon --show'
ssh wooo@192.168.0.121 'hostname; date; who -b; uptime; systemctl is-system-running || true; systemctl --failed --no-pager --plain || true'
ssh ollama@192.168.0.188 'hostname; date; who -b; uptime; systemctl is-system-running || true; systemctl --failed --no-pager --plain || true; free -h'
```
120 若不可 SSH狀態只能是 `HOST_POWERED_UNKNOWN``HOST_BOOTED_UNKNOWN`,必須走 console / VM / network 檢查,不可用 Kubernetes stale node object 代替主機現況。
| 判定 | 必要條件 | 下一步 |
|------|----------|--------|
| `HOST_BOOTED` | ping 或 ARP 有回應、SSH port open、`who -b` 顯示本次 boot time | 檢查角色服務 |
| `HOST_READY` | `systemctl is-system-running``running`,或 degraded units 已逐一解釋且不影響本 host 角色 | 進入服務層驗證 |
| `HOST_DEGRADED` | failed units 存在且影響本 host 角色,或 swap 滿、root readonly、boot storage error | 先修 host不釋出下一層 |
| `HOST_UNREACHABLE` | ping/SSH/ARP 失敗 | 停止遠端修復假設,改 console/VM/network |
2026-06-12 110 事故教訓failed unit 若指向不存在的 legacy 路徑,要先確認是否仍屬現役服務。停用 stale timer 可以解除 host degraded但必須同步 source-of-truth 後續清理,不能靠反覆 `reset-failed` 掩蓋。
---
## 4. P0 188 Data Layer
188 is the first real service dependency because K3s datastore and AWOOOI DB depend on PostgreSQL.
### 4.1 Startup order
1. `containerd`
2. `docker`
3. `postgresql@14-main`
4. `k3s_datastore.kine` maintenance
5. `redis-server` on `6380`
6. `ollama` or current AI proxy dependencies
7. `nginx`
8. Docker networks
9. MinIO / OpenClaw / SignOz
10. momo / litellm / batch services after load is stable
### 4.2 Read-only check
```bash
ssh ollama@192.168.0.188 '
hostname; date; uptime; free -h
systemctl is-active containerd docker postgresql@14-main redis-server ollama nginx || true
pg_isready -h localhost -p 5432 || true
redis-cli -p 6380 ping 2>/dev/null || redis-cli ping 2>/dev/null || true
docker ps --format "{{.Names}}\t{{.Status}}\t{{.Ports}}" | head -120
'
```
### 4.3 PostgreSQL WAL checkpoint damage
Signature:
```text
PANIC: could not locate a valid checkpoint record
invalid primary checkpoint record
unexpected pageaddr ... in log segment ...
```
This blocks:
- `188:5432`
- K3s startup on 120/121
- AWOOOI API DB access
- Alertmanager webhook if API cannot start
Human-approved recovery command on 188:
```bash
sudo systemctl stop postgresql@14-main
sudo install -d -m 700 -o postgres -g postgres /var/backups/postgresql
sudo tar -C /var/lib/postgresql/14 -czf /var/backups/postgresql/14-main-before-pg-resetwal-$(date +%Y%m%d-%H%M%S).tgz main
sudo -u postgres /usr/lib/postgresql/14/bin/pg_resetwal -f /var/lib/postgresql/14/main
sudo systemctl start postgresql@14-main
pg_isready -h localhost -p 5432
sudo -u postgres psql -d k3s_datastore -c "VACUUM ANALYZE kine;"
```
Do not run `DROP`, reinitialize the cluster, delete `/var/lib/postgresql`, or restore an old backup unless the commander explicitly approves it.
---
## 5. P0/P1 110 Registry And Observability
110 must recover Harbor/Gitea/Monitoring early, but runners last.
### 5.1 Startup order
1. `docker`
2. Remove `Exited (128)` / `Exited (137)` orphan containers
3. Harbor `harbor-log`
4. Harbor full stack
5. Gitea
6. Prometheus / Alertmanager / Grafana / exporters
7. Langfuse
8. SignOz
9. Sentry DB layer
10. Sentry web/worker/consumer layer
11. Gitea host runner and actions runners
### 5.2 Checks
```bash
ssh wooo@192.168.0.110 '
hostname; date; uptime; free -h
systemctl is-active docker || true
curl -s -o /dev/null -w "harbor=%{http_code}\n" --max-time 5 http://127.0.0.1:5000/v2/ || true
curl -s -o /dev/null -w "gitea=%{http_code}\n" --max-time 5 http://127.0.0.1:3001/ || true
curl -s --max-time 5 http://127.0.0.1:9090/-/ready || true
curl -s --max-time 5 http://127.0.0.1:9093/-/healthy || true
curl -s -o /dev/null -w "sentry=%{http_code}\n" --max-time 10 http://127.0.0.1:9000/ || true
docker ps --format "{{.Names}}\t{{.Status}}" | head -120
'
```
Harbor healthy means `/v2/` returns `200` or `401`. Do not treat `401` as failure.
### 5.3 Runner gate
Runner may start only after all are true:
- `188 PostgreSQL` ready
- `110 Harbor` ready
- `110 Gitea` ready
- `120/121 K3s` nodes ready
- AWOOOI API health passes
- 110 load/core is below `1.0` for at least 15 minutes
- runner systemd guardrails are active: `CPUQuota=200%`, `MemoryMax=2G`, `WatchdogUSec=0`
Check:
```bash
ssh wooo@192.168.0.110 '
for u in $(systemctl list-units "actions.runner.*" --all --no-legend --plain | awk "{print \$1}"); do
echo "=== $u ==="
systemctl show "$u" -p ActiveState -p SubState -p CPUQuotaPerSecUSec -p MemoryMax -p WatchdogUSec -p NRestarts
done
'
```
If `WatchdogUSec` is not `0`, apply the guardrail script manually with sudo:
```bash
sudo /home/wooo/scripts/apply-runner-systemd-guardrails.sh --apply
```
---
## 6. P1 120/121 K3s
K3s must wait for 188 PostgreSQL and 110 Harbor.
### 6.1 Startup order
1. 120 `k3s.service`
2. 121 `k3s.service`, `k3s-agent.service`, or its live role
3. CNI / kube-proxy
4. Nodes Ready
5. Core pods
6. `awoooi-prod` pods
7. keepalived VIP `192.168.0.125`
8. NodePorts `32334` and `32335`
### 6.2 Checks
```bash
ssh wooo@192.168.0.120 '
hostname; uptime
pg_isready -h 192.168.0.188 -p 5432 || true
systemctl is-active k3s k3s-agent keepalived 2>/dev/null || true
kubectl get nodes -o wide 2>/dev/null || true
kubectl get pods -A 2>/dev/null | grep -v -E "Running|Completed" || true
kubectl get pods -n awoooi-prod -o wide 2>/dev/null || true
ip addr show | grep 192.168.0.125 || true
'
ssh wooo@192.168.0.121 '
hostname; uptime
systemctl is-active k3s k3s-agent keepalived 2>/dev/null || true
ip addr show | grep 192.168.0.125 || true
'
```
If K3s is `activating` while 188 PostgreSQL is down, fix PostgreSQL first. Restarting K3s repeatedly will not solve it.
### 6.3 120 / 121 AA / AS 與負載平衡判定
2026-06-12 15:19 live check 確認 120 / 121 都是 K3s `control-plane`,且兩台都是 `k3s active``k3s-agent inactive`。因此它們是 K3s 控制面 AA不是傳統一主一從 AS。
但控制面 AA 不等於業務 workload AA。120 剛從 root filesystem fsck 恢復後,大多數 ArgoCD / AWOOOI / Velero / kube-system workload 仍集中在 121120 主要只有 DaemonSet 類 Pod。每次 120 / 121 重啟或恢復後,都要額外跑 Pod 落點檢查:
```bash
ssh wooo@192.168.0.120 '
sudo kubectl get nodes -o wide
sudo kubectl get pods -A -o wide
sudo kubectl top nodes 2>/dev/null || true
sudo kubectl top pods -A --sort-by=cpu 2>/dev/null | head -30 || true
'
```
判定規則:
| 判定 | 條件 | 可宣稱 |
|------|------|--------|
| `K3S_CONTROL_PLANE_AA` | 120 / 121 都是 `Ready control-plane` | 控制面雙節點可用 |
| `WORKLOAD_IMBALANCED` | 主要 deployment / pod 都落在單一節點 | 不可宣稱服務 AA需排程治理 |
| `WORKLOAD_BALANCED` | replicas >= 2 的核心 API / Web 跨 120 / 121 分散 | 可宣稱承載層分散 |
| `STATEFUL_AA` | storage replication、backup / restore drill、failover drill 都通過 | 才可宣稱資料層 AA |
負載平衡與遷移評估的正式基準文件是 `docs/runbooks/HOST-ROLE-LOAD-BALANCING-ASSESSMENT.md`。恢復期先完成 P0 備份鏈與 cold-start scorecard再做 topology spread 或服務搬遷。
---
## 7. P2 AWOOOI Workloads
Run after K3s nodes are Ready:
```bash
ssh wooo@192.168.0.120 '
kubectl get deploy -n awoooi-prod
kubectl get pods -n awoooi-prod -o wide
kubectl get svc -n awoooi-prod
kubectl get events -n awoooi-prod --sort-by=.lastTimestamp | tail -40
'
curl -s --max-time 8 http://192.168.0.125:32334/api/v1/health
curl -s -o /dev/null -w "web=%{http_code}\n" --max-time 8 http://192.168.0.125:32335/
```
If pods are `ImagePullBackOff`, go back to 110 Harbor.
If API health fails because DB/Redis is down, go back to 188.
---
## 8. P2 Alert Chain
Current main path:
```text
Prometheus/Alertmanager on 110
-> http://192.168.0.125:32334/api/v1/webhooks/alertmanager
-> AWOOOI API
-> TelegramGateway
-> Telegram
```
Alertmanager health alone is not enough. Run E2E:
```bash
curl -s -X POST http://192.168.0.125:32334/api/v1/webhooks/alertmanager \
-H 'Content-Type: application/json' \
-d '{"receiver":"cold-start-test","status":"firing","alerts":[{"status":"firing","labels":{"alertname":"ColdStartE2ETest","severity":"info"},"annotations":{"summary":"Cold start E2E test, ignore"},"startsAt":"2026-05-05T11:00:00Z","endsAt":"0001-01-01T00:00:00Z","generatorURL":""}],"groupLabels":{},"commonLabels":{},"commonAnnotations":{},"externalURL":"","version":"4","groupKey":"cold-start-test"}'
```
Expected: API returns success and Telegram receives the test alert.
---
## 9. P2 Schedules And Delayed Work
Do not mark the reboot complete until scheduled work is proven runnable. A container can be healthy while its cron path is broken.
| Host / Layer | Required check | Success baseline |
|--------------|----------------|------------------|
| 188 cron | `systemctl is-active cron` and `crontab -l` | cron active; backup, restart exporter, stats exporter entries present |
| 188 backup-from-110 | `backup_110_last_success_timestamp` in textfile/Prometheus | last success age `< 25h` |
| 188 momo-scheduler | `docker inspect momo-scheduler` and `docker logs --since 6h momo-scheduler` | container `running healthy`; `全部排程任務已註冊`; Google Drive auth works; dashboard URLs use container-reachable hostnames |
| 188 momo import | manual `run_auto_import_task()` after parser changes | selected sheet is `即時業績明細`; imported date range has matching rows in `daily_sales_snapshot` and `realtime_sales_monthly` |
| 110 cron | `systemctl is-active cron` | cron active; Docker/systemd textfile exporters fresh |
| 110 startup units | `systemctl --failed` | zero failed units; stale `momo-startup-complete` and `wooo-staggered-startup` disabled |
| 120 K8s CronJobs | `kubectl get cronjobs -n awoooi-prod` | unsuspended; no failed Jobs remain after current validation |
| 121 DR drill | `crontab -l` | DR drill cron present unless explicitly paused |
Useful checks:
```bash
ssh ollama@192.168.0.188 'systemctl is-active cron; crontab -l; ls -l /home/ollama/node_exporter_textfiles/*.prom'
ssh wooo@192.168.0.110 'systemctl --failed --no-pager; systemctl is-active cron; crontab -l'
ssh wooo@192.168.0.120 'sudo kubectl get cronjobs,jobs -n awoooi-prod'
ssh wooo@192.168.0.121 'systemctl is-active cron; crontab -l'
```
If a schedule succeeds but emits a false verification alert, fix the verification rule before releasing AI auto-remediation. False positives train operators to ignore real alarms.
---
## 10. P2/P3 Stateful Service Guardrails
| Tier | Examples | Automation |
|------|----------|------------|
| BLOCK | PostgreSQL data dir, ClickHouse data dir, Harbor DB, Sentry DB | No automatic destructive action. Human approval only. |
| CRITICAL_HITL | Redis, Kafka, MinIO, SignOz ClickHouse, Sentry ClickHouse | Human-in-the-loop restart/repair. |
| STANDARD_HITL | API/Web/worker, OpenClaw, litellm | Restart only with evidence and blast-radius check. |
| AUTO | Stateless exporters, blackbox, nginx exporter | Auto restart allowed after verification. |
Never use generic `docker restart $(docker ps -q)` during cold start.
### 10.1 Dirty-Reboot Storage Corruption
Treat these log signatures as storage corruption, not ordinary service flakiness:
- `Bad message`
- `Structure needs cleaning`
- `Unknown codec`
- `PANIC: could not locate a valid checkpoint record`
- Kafka `Malformed line` in checkpoint files
- ClickHouse `broken and needs manual correction`
Cold-start automation may stop a restart storm and collect evidence, but it must not delete the original data directory. If a filesystem returns `Bad message` or `Structure needs cleaning`, the real root cause is below the container layer. Online recovery can restore service from readable data, but complete historical recovery requires an offline filesystem check or backup restore.
### 10.2 ClickHouse Clean-Clone Recovery Pattern
Use this pattern for Sentry ClickHouse or SignOz ClickHouse when individual corrupted parts cannot be moved because the host filesystem rejects reads.
```text
1. Stop the compose stack or at least stop dependent consumers.
2. Disable restart loops for the failing container.
3. Save logs and build an exclude list from unreadable store paths.
4. Preserve the original volume as _data.corrupt-YYYYMMDD-HHMMSS.
5. Create a clean _data clone with readable files only.
6. Add flags/force_restore_data.
7. Start ClickHouse first, then web/API, then consumers.
8. Verify HTTP, merge backlog, and restart count before releasing high-load services.
```
Do not replace this with `rm -rf store/...` unless the unreadable path is already backed up or the commander explicitly accepts data loss. The preferred incident artifact is:
```text
/var/lib/docker/volumes/<volume>/_data.corrupt-YYYYMMDD-HHMMSS
/var/backups/<service>-<component>-YYYYMMDD-HHMMSS
```
### 10.3 Kafka Checkpoint Recovery Pattern
If Kafka refuses to start with malformed checkpoint files after a dirty reboot, preserve and move only checkpoint files:
```text
log-start-offset-checkpoint
recovery-point-offset-checkpoint
replication-offset-checkpoint
```
Then start Kafka and confirm health before starting Snuba/Sentry consumers. Do not delete topic directories or Kafka logs during cold-start recovery.
---
## 11. P3 High-Load Services
Only release these after P0/P1/P2 gates are green:
| Host | Service | Release condition |
|------|---------|-------------------|
| 188 | momo-scheduler / crawler | load/core < 1.0 for 15 minutes and DB healthy |
| 188 | SignOz ClickHouse | healthy and merge backlog trending down |
| 188 | litellm | `/health/liveliness` good and provider route verified |
| 110 | Sentry Snuba consumers | ClickHouse healthy and Kafka backlog decreasing |
| 110 | Sentry uptime-checker | Sentry web/DB healthy |
| 110 | runners | all previous gates green and load/core < 1.0 for 15 minutes |
---
## 12. Baseline And AI Auto-Remediation Gate
### 12.1 Stable Runtime Baseline
These are release gates after the first cold-start recovery pass:
| Area | Baseline |
|------|----------|
| 188 host | PostgreSQL accepting, Redis PONG, momo `/health` 200, SignOz HTTP reachable, load/core < 1.0 sustained before crawlers |
| 110 host | Harbor `/v2/` 200/401, Gitea 200/302, Prometheus ready, Alertmanager healthy, Sentry HTTP 200/302/400, no ClickHouse/Kafka restart loop |
| K3s | 120/121 nodes Ready, VIP `192.168.0.125` present, AWOOOI API 2xx/3xx, Web 2xx/3xx |
| Public routes | `https://awoooi.wooo.work/api/v1/health` 2xx/3xx, `https://mo.wooo.work/health` 2xx/3xx |
| Guardrails | Docker/systemd textfile exporters fresh, runner `CPUQuota=200%`, `MemoryMax=2G`, `WatchdogUSec=0` |
| Schedules | cron active on 110/188/120/121; K8s CronJobs unsuspended; no current failed Jobs; 188 backup success `< 25h` |
| Backlog | ClickHouse merges and Kafka/Snuba lag trending down, not increasing for two consecutive checks |
If service health is green but load average remains high, check live CPU and IO before changing memory limits. High load after Sentry/Snuba or ClickHouse startup can be backlog drain; high CPU from runners/builds/crawlers is a release-order problem.
### 12.2 AI Auto-Remediation Gate
AI auto-repair can move from observe-only to limited execution only after:
- Prometheus rules are loaded.
- docker/systemd textfile exporter files are fresh.
- blackbox probes have stable results.
- cron/CronJob schedule checks are green.
- AWOOOI API `/api/v1/health` passes.
- Alertmanager E2E webhook passes.
- Redis/KM/playbook health is available.
- No active restart storm.
- Host load/core remains below `1.0` for 15 minutes.
Until then:
- diagnose only
- notify only
- require human approval for remediation
- no DB/ClickHouse/Harbor/Sentry destructive action
- no generic restart action against stateful services
---
## 13. One-Command Readiness Script
### 13.1 Single Pass
Run this when you want one read-only snapshot:
```bash
bash scripts/reboot-recovery/full-stack-cold-start-check.sh
```
The script is read-only. It does not restart services, delete data, change memory/CPU limits, or patch Kubernetes. It reports gates:
- `P0-NETWORK`
- `P0-188-DATA`
- `P0-110-REGISTRY-OBSERVABILITY`
- `P1-K3S`
- `P2-WORKLOAD-ALERTCHAIN`
- `P2-PUBLIC-ROUTES`
- `P2-SCHEDULES`
- runner guardrail state inside `P0-110-REGISTRY-OBSERVABILITY`
If it prints `BLOCKED`, fix the first blocked gate before moving forward.
### 13.2 Professional Watch Mode
Run this after a full reboot when you want the machine to keep checking until the whole stack is ready:
```bash
bash scripts/reboot-recovery/full-stack-cold-start-check.sh \
--watch \
--interval 60 \
--max-attempts 30 \
--send-alert-test
```
This is the standard next-reboot release command. It checks every 60 seconds for up to 30 attempts and exits only when the stack is `GREEN` or the last attempt remains degraded/blocked.
Use `--send-alert-test` for final release because the alert chain is not proven until the AWOOOI Alertmanager webhook accepts a real POST. Without `--send-alert-test`, the script intentionally leaves a warning so operators do not falsely mark alerting as complete.
### 13.3 Persistent Read-Only Monitor
After recovery, host 110 should run the same gate as a node-exporter textfile monitor:
```bash
bash scripts/reboot-recovery/install-cold-start-monitor-110.sh
```
This installs two scripts under `/home/wooo/scripts/`, adds a marked user-cron block, and writes:
- `/home/wooo/node_exporter_textfiles/cold_start_recovery.prom`
- `/home/wooo/reboot-recovery/cold-start-last.log`
The cron path uses `--monitor-read-only`, so it does not POST Alertmanager smoke events every 10 minutes. It converts the cold-start gate into Prometheus metrics:
- `awoooi_cold_start_monitor_up`
- `awoooi_cold_start_pass_gates`
- `awoooi_cold_start_warn_gates`
- `awoooi_cold_start_blocked_gates`
- `awoooi_cold_start_last_run_timestamp`
- `awoooi_cold_start_last_green_timestamp`
- `awoooi_cold_start_last_result{result="green|degraded|blocked|check_failed"}`
Prometheus rules in `ops/monitoring/alerts-unified.yml` alert when the monitor is missing, stale, blocked, degraded, or has not been green for more than 6 hours.
### 13.4 Script-To-SOP Coverage Map
| Script gate | SOP coverage | Blocks |
|-------------|--------------|--------|
| `P0-NETWORK` | host reachability, ARP, SSH | every later phase |
| `P0-188-DATA` | PostgreSQL, Redis, momo, SignOz | K3s, AWOOOI API, momo public site |
| `P0-110-REGISTRY-OBSERVABILITY` | Harbor, Gitea, Prometheus, Alertmanager, Sentry, runner quotas | image pulls, CD, alert rules, runners |
| `P1-K3S` | 120/121 K3s, VIP, node readiness, pod health | workload and webhook health |
| `P2-WORKLOAD-ALERTCHAIN` | AWOOOI API/Web, Alertmanager webhook | AI auto-remediation and alert confidence |
| `P2-PUBLIC-ROUTES` | external AWOOOI and momo URLs | external release |
| `P2-SCHEDULES` | cron, CronJobs, backups, textfile exporters, DR drill | final done criteria |
### 13.5 Next-Reboot Operator Contract
1. Run the watch command above.
2. If it stops at `BLOCKED`, repair the first blocked gate and rerun watch mode.
3. If it stops at `WARN`, do not release runner/CD/AI full execution; clear or explicitly accept each warning.
4. Release high-load services only after `GREEN` and load/core stays below `1.0` for 15 minutes.
5. Record the final output summary and any manual repair in `docs/LOGBOOK.md`.
### 13.6 2026-05-29 補充188 Public Gateway 與備份告警
`aiops.wooo.work` 的 188 public gateway 不可再指向單一 `192.168.0.120:31234/31235`。120 失聯時這會讓 public route 直接 502。正式 baseline 必須走 K3s VIP
```nginx
location /api/ {
proxy_pass http://192.168.0.125:32334/api/;
}
location /api/v1/ws {
proxy_pass http://192.168.0.125:32334/api/v1/ws;
}
location / {
proxy_pass http://192.168.0.125:32335;
}
```
變更來源必須是 `infra/ansible/roles/nginx/templates/188-all-sites.conf.j2`,再用 `infra/ansible/playbooks/nginx-sync.yml` 收斂;禁止只改 188 live 檔而不回寫 Ansible baseline。
備份告警有兩層,缺一不可:
- `ops/monitoring/alerts-unified.yml` 是 repo canonical。
- 110 live `/home/wooo/monitoring/alerts.yml``/home/wooo/monitoring/alerts-unified.canonical.yml` 必須一致,否則 `prometheus-rule-drift-guard` 可能把規則拉回舊版。
重啟後必查:
```bash
curl -s http://127.0.0.1:9090/api/v1/rules \
| python3 -c 'import json,sys; d=json.load(sys.stdin); names=[r.get("name") for g in d["data"]["groups"] for r in g["rules"]]; print([n for n in ["BackupAggregateRunFailed","BackupConfigCapturePartial","BackupOffsiteCopyStale","BackupCredentialEscrowEvidenceMissing","ColdStartRecoveryBlocked"] if n not in names])'
cat /home/wooo/node_exporter_textfiles/prometheus_rule_drift_guard.prom
```
若 120 尚未恢復,`BackupConfigCapturePartial{target="120-k3s-host-configs"}` 與 cold-start blocked 是正確訊號不可消音。120 恢復後再重跑:
```bash
/backup/scripts/backup-configs.sh
/backup/scripts/backup-all.sh
/backup/scripts/sync-offsite-backups.sh --mode sync
/backup/scripts/verify-offsite-full-sync.sh --write-textfile --no-color
```
### 13.7 2026-05-29 補充momo PostgreSQL Index 與資料同步
`mo.wooo.work` 不能只看 `/health` 或首頁 200。重啟或 fsck 後PostgreSQL index 可能讓匯入流程表面完成,但 `daily_sales_snapshot` 未同步到 `realtime_sales_monthly`。本次症狀:
- `daily_sales_snapshot` 已有 2026-05-01 到 2026-05-28 的 17,353 筆。
- `realtime_sales_monthly` 同日期範圍為 0 筆。
- momo-scheduler log 出現 PostgreSQL 內部錯誤 `posting list tuple ... cannot be split`
標準處理順序:
```bash
# 188 / momo-db只重建索引不刪資料
docker exec -i momo-db bash -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1' <<'SQL'
REINDEX TABLE CONCURRENTLY public.realtime_sales_monthly;
SQL
```
重建索引後,才可針對缺漏日期做 idempotent 補同步。正式作法必須先確認 `realtime_sales_monthly` 該日期範圍筆數,若非 0需先保存查詢結果並確認是否重跑同範圍同步不可整表 truncate、不可整庫 restore。補同步後至少驗證
```sql
SELECT count(*), min(snapshot_date::date), max(snapshot_date::date)
FROM daily_sales_snapshot
WHERE snapshot_date::date BETWEEN DATE '2026-05-01' AND DATE '2026-05-28';
SELECT count(*), min("日期"::date), max("日期"::date)
FROM realtime_sales_monthly
WHERE "日期"::date BETWEEN DATE '2026-05-01' AND DATE '2026-05-28';
```
兩張表同日期範圍筆數與日期上下界必須一致。完成後清除 momo 應用 cache
```bash
docker exec momo-pro-system python -c 'from services.cache_service import clear_all_cache; clear_all_cache(); print("cache_cleared")'
```
---
## 14. 主機開機、關機、重啟 SOP
本節是每次 110 / 120 / 121 / 188 相關電源操作的標準程序。112 是 Kali只保留 read-only evidence不納入本輪恢復或例行重啟釋出。
### 14.1 共同紅線
| 類型 | 禁止事項 | 正確處理 |
|------|----------|----------|
| 120 offline | 不可消音 `ColdStartHost120Unreachable``ColdStartRecoveryBlocked` 或 120 config backup alert | 保留紅燈,直到 console/VM recovery 後重跑完整 chain |
| Filesystem | 不可對已掛載 root filesystem 做 online `fsck` | 只在 console/rescue/initramfs 狀態下離線修復 |
| Backup | 不可用單項 backup 成功宣稱 aggregate backup green | 以 `backup-all`、offsite verifier、cold-start scorecard 三者共同判定 |
| Credential | 不可把密碼、token、private key 寫進 repo、LOGBOOK 或聊天 | 只寫 non-secret evidence marker / vault reference |
| Stateful data | 不可 truncate、DROP、整庫 restore 或整批刪 volume | 先保存證據,優先 `REINDEX TABLE CONCURRENTLY` / clean-clone / idempotent resync |
| Automation | 不可在 P0/P1 未綠時釋出 runner/CD/AI full execution | observe-onlyrunner/CD 最後釋出 |
### 14.2 關機前 SOP
目標是保留證據、停止高負載來源、讓 stateful service 乾淨落地。
1. 宣告維護窗口,建立 `docs/LOGBOOK.md` 重啟紀錄草稿。
2. 跑 preflight snapshot
```bash
/home/wooo/scripts/full-stack-cold-start-check.sh --monitor-read-only --no-color --watch --interval 1 --max-attempts 1
/backup/scripts/backup-status.sh --no-notify
/backup/scripts/verify-offsite-full-sync.sh --write-textfile --no-color
```
3. 保存 host reboot evidence
```bash
for h in 110 120 121; do
ssh wooo@192.168.0.$h 'hostname; date; uptime; who -b; last -x reboot shutdown | head -20; systemctl --failed --no-pager' || true
done
ssh ollama@192.168.0.188 'hostname; date; uptime; who -b; last -x reboot shutdown | head -20; systemctl --failed --no-pager' || true
```
4. 暫停高負載與自動化釋出:
| 順序 | 對象 | 操作原則 |
|------|------|----------|
| 1 | Gitea / actions runners | 停止新 job不要在 build 中途硬關,先讓可完成 job 結束或人工取消 |
| 2 | AI auto-remediation | 切 observe-only禁止自動 restart stateful services |
| 3 | momo crawler / scheduler / heavy batch | 暫停會啟動 Chrome、批次匯入或大量 DB 寫入的工作 |
| 4 | Sentry/Snuba/ClickHouse heavy consumers | 確認沒有 restart storm必要時 controlled stop |
| 5 | K3s workload | 優先 drain / cordon 可達節點;不可在 120 已不可達時假裝 drain 完成 |
5. 全機關機順序:
```text
1. runner/CD and high-load batch
2. AI auto-remediation execution
3. AWOOOI workload layer
4. 121 K3s agent side
5. 120 K3s server side
6. 110 registry / observability, after evidence and backup status are captured
7. 188 data layer last
8. network / UPS / hypervisor last, if applicable
```
188 必須最後關,因為 PostgreSQL / Redis / momo DB / K3s datastore 是其他層的共同依賴。
### 14.3 開機 SOP
開機順序固定走 dependency chain不追最吵的 alert。
```text
1. Physical network: switch, NIC, ARP, SSH
2. 188 data layer: PostgreSQL, Redis, Docker, momo DB, SignOz dependencies
3. 110 registry / observability: Harbor, Gitea, Prometheus, Alertmanager, Sentry
4. 120 K3s server / VIP path
5. 121 K3s agent / failover path
6. AWOOOI API/Web workload
7. Public routes and Alertmanager E2E
8. Backups, cron, CronJobs, textfile exporters
9. momo scheduler / crawlers and high-load consumers
10. runners/CD
11. AI auto-remediation limited execution
```
開機後每一層都要有 live evidence。最小驗收命令
```bash
for h in 110 120 121 188; do
ping -c 2 -W 2 192.168.0.$h >/dev/null && echo "PING_OK 192.168.0.$h" || echo "PING_FAIL 192.168.0.$h"
nc -G 3 -z 192.168.0.$h 22 && echo "SSH_OK 192.168.0.$h" || echo "SSH_FAIL 192.168.0.$h"
done
ssh ollama@192.168.0.188 'systemctl is-active docker postgresql@14-main redis-server nginx || true; pg_isready -h localhost -p 5432 || true; docker ps --format "{{.Names}}\t{{.Status}}" | head -80'
ssh wooo@192.168.0.110 'systemctl is-active docker cron || true; curl -s --max-time 5 http://127.0.0.1:9090/-/ready || true; curl -s --max-time 5 http://127.0.0.1:9093/-/healthy || true'
ssh wooo@192.168.0.121 'sudo kubectl get nodes -o wide; sudo kubectl get pods -A | grep -v -E "Running|Completed" || true'
/backup/scripts/backup-status.sh --no-notify
/backup/scripts/verify-offsite-full-sync.sh --write-textfile --no-color
/home/wooo/scripts/full-stack-cold-start-check.sh --monitor-read-only --no-color --watch --interval 1 --max-attempts 1
```
### 14.4 單主機重啟 SOP
| Host | 重啟前條件 | 重啟後必查 | 完成條件 |
|------|------------|------------|----------|
| 110 | 不在 `backup-all` / rclone / verify windowrunner job 已停止或人工取消188 healthy | Docker, Harbor, Gitea, Prometheus, Alertmanager, Sentry, cron, textfile exporters, `/backup/scripts/backup-status.sh --no-notify` | 110 services greenbackup status 沒有新增 stale / failedrunner/CD 最後釋出 |
| 120 | 必須是 console-first 維護;若可達,先 cordon/drain若不可達不宣稱 drain 成功 | power/VM/NIC/boot/initramfs/fsck state, SSH, `kubectl get nodes`, `SchedulingDisabled` 清除狀態 | 120 ping/SSH OK`mon Ready`backup configs/all/offsite/verify/cold-start chain 重跑 |
| 121 | 120 / 188 healthy可達時先 cordon/drain | `k3s-agent` 或 live role、VIP 狀態、`kubectl get nodes`, pod placement | `mon1 Ready`VIP / NodePort 路徑正常workload 無新增 failed pods |
| 188 | 110 backup status 已保存;停止或延後 momo heavy import確認無 DB restore / migration | PostgreSQL, Redis, Docker, momo DB parity, SignOz/ClickHouse, cron, backup freshness | DB acceptingmomo parity 綠188 backup jobs fresh高負載服務最後釋出 |
### 14.4.1 110 重啟後恢復指揮卡
110 是 registry / observability / backup center。重啟後先看 host 與核心端口,不要第一時間重啟 Docker daemon。
| 順序 | 檢查 | 成功基準 | 失敗處理 |
|------|------|----------|----------|
| 1 | `systemctl is-system-running` / failed units / Swap | `running`、failed `0` 或可解釋、Swap 未持續增加 | 先分辨 stale unit、現役 service、storage/network 問題 |
| 2 | Docker daemon | `systemctl is-active docker=active` | 若 Docker `activating`,先看 journal不要連續 restart/kill |
| 3 | Harbor / registry | local `/v2/``200/401`public registry 未登入 `401` | 只針對失效 upstream 做最小修復;避免 daemon restart |
| 4 | Gitea / runners | Gitea 200/302runner 最後釋出 | runner job 不可在 P0/P1 未綠時搶資源 |
| 5 | Prometheus / Alertmanager | `/-/ready``/-/healthy` OKrequired alerts visible | 若告警缺失,先修 rules/drift guard再談自動化 |
| 6 | Sentry / Langfuse / Stock / public tools | public 2xx/3xxcontainer 非 restart loop | 只修明確故障服務;不要 compose 全套重建 |
| 7 | backup / offsite | `backup-status --no-notify`、offsite verifier | 120 不可達時 Configs 紅燈保留 |
110 post-reboot 最小命令:
```bash
ssh wooo@192.168.0.110 '
date; uptime; systemctl is-system-running || true; systemctl --failed --no-pager --plain || true
free -h; swapon --show
systemctl is-active docker cron || true
curl -s -o /dev/null -w "harbor_v2=%{http_code}\n" --max-time 5 http://127.0.0.1:5000/v2/ || true
curl -s -o /dev/null -w "gitea=%{http_code}\n" --max-time 5 http://127.0.0.1:3001/ || true
curl -s --max-time 5 http://127.0.0.1:9090/-/ready || true
curl -s --max-time 5 http://127.0.0.1:9093/-/healthy || true
docker ps --format "{{.Names}}\t{{.Status}}" | head -120
'
```
2026-06-12 補充:`stockplatform-shared-ui-monitor.timer` 指向不存在的 legacy path 時,可停用 stale timer 解除 host failed unit但正式 source-of-truth 必須後續清理,不能把 `reset-failed` 當修復。
### 14.4.2 188 重啟後恢復指揮卡
188 是資料與 AI/Web 依賴主機。它恢復前,不釋出 K3s、AWOOOI API、momo heavy import 或 AI auto-remediation。
| 順序 | 檢查 | 成功基準 |
|------|------|----------|
| 1 | PostgreSQL | `pg_isready` accepting無 checkpoint / WAL panic |
| 2 | Redis | `PONG` |
| 3 | Docker / containerd | activemomo-db / signoz / openclaw / litellm 非 restart loop |
| 4 | momo DB parity | `daily_sales_snapshot``realtime_sales_monthly` 目前月份筆數與日期上下界一致 |
| 5 | SignOz / monitoring bridge | HTTP 200ClickHouse 不在修復風暴 |
| 6 | momo scheduler | container healthyrecent activity pattern > 0heavy import 等 DB green 後釋出 |
| 7 | backup freshness | 188 backup textfile / 110 backup-from-188 freshness OK |
188 post-reboot 不可用「首頁 200」取代 DB parity若出現 `posting list tuple ... cannot be split`,只走 `REINDEX TABLE CONCURRENTLY public.realtime_sales_monthly;`,不可 truncate 或整庫 restore。
### 14.4.3 120 恢復指揮卡
120 目前是 console-first blocker。它不可達時遠端只能做證據收集不能假裝修復。
| 狀態 | 判定 | 正確動作 |
|------|------|----------|
| ping / SSH / ARP 全失敗 | host / VM / network 層未知 | 到 hypervisor / console 確認 power、NIC、boot screen |
| initramfs / fsck prompt | filesystem repair gate | 依 `120-fsck-maintenance-checklist.sh` 離線處理 |
| SSH 恢復但 K3s NotReady | K3s / runtime 層 | 先看 `journalctl -u k3s`、containerd、188 PostgreSQL再解除 cordon |
| node Ready 但 SchedulingDisabled | 調度狀態未解除 | 確認健康後 `kubectl uncordon mon`,再看 workload |
120 恢復後不得只看 `kubectl get nodes`。必須強制補跑:
```bash
/backup/scripts/backup-configs.sh
/backup/scripts/backup-all.sh
/backup/scripts/sync-offsite-backups.sh --mode sync
/backup/scripts/verify-offsite-full-sync.sh --write-textfile --no-color
/home/wooo/scripts/full-stack-cold-start-check.sh --monitor-read-only --no-color --watch --interval 1 --max-attempts 1
```
### 14.4.4 121 重啟後恢復指揮卡
121 是 K3s failover / secondary control-plane path。它重啟後的核心是「不要讓 mon1 Ready 掩蓋 mon 不可達」。
| 檢查 | 成功基準 | 注意 |
|------|----------|------|
| SSH / systemd | host ready、failed units 可解釋 | 121 green 不代表 120 green |
| K3s role | `kubectl get nodes -o wide` 可讀 | 若只剩 `mon1 Ready`,仍是 degraded cluster |
| VIP / NodePort | VIP / public routes 通 | 必須確認 route 走 `192.168.0.125:32334/32335` |
| Cron / DR drill | cron present、DR drill 未被誤停 | schedule green 是 cold-start done criteria 的一部分 |
若 121 重啟後看到 `mon1 Ready``mon NotReady,SchedulingDisabled`結論是「121 恢復cluster 仍 degraded」不可把 121 正常誤報成 K3s 全綠。
### 14.5 每次重啟紀錄格式
每次開機、關機、重啟都要在 `docs/LOGBOOK.md` 追加紀錄,並把必要狀態同步到本 SOP 或 workplan。
```text
## YYYY-MM-DD | Host reboot / shutdown / startup record
Scope:
- Hosts:
- Operation: shutdown / startup / reboot / recovery
- SOP version used:
- Operator:
- Maintenance window:
Pre-check:
- Cold-start scorecard:
- Backup status:
- Offsite verifier:
- Public routes:
- momo DB parity:
- Alertmanager rules / E2E:
- Credential escrow:
Execution:
- Start time:
- End time:
- Commands / console actions:
- Services paused:
- Services released:
Result:
- 110:
- 120:
- 121:
- 188:
- Cold-start scorecard after:
- Backup status after:
- Offsite verifier after:
- DB parity after:
- Alerts after:
Difference versus previous reboot:
- Faster:
- Slower:
- New blocker:
- Repeated blocker:
- False positive / detector tuning:
- SOP change required: yes/no
SOP update:
- Previous version:
- New version:
- Change reason:
- Files updated:
```
### 14.6 SOP 版本比較與改版規則
每次重啟後必須比較上一次紀錄,不只寫「已恢復」。
| 比較項 | 判定方式 |
|--------|----------|
| Time to SSH | 從 power-on 到各 host SSH OK |
| Time to K3s Ready | 從 120/121 boot 到 nodes Ready |
| Time to public routes | 從 K3s Ready 到 public 2xx/3xx |
| Time to backup green | 從 110 ready 到 backup status / offsite verifier green |
| Persistent blockers | 連續兩次以上出現即列入 SOP hard gate |
| False positives | 例如 momo scheduler detector WARN要寫清楚直接證據與調整方向 |
| Procedure drift | live cron、Ansible template、script path 與 SOP 不一致時,先修 canonical再修 SOP |
改版規則:
- 只更新 live baseline 或百分比:不升版,只更新日期與 evidence。
- 新增、刪除或改變操作順序:升 minor version例如 `v1.4` -> `v1.5`
- 牽涉破壞性操作、資料修復策略或人為批准邊界:升 major-ready review先經人工批准。
### 14.7 2026-06-06 重啟紀錄比較錨點
2026-06-06 沒有執行新重啟;本次是 live recovery check。它仍要作為下一次重啟比較基準
| 項目 | 2026-06-06 baseline |
|------|----------------------|
| Overall | `65% BLOCKED` |
| Cold-start | `PASS=71 WARN=3 BLOCKED=3` |
| Remaining hard blocker | 120 ping / SSH / K3s read-only check |
| Backup aggregate | `failed=1`, Configs only, due 120 config capture |
| Backup freshness | 110 and 188 fresh, no stale jobs |
| Offsite | 13 repos latest-only green |
| Escrow | 5 markers missing |
| momo scheduler | direct healthy; 15:03 scorecard no longer emits scheduler WARN |
### 14.8 2026-06-12 重啟後比較錨點
2026-06-12 110 被非計畫重啟後,本 SOP v1.5 的新比較錨點如下:
| 項目 | 2026-06-12 post-reboot baseline |
|------|----------------------------------|
| 110 host | `systemd running`failed units `0`Swap `0B/7.8GiB` |
| 110 service recovery | Harbor / Gitea / Prometheus / Alertmanager / Sentry / Stock / public tools reachable |
| Cold-start | `PASS=72 WARN=2 BLOCKED=3` |
| Remaining hard blocker | 120 ping / SSH / K3s read-only check |
| WARN | 120-driven backup aggregate/config component and 120 K3s schedule check |
| Backup freshness | 110 `13/13 fresh failed=1`188 `2/2 fresh failed=0`stale none |
| Offsite | 13 repos latest-only green`REMOTE_LATEST_ONLY_OK=1``VERIFY_OK=1` |
| Alerts | Prometheus and Alertmanager expose all five required backup/cold-start/escrow alerts |
| momo scheduler | scorecard reads `SCHEDULER_RECENT_ACTIVITY 1070` after detector fix |
| SOP change | v1.5 adds startup judgment layers, GO/NO-GO tree, host recovery cards, and timeline checks |
### 14.9 2026-06-13 CD 後恢復比較錨點
2026-06-13 不是主機重啟而是用來驗證「120/121 workload balancing + CD known_hosts guardrail」是否能承受下一次正常部署的比較錨點。
| 項目 | 2026-06-14 03:10 baseline |
|------|----------------------------|
| Gitea / ArgoCD | Gitea main `8868c025`deploy marker `7b034b58`ArgoCD revision `8868c025`sync `Synced`health `Degraded` |
| K3s image readback | API/Web/Worker/CronJob image tag `26b67d11f7b7de4f9c9d95c01bb1dacf4000e887` |
| K3s placement | API/Web verified split across `mon` / `mon1` after the latest deploy markerWorker single replica healthy |
| Cold-start | `PASS=81 WARN=2 BLOCKED=0` |
| Public routes | Scorecard verifies awoooi API/Web, momo, gitea, harbor, registry, sentry, signoz, stock, langfuse, bitan, aiops over TLS |
| Backup | `backup-status`: 110 `13/13 fresh failed=0`188 `2/2 fresh failed=0``core_blockers=0``escrow_missing=5`last aggregate `2026-06-14 02:40:22` |
| Offsite | textfile `remote_verify_ok=1``full_verify_fresh=1`13 repos each `snapshot_count=1` |
| SSH trust | Global `known_hosts` retained 120 / 188 entries after CD; deploy-specific trust moved to `deploy_known_hosts` |
| Remaining non-service debt | `km-vectorize-29689620` official Job failed with `BackoffLimitExceeded`; failed Pod/log was deleted before inspection; credential escrow missing count remains `5`; 110 has `fwupd` failed units |
| SOP change | v1.10 changes the first-screen declaration from full green back to degraded, records official `km-vectorize` failure evidence, and verifies live `restartPolicy: Never` / `FallbackToLogsOnError` evidence retention for the next official run |
### 14.10 2026-06-14 110 failed-unit 清理比較錨點
2026-06-14 08:24 的變更不是主機重啟,而是把 110 非核心 `fwupd` failed-unit 噪音從 cold-start 判定中收斂。這個錨點的用途是避免未來把 firmware metadata refresh failure 誤判成 AWOOOI runtime 失敗,同時保留 rollback。
| 項目 | 2026-06-14 08:24 baseline |
|------|----------------------------|
| 110 failed units | `systemctl --failed``0 loaded units listed` |
| fwupd policy | `fwupd-refresh.timer``disabled / inactive`,原因是非核心 firmware metadata refresh 失敗不應阻擋 AWOOOI service recovery |
| Rollback | 若需要恢復 firmware metadata refresh timer執行 `sudo systemctl enable --now fwupd-refresh.timer` 後重跑 cold-start |
| Cold-start | `PASS=82 WARN=1 BLOCKED=0` |
| Remaining WARN | 只剩 K8s failed Job `km-vectorize-29689620`;等待下一次官方 03:00 排程成功或保留 failed Pod/log |
| Backup | 110 `13/13 fresh failed=0`188 `2/2 fresh failed=0``core_blockers=0``escrow_missing=5` |
| Credential escrow | 仍缺 5 個 non-secret evidence marker不可用 placeholder 或 secret 清紅燈 |
| SOP change | v1.11 把 110 failed-unit gate 從 `GREEN_WITH_FWUPD_WARNING` 改成 `GREEN_WITH_FWUPD_TIMER_DISABLED`,並把完成宣告上限固定為 `SERVICE_AVAILABLE_KM_VECTORIZE_FAILED_DR_ESCROW_BLOCKED` |
### 14.11 2026-06-14 post-CD recovery readback
2026-06-14 08:40 的變更不是主機重啟,而是確認 latest CD deploy marker 沒有讓重啟恢復狀態倒退。這個錨點用來比較「治理 / 前端 / API CD 後cold-start SOP 是否仍成立」。
| 項目 | 2026-06-14 08:40 post-CD baseline |
|------|------------------------------------|
| Gitea / ArgoCD | Gitea main `18b867c3`ArgoCD revision `18b867c3`sync `Synced`health `Degraded` |
| K3s image readback | API/Web/Worker/CronJob image tag `e0a6d339669fc635357d36ea94215df25e652fa9` |
| CronJob readback | `km-vectorize` has `KM_PROJECT_ID=awoooi``restartPolicy: Never``terminationMessagePolicy: FallbackToLogsOnError``lastScheduleTime=2026-06-13T19:00:00Z``lastSuccessfulTime=2026-06-04T11:00:37Z` |
| K3s placement | API pods split `mon` / `mon1`Web pods split `mon` / `mon1`Worker single replica on `mon` |
| Cold-start | `PASS=82 WARN=1 BLOCKED=0` |
| Public routes | Scorecard verifies awoooi API/Web, momo, gitea, harbor, registry, sentry, signoz, stock, langfuse, bitan, aiops over TLS |
| Backup | 110 `13/13 fresh failed=0`188 `2/2 fresh failed=0``core_blockers=0``escrow_missing=5` |
| 110 host | `systemctl --failed``0 loaded units listed``fwupd-refresh.timer` 維持 `disabled / inactive` |
| Remaining gate | `km-vectorize-29689620` official Job 仍 failedCredential escrow missing count 仍 `5` |
| SOP change | v1.12 records the post-CD no-regression readback and keeps the declaration ceiling at `SERVICE_AVAILABLE_KM_VECTORIZE_FAILED_DR_ESCROW_BLOCKED` |
### 14.12 2026-06-14 P2-135 deploy 後 recovery readback
2026-06-14 09:27 的變更不是主機重啟,而是確認 P2-135 deploy 與正式驗證後reboot recovery baseline 沒有倒退。這個錨點也記錄 stockplatform-v2 rollout warmup 期間短暫 `502` 的判定方式:直接重查 route / TLS並重跑完整 cold-start只有重跑仍失敗才升級成 persistent public route blocker。
| 項目 | 2026-06-14 09:27 post-P2-135 baseline |
|------|----------------------------------------|
| Gitea / ArgoCD | Gitea main `5bad267e`ArgoCD revision `5bad267e`sync `Synced`health `Degraded` |
| K3s image readback | API/Web/Worker/CronJob image tag `280e0fbef0d5dccb10f1efe2cc18cf423544254e` |
| CronJob readback | `km-vectorize` has `KM_PROJECT_ID=awoooi``restartPolicy: Never``terminationMessagePolicy: FallbackToLogsOnError``lastScheduleTime=2026-06-13T19:00:00Z``lastSuccessfulTime=2026-06-04T11:00:37Z` |
| K3s placement | API pods split `mon` / `mon1`Web pods split `mon` / `mon1`Worker single replica on `mon1` |
| First cold-start | 09:26 first run saw `stock.wooo.work` `502` while stockplatform-v2 containers were less than one minute old; direct route and TLS recheck returned `200` |
| Final cold-start | 09:27 rerun returned `PASS=82 WARN=1 BLOCKED=0` |
| Public routes | Final scorecard verifies awoooi API/Web, momo, gitea, harbor, registry, sentry, signoz, stock, langfuse, bitan, aiops over TLS |
| Backup | 110 `13/13 fresh failed=0`188 `2/2 fresh failed=0``core_blockers=0``escrow_missing=5` |
| 110 host | `systemctl --failed``0 loaded units listed``fwupd-refresh.timer` 維持 `disabled / inactive` |
| Remaining gate | `km-vectorize-29689620` official Job 仍 failedCredential escrow missing count 仍 `5` |
| SOP change | v1.13 records the P2-135 post-deploy no-regression readback and keeps the declaration ceiling at `SERVICE_AVAILABLE_KM_VECTORIZE_FAILED_DR_ESCROW_BLOCKED` |
### 14.13 2026-06-14 P2-136 / AI Agent 活動正式部署後 recovery readback
2026-06-14 09:56 的變更不是主機重啟,而是確認 P2-136 / AI Agent 活動正式部署後reboot recovery baseline 仍沒有倒退。這個錨點特別記錄 deploy marker、ArgoCD revision、live image 與 cold-start scorecard 必須一起看,避免只看 `gitea/main` 或 CD 成功就誤報 full-stack green。
| 項目 | 2026-06-14 09:56 post-P2-136 baseline |
|------|----------------------------------------|
| Gitea / ArgoCD | 本 recovery commit 前最新文件 head `a0fe7741`runtime deploy marker `60a0415c chore(cd): deploy a3de0ff [skip ci]`ArgoCD revision `60a0415c`sync `Synced`health `Degraded` |
| K3s image readback | API/Web/Worker/CronJob image tag `a3de0ffb8275b6838604b6dff87cd978b8e91122` |
| CronJob readback | `km-vectorize` has `KM_PROJECT_ID=awoooi``restartPolicy: Never``terminationMessagePolicy: FallbackToLogsOnError``lastScheduleTime=2026-06-13T19:00:00Z``lastSuccessfulTime=2026-06-04T11:00:37Z`failed Job `km-vectorize-29689620` remains retained |
| K3s placement | API pods split `mon` / `mon1`Web pods split `mon` / `mon1`Worker single replica on `mon1` |
| Cold-start | 09:56 returned `PASS=82 WARN=1 BLOCKED=0` |
| Public routes | Final scorecard verifies awoooi API/Web, momo, gitea, harbor, registry, sentry, signoz, stock, langfuse, bitan, aiops over TLS |
| Backup | 110 `13/13 fresh failed=0`188 `2/2 fresh failed=0``core_blockers=0``escrow_missing=5` |
| 110 host | `systemctl --failed``0 loaded units listed``fwupd-refresh.timer` 維持 `disabled / inactive` |
| Remaining gate | `km-vectorize-29689620` official Job 仍 failedCredential escrow missing count 仍 `5` |
| SOP change | v1.14 records the P2-136 / AI Agent 活動正式部署後 no-regression readback and keeps the declaration ceiling at `SERVICE_AVAILABLE_KM_VECTORIZE_FAILED_DR_ESCROW_BLOCKED` |
### 14.14 2026-06-14 P2-137 / CI smoke timeout 修正後 recovery readback
2026-06-14 10:40 的變更不是主機重啟,而是確認 P2-137 正式部署與 BusyBox `timeout` smoke 修正後reboot recovery baseline 仍沒有倒退。這個錨點只記錄 recovery / cold-start readback不重複 P2-137 正式驗證內容。
| 項目 | 2026-06-14 10:40 post-P2-137 baseline |
|------|----------------------------------------|
| Gitea / ArgoCD | 本 recovery commit 前最新文件 head `50d4f2ba`runtime deploy marker `d023f5d7 chore(cd): deploy f737f27 [skip ci]`ArgoCD revision `50d4f2ba`sync `Synced`health `Degraded` |
| K3s image readback | API/Web/Worker/CronJob image tag `f737f278dc14372ff1fb15b124b1370c20e1bb99` |
| CronJob readback | `km-vectorize` has `KM_PROJECT_ID=awoooi``restartPolicy: Never``terminationMessagePolicy: FallbackToLogsOnError``lastScheduleTime=2026-06-13T19:00:00Z``lastSuccessfulTime=2026-06-04T11:00:37Z`failed Job `km-vectorize-29689620` remains retained |
| K3s placement | API pods split `mon` / `mon1`Web pods split `mon` / `mon1`Worker single replica on `mon` |
| Cold-start | 10:40 returned `PASS=82 WARN=1 BLOCKED=0` |
| Public routes | Final scorecard verifies awoooi API/Web, momo, gitea, harbor, registry, sentry, signoz, stock, langfuse, bitan, aiops over TLS |
| Backup | 110 `13/13 fresh failed=0`188 `2/2 fresh failed=0``core_blockers=0``escrow_missing=5` |
| 110 host | `systemctl --failed``0 loaded units listed``fwupd-refresh.timer` 維持 `disabled / inactive` |
| Remaining gate | `km-vectorize-29689620` official Job 仍 failedCredential escrow missing count 仍 `5` |
| SOP change | v1.15 記錄 P2-137 / CI smoke timeout 修正後 no-regression readback並維持宣告上限為 `SERVICE_AVAILABLE_KM_VECTORIZE_FAILED_DR_ESCROW_BLOCKED` |
### 14.15 2026-06-14 P2-143 owner response 預檢後 recovery readback
2026-06-14 15:00 的變更不是主機重啟,而是確認 P2-143 owner response 預檢與拒收邊界正式部署後reboot recovery baseline 仍沒有倒退。這個錨點只記錄 recovery / cold-start readback不重複 P2-142 / P2-143 正式驗證內容,也不把 owner response preflight 視為 runtime 授權。
| 項目 | 2026-06-14 15:00 post-P2-143 baseline |
|------|----------------------------------------|
| Gitea / ArgoCD | 最新文件基準 `b09eb1c6 docs(ai): 校準 P2-143 正式驗證紀錄`runtime deploy marker `667d6329 chore(cd): deploy 755b0a8 [skip ci]`ArgoCD revision `4abf0c0f750254d3c7137eae049abdfd99630f5f`sync `Synced`health `Degraded` |
| K3s image readback | API/Web/Worker/CronJob image tag `755b0a8d3038df2c52dee280067863d92db1eda5` |
| CronJob readback | `km-vectorize` schedule `0 3 * * *``timeZone=Asia/Taipei``suspend=false``failedJobsHistoryLimit=3``lastScheduleTime=2026-06-13T19:00:00Z``lastSuccessfulTime=2026-06-04T11:00:37Z`failed Job `km-vectorize-29689620` 仍保留,但目前沒有可讀的 failed Pod / log |
| K3s placement | API pods split `mon` / `mon1`Web pods split `mon` / `mon1`Worker single replica on `mon` |
| Cold-start | 15:00 returned `PASS=82 WARN=1 BLOCKED=0` |
| Public routes | 最終 scorecard 已驗證 awoooi API/Web、momo、gitea、harbor、registry、sentry、signoz、stock、langfuse、bitan、aiops 皆可透過 TLS 回應 |
| Backup | 110 `13/13 fresh failed=0`188 `2/2 fresh failed=0``core_blockers=0``escrow_missing=5` |
| 110 host | `systemctl --failed``0 loaded units listed``fwupd-refresh.timer` 維持 `disabled / inactive` |
| P2-143 API boundary | Production endpoint 回 current `P2-143`、next `P2-144`、completion `100`,且 reviewer / Gateway queue、Telegram、Bot API、result capture、learning、PlayBook trust、production write、secret read、destructive operation 全部維持 `0 / false` |
| Remaining gate | `km-vectorize-29689620` official Job 仍 failedCredential escrow missing count 仍 `5` |
| SOP change | v1.16 記錄 P2-143 owner response 預檢後 no-regression readback並維持宣告上限為 `SERVICE_AVAILABLE_KM_VECTORIZE_FAILED_DR_ESCROW_BLOCKED` |
### 14.16 2026-06-14 P2-144 owner response 回讀後 recovery readback
2026-06-14 15:58 的變更不是主機重啟,而是確認 P2-144 owner response 回讀狀態與後續 deploy marker `180a6543` 正式部署後reboot recovery baseline 仍沒有倒退。這個錨點只記錄 recovery / cold-start readback不重複 P2-144 正式驗證內容,也不把 owner response readback 視為 runtime 授權、正式收件或 owner acceptance。
| 項目 | 2026-06-14 15:58 post-P2-144 baseline |
|------|----------------------------------------|
| Gitea / ArgoCD | `gitea/main` 已前進至 `180a6543 chore(cd): deploy fef94df [skip ci]`ArgoCD source revision `180a6543eaf26dd6b345d45114316926056a965a`sync `Synced`health `Degraded` |
| K3s image readback | API/Web/Worker/CronJob image tag `fef94df877c5438f9f34ddbcace8ad8112a141ef` |
| CronJob readback | `km-vectorize` schedule `0 3 * * *``timeZone=Asia/Taipei``suspend=false``failedJobsHistoryLimit=3``lastScheduleTime=2026-06-13T19:00:00Z``lastSuccessfulTime=2026-06-04T11:00:37Z`failed Job `km-vectorize-29689620` 仍保留,但目前沒有可讀的 failed Pod / log |
| K3s placement | API pods split `mon` / `mon1`Web pods split `mon` / `mon1`Worker single replica on `mon1` |
| Cold-start | 15:58 returned `PASS=82 WARN=1 BLOCKED=0` |
| Public routes | 最終 scorecard 已驗證 awoooi API/Web、momo、gitea、harbor、registry、sentry、signoz、stock、langfuse、bitan、aiops 皆可透過 TLS 回應 |
| Backup | 110 `13/13 fresh failed=0`188 `2/2 fresh failed=0``core_blockers=0``escrow_missing=5` |
| 110 host | `systemctl --failed``0 loaded units listed``fwupd-refresh.timer` 維持 `disabled / inactive` |
| P2-144 API boundary | Production endpoint 回 current `P2-144`、next `P2-145`、completion `100`,且 owner response received / accepted / rejected、reviewer / Gateway queue、Telegram、Bot API、result capture、learning、PlayBook trust、production write、secret read、destructive operation 全部維持 `0 / false` |
| Remaining gate | `km-vectorize-29689620` official Job 仍 failedCredential escrow missing count 仍 `5` |
| SOP change | v1.17 記錄 P2-144 owner response 回讀後 no-regression readback並維持宣告上限為 `SERVICE_AVAILABLE_KM_VECTORIZE_FAILED_DR_ESCROW_BLOCKED` |
### 14.17 2026-06-14 P2-145 owner response 驗收門檻後 recovery readback
2026-06-14 16:29 的變更不是主機重啟,而是確認 P2-145 owner response 驗收門檻正式部署後reboot recovery baseline 仍沒有倒退。這個錨點只記錄 recovery / cold-start readback不重複 P2-145 正式驗證內容,也不把 acceptance gate 視為 owner response received / accepted、runtime 授權或正式寫入。
| 項目 | 2026-06-14 16:29 post-P2-145 baseline |
|------|----------------------------------------|
| Gitea / ArgoCD | 最新文件基準 `06fe0a8f docs(logbook): 記錄 P2-145 正式驗證 [skip ci]`runtime deploy marker `36fbfc6b chore(cd): deploy 386dbd0 [skip ci]`ArgoCD source revision `06fe0a8f14167824fea512f942d2569431bbcbc8`sync `Synced`health `Degraded` |
| K3s image readback | API/Web/Worker/CronJob image tag `386dbd078ef63401d9736048463f4ef5326442d9` |
| CronJob readback | `km-vectorize` schedule `0 3 * * *``timeZone=Asia/Taipei``suspend=false``failedJobsHistoryLimit=3``lastScheduleTime=2026-06-13T19:00:00Z``lastSuccessfulTime=2026-06-04T11:00:37Z`failed Job `km-vectorize-29689620` 仍為 `Failed 0/1` |
| K3s placement | API pods split `mon` / `mon1`Web pods split `mon` / `mon1`Worker single replica on `mon` |
| Cold-start | 16:29 returned `PASS=82 WARN=1 BLOCKED=0` |
| Public routes | 最終 scorecard 已驗證 awoooi API/Web、momo、gitea、harbor、registry、sentry、signoz、stock、langfuse、bitan、aiops 皆可透過 TLS 回應 |
| Backup | 110 `13/13 fresh failed=0`188 `2/2 fresh failed=0``core_blockers=0``escrow_missing=5` |
| 110 host | `systemctl --failed``0 loaded units listed``fwupd-refresh.timer` 維持 `disabled / inactive` |
| P2-145 API boundary | Production endpoint 回 current `P2-145`、next `P2-146`、completion `100`,且 owner response received / accepted / rejected、reviewer / Gateway queue、Telegram、Bot API、result capture、learning、PlayBook trust、production write、secret read、destructive operation 全部維持 `0 / false` |
| Remaining gate | `km-vectorize-29689620` official Job 仍 failedCredential escrow missing count 仍 `5` |
| SOP change | v1.18 記錄 P2-145 owner response 驗收門檻後 no-regression readback並維持宣告上限為 `SERVICE_AVAILABLE_KM_VECTORIZE_FAILED_DR_ESCROW_BLOCKED` |
### 14.18 2026-06-14 IwoooS P0 配置控管優先序後 recovery readback
2026-06-14 17:04 的變更不是主機重啟,而是確認 IwoooS P0 配置控管優先序正式部署後reboot recovery baseline 仍沒有倒退。這個錨點只記錄 recovery / cold-start readback不重複 P0 配置控管正式驗證內容,也不把前台看板可見視為 Nginx reload、DNS / TLS probe、certbot renew、workflow / secret 修改、public route change 或 runtime gate。
| 項目 | 2026-06-14 17:04 post-IwoooS-P0-config baseline |
|------|--------------------------------------------------|
| Gitea / ArgoCD | 最新文件基準 `af62ec1f docs(iwooos): 記錄 P0 配置控管正式驗證 [skip ci]`runtime deploy marker `ed651a98 chore(cd): deploy e992af8 [skip ci]`ArgoCD source revision `af62ec1fe72b3e84e179d80e788e5a5902bdaf27`sync `Synced`health `Degraded` |
| K3s image readback | API/Web/Worker/CronJob image tag `e992af89955f8aae40a383b2f2e2f645445a690d` |
| CronJob readback | `km-vectorize` schedule `0 3 * * *``timeZone=Asia/Taipei``suspend=false``failedJobsHistoryLimit=3``lastScheduleTime=2026-06-13T19:00:00Z``lastSuccessfulTime=2026-06-04T11:00:37Z`failed Job `km-vectorize-29689620` 仍為 `Failed 0/1` |
| K3s placement | API pods split `mon` / `mon1`Web pods split `mon` / `mon1`Worker single replica on `mon1` |
| Cold-start | 17:04 returned `PASS=82 WARN=1 BLOCKED=0` |
| Public routes | 最終 scorecard 已驗證 awoooi API/Web、momo、gitea、harbor、registry、sentry、signoz、stock、langfuse、bitan、aiops 皆可透過 TLS 回應IwoooS route `/zh-TW/iwooos` 額外 readback 回 `200` |
| Backup | 110 `13/13 fresh failed=0`188 `2/2 fresh failed=0``core_blockers=0``escrow_missing=5` |
| 110 host | `systemctl --failed``0 loaded units listed``fwupd-refresh.timer` 維持 `disabled / inactive` |
| IwoooS boundary | P0 配置控管優先序已可見,但 live evidence received、runtime gate、Nginx live config、DNS / TLS probe、certbot renew、workflow / secret 修改、public route change、production write 仍不得從本 readback 推定為已授權 |
| Remaining gate | `km-vectorize-29689620` official Job 仍 failedCredential escrow missing count 仍 `5` |
| SOP change | v1.19 記錄 IwoooS P0 配置控管優先序後 no-regression readback並維持宣告上限為 `SERVICE_AVAILABLE_KM_VECTORIZE_FAILED_DR_ESCROW_BLOCKED` |
### 14.19 2026-06-14 高價值配置 Owner Packet 前台同步後 recovery readback
2026-06-14 18:15 的變更不是主機重啟,而是確認高價值配置 Owner Packet 前台同步正式部署後reboot recovery baseline 仍沒有倒退。這個錨點只記錄 recovery / cold-start readback不重複 Owner Packet 前台正式驗證、posture projection 或 intake preflight 內容,也不把前台草案可見視為 request sent、owner response received / accepted、runtime gate、Nginx reload、DNS / TLS probe、certbot renew、workflow / secret 修改、host write、active scan 或 production write。
| 項目 | 2026-06-14 18:15 post-owner-packet-frontend baseline |
|------|-------------------------------------------------------|
| Gitea / ArgoCD | 最新 repo 文件基準 `0a4766dd docs(security): 新增高價值配置 owner request 草稿包 [skip ci]`runtime deploy marker `16c6b983 chore(cd): deploy e999c16 [skip ci]`feature commit `e999c16b fix(iwooos): 同步高價值配置 owner packet 前台`ArgoCD source revision `0a4766ddc94b0690824ce3deba5c6b9a69764f94`sync `Synced`health `Degraded` |
| K3s image readback | API/Web/Worker/CronJob image tag `e999c16b3435f197b78fe2adfeec1c4faa6c4675` |
| CronJob readback | `km-vectorize` schedule `0 3 * * *``timeZone=Asia/Taipei``suspend=false``failedJobsHistoryLimit=3``lastScheduleTime=2026-06-13T19:00:00Z``lastSuccessfulTime=2026-06-04T11:00:37Z`failed Job `km-vectorize-29689620` 仍為 `Failed 0/1` |
| K3s placement | API pods split `mon` / `mon1`Web pods split `mon` / `mon1`Worker single replica on `mon` |
| Cold-start | 18:15 returned `PASS=82 WARN=1 BLOCKED=0` |
| Public routes | 最終 scorecard 已驗證 awoooi API/Web、momo、gitea、harbor、registry、sentry、signoz、stock、langfuse、bitan、aiops 皆可透過 TLS 回應IwoooS route `/zh-TW/iwooos` 與 AwoooP route `/zh-TW/awooop` 額外 readback 皆回 `200` |
| Backup | 110 `13/13 fresh failed=0`188 `2/2 fresh failed=0``core_blockers=0``escrow_missing=5` |
| 110 host | `systemctl --failed``0 loaded units listed``fwupd-refresh.timer` 維持 `disabled / inactive` |
| Owner Packet boundary | Owner Packet 前台數字已可見,但 request sent、owner response received / accepted / rejected、reviewer queue write、live evidence、runtime gate、Nginx live config、DNS / TLS probe、certbot renew、workflow / secret 修改、host write、active scan、production write 仍不得從本 readback 推定為已授權 |
| Remaining gate | `km-vectorize-29689620` official Job 仍 failedCredential escrow missing count 仍 `5` |
| SOP change | v1.20 記錄高價值配置 Owner Packet 前台同步後 no-regression readback並維持宣告上限為 `SERVICE_AVAILABLE_KM_VECTORIZE_FAILED_DR_ESCROW_BLOCKED` |
### 14.20 重啟後時間軸驗證
每次重啟後照時間軸推進,不要等到最後才一次判定。
| 時間點 | 目標 | 必跑證據 | 可以宣稱 |
|--------|------|----------|----------|
| `T+0` | power / VM / console 已開始 | console / hypervisor / UPS / operator note | maintenance started |
| `T+5m` | LAN / SSH 回復 | ping、ARP、SSH port、`who -b` | `HOST_BOOTED` |
| `T+15m` | 主機基礎服務回復 | `systemctl is-system-running`、failed units、Docker / PostgreSQL / Redis / K3s role checks | `HOST_READY` |
| `T+30m` | 核心服務回復 | 188 DB、110 Harbor/Gitea/Prom/AM、K3s nodes、AWOOOI API/Web、public routes | `SERVICE_READY` for scoped hosts |
| `T+45m` | 排程與資料一致性 | backup status、offsite verifier、momo DB parity、CronJobs、alert visibility | service recovery confidence |
| `T+60m` | 釋出高負載與自動化 | cold-start scorecard、load/core、runner guardrails、AI observe-only gate | release runner/CD only if gates allow |
若任一時間點卡住,記錄卡在哪個 gate不要跳到下一層。連續兩次重啟都卡同一 gate必須回寫 §16 Known Drift 或 workplan。
---
## 15. Done Criteria
All must be true:
- Four hosts reachable by SSH.
- 188 PostgreSQL and Redis healthy.
- 110 Harbor, Gitea, Prometheus, Alertmanager healthy.
- 120/121 K3s nodes Ready.
- VIP `192.168.0.125` present.
- AWOOOI API and Web reachable through NodePort/VIP.
- Alertmanager E2E webhook succeeds.
- cron/CronJob schedules are active, unsuspended, and verified.
- momo `daily_sales_snapshot``realtime_sales_monthly` 在最新匯入日期範圍內筆數一致。
- Sentry and SignOz are either healthy or explicitly in controlled backlog recovery.
- High-load batch services are capped or delayed.
- Runners are guarded and released last.
- AI auto-remediation is not in full execution mode until all gates are green.
- 110 cold-start textfile monitor is fresh, and Prometheus has the cold-start alert rules loaded.
- 110 global `/home/wooo/.ssh/known_hosts` still contains verified 120 / 188 entries after any CD run; deploy jobs use `/home/wooo/.ssh/deploy_known_hosts` only.
### 15.1 可宣稱狀態
| 可宣稱文字 | 必要條件 |
|------------|----------|
| `110 host recovered` | 110 `HOST_READY`failed units `0` 或全部可解釋,核心端口與 cron / backup status 已查 |
| `public core services recovered` | public routes/TLS 2xx/3xxAWOOOI API health、Harbor/Gitea/Stock/Sentry/SignOz/Langfuse/Bitan smoke OK |
| `backup/offsite current` | `backup-status --no-notify` 無 staleoffsite verifier `VERIFY_OK=1`,且任何 failed component 有明確 owner |
| `service recovery with known blocker` | cold-start `BLOCKED` 只剩已知 blocker例如 120告警保持可見 |
| `full-stack green` | §15 全部成立cold-start `WARN=0 BLOCKED=0` |
| `DR complete` | full-stack green 且 credential escrow missing count 為 0 |
---
## 16. Known Drift To Fix After Recovery
這些項目必須在事故後整理,不要在 P0 恢復中途順手大改。
- `SERVICE-ENDPOINTS.md` still has old Prometheus/Alertmanager locations.
- Audit older docs for direct node webhook targets; current main path should be VIP `192.168.0.125:32334`.
- OpenClaw `8088` vs `8089` must be live-confirmed and normalized.
- 188 compose paths drift between `/home/ollama/*` and Ansible `/opt/*`.
- 110 runner docs still mention Docker runner in places; live startup prefers host `gitea-act-runner-host.service`.
- `scripts/setup-runner-watchdog.sh` conflicts with the 2026-05-05 runner watchdog disablement guardrail.
- `grist.wooo.work` / `registry.wooo.work` public HTTP/HTTPS currently route to `aiops.wooo.work`; their old 110 certbot renewal configs are disabled until public routing is corrected or DNS-01 renewal is configured.
- `stockplatform-shared-ui-monitor.timer` / service source-of-truth 仍需清理或重建2026-06-12 只停用 stale timer 以解除 host degraded。
- 111 local Ollama fallback 目前不可達production provider 由 GCP-A / GCP-B 承接,但 111 恢復應另列 AI provider resilience 工作。
- 本 SOP v1.5 新增內容已用繁體中文補強;舊章節仍有英文段落,後續 runbook hygiene 應分批翻譯,不要在事故 P0 中混入大規模格式重排。