diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index b13407f0e..265182dac 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -24,6 +24,14 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + with: + # 修復: Harbor 是 HTTP,需要設定 insecure registry + driver-opts: | + network=host + buildkitd-config-inline: | + [registry."192.168.0.110:5000"] + http = true + insecure = true - name: Login to WOOO Harbor uses: docker/login-action@v3 @@ -53,19 +61,20 @@ jobs: run: | echo "::notice::Image pushed: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-${{ matrix.app }}:${{ steps.tag.outputs.tag }}" - # ==================== Deploy to UAT ==================== - deploy-uat: - name: Deploy to UAT + # ==================== Deploy to Production ==================== + # Memory 鐵律: 禁止 UAT,只有 Dev + Prod + deploy-prod: + name: Deploy to Production runs-on: self-hosted needs: build-images - environment: uat + environment: production steps: - uses: actions/checkout@v4 - name: Setup Kubeconfig run: | mkdir -p ~/.kube - echo "${{ secrets.KUBE_CONFIG_UAT }}" | base64 -d > ~/.kube/config + echo "${{ secrets.KUBE_CONFIG_PROD }}" | base64 -d > ~/.kube/config chmod 600 ~/.kube/config - name: Generate image tag @@ -77,7 +86,7 @@ jobs: - name: Deploy with Kustomize run: | - cd k8s/overlays/uat + cd k8s/overlays/prod kustomize edit set image \ awoooi-web=${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-web:${{ steps.tag.outputs.tag }} \ awoooi-api=${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-api:${{ steps.tag.outputs.tag }} @@ -85,10 +94,26 @@ jobs: - name: Wait for rollout run: | - kubectl rollout status deployment/awoooi-web -n awoooi-uat --timeout=300s - kubectl rollout status deployment/awoooi-api -n awoooi-uat --timeout=300s + kubectl rollout status deployment/awoooi-web -n awoooi --timeout=300s + kubectl rollout status deployment/awoooi-api -n awoooi --timeout=300s - name: Health check run: | sleep 10 - curl -f https://api-uat.awoooi.wooo.work/v1/health || exit 1 + curl -f https://api.awoooi.wooo.work/api/v1/health || exit 1 + + - name: Notify Telegram on Success + if: success() + run: | + curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \ + -d chat_id="${{ secrets.TELEGRAM_CHAT_ID }}" \ + -d text="✅ *AWOOOI 部署成功*%0A%0ACommit: \`${{ github.sha }}\`%0ABranch: \`${{ github.ref_name }}\`%0AWorkflow: [查看](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" \ + -d parse_mode="Markdown" + + - name: Notify Telegram on Failure + if: failure() + run: | + curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \ + -d chat_id="${{ secrets.TELEGRAM_CHAT_ID }}" \ + -d text="❌ *AWOOOI 部署失敗*%0A%0ACommit: \`${{ github.sha }}\`%0ABranch: \`${{ github.ref_name }}\`%0AWorkflow: [查看](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" \ + -d parse_mode="Markdown" diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 6799dc6f2..5f670e90a 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -303,9 +303,10 @@ jobs: fi # 發送 Telegram 訊息 (JSON payload with HTML + Inline Keyboard) - curl -s -X POST "https://api.telegram.org/bot8569720657:AAHdvKf_P2ms-QKFTyqTLtLiqEggz8cpjMk/sendMessage" \ + # 鐵律: Token 必須用 Secrets,禁止硬編碼 + curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \ -H "Content-Type: application/json" \ - -d "{\"chat_id\":\"5619078117\",\"text\":\"${MESSAGE}\",\"parse_mode\":\"HTML\",\"reply_markup\":${KEYBOARD}}" + -d "{\"chat_id\":\"${{ secrets.TELEGRAM_CHAT_ID }}\",\"text\":\"${MESSAGE}\",\"parse_mode\":\"HTML\",\"reply_markup\":${KEYBOARD}}" - name: Send OpenClaw Webhook if: always() diff --git a/docs/guidelines/ARCHITECTURE.md b/docs/guidelines/ARCHITECTURE.md new file mode 100644 index 000000000..ad4a20e4f --- /dev/null +++ b/docs/guidelines/ARCHITECTURE.md @@ -0,0 +1,137 @@ +# AWOOOI 架構指引 + +> 架構相關的核心原則與規範 + +## 快速索引 + +| 主題 | 核心原則 | 詳細章節 | +|------|---------|---------| +| OpenClaw | 產品核心,只能增強不能移除 | [→ OpenClaw](#openclaw-核心架構) | +| 模組化 | Interface → Memory → Brain → Skill | [→ leWOOOgo](#lewooogo-模組化) | +| API 整合 | Props Mapping 五步驟檢查 | [→ API](#api-整合) | +| 防禦性 | 先質疑後實作 | [→ 防禦性工程](#防禦性工程) | + +--- + +## OpenClaw 核心架構 + +**Memory 來源:** `feedback_architecture_openclaw_core.md` + +### 原則 + +``` +✅ OpenClaw 是 AWOOOI 產品核心 +✅ 只能增強,不能移除 +✅ 決策鏈必須可視化 (ThinkingTerminal) +✅ 雙軌決策: LLM + Expert System Fallback +``` + +### 決策流程 + +``` +Signal → IncidentEngine → DecisionManager → Telegram/UI + ↓ + LLM 提案 + Expert System 備援 +``` + +### 命名規範 + +- ✅ 正確: OpenClaw, openclaw +- ❌ 禁止: ClawBot, clawbot (舊稱) + +--- + +## leWOOOgo 模組化 + +**Memory 來源:** `feedback_modular_architecture.md`, `feedback_modular_core_spirit.md` + +### 四層架構 + +``` +┌─────────────────────────────────────────┐ +│ Skill Layer (技能層) │ +│ - 特定領域知識 │ +│ - 可組合、可替換 │ +├─────────────────────────────────────────┤ +│ Brain Layer (決策層) │ +│ - LLM + Expert System │ +│ - 決策邏輯 │ +├─────────────────────────────────────────┤ +│ Memory Layer (記憶層) │ +│ - Working: Redis (短期) │ +│ - Episodic: PostgreSQL (長期) │ +├─────────────────────────────────────────┤ +│ Interface Layer (介面層) │ +│ - Telegram Gateway │ +│ - REST API │ +└─────────────────────────────────────────┘ +``` + +### 核心精神 + +1. **Interface 先行** - 先定義清晰的介面契約 +2. **Memory 分離** - Working Memory ≠ Episodic Memory +3. **Brain 獨立** - 決策邏輯不綁定特定 LLM +4. **Skill 可組合** - 技能可插拔、可替換 + +--- + +## API 整合 + +**Memory 來源:** `feedback_api_response_verification.md` + +### Props Mapping 五步驟檢查 + +新增 API 欄位時,必須確認完整鏈路: + +``` +1. API Response 有該欄位 ✓ +2. Mapper 函數有轉換該欄位 ✓ ← 最常遺漏! +3. TypeScript 型別有定義該欄位 ✓ +4. 組件 Props 有接收該欄位 ✓ +5. 組件有使用該欄位 ✓ +``` + +### 快速診斷 + +```bash +# 確認 API 有回傳 +curl -s API_URL | jq '.[0].decision' + +# 確認 Mapper 有轉換 +grep -n "decision" apps/web/src/app/*/page.tsx +``` + +### 2026-03-23 教訓 + +Y/n 按鈕灰色無法點擊,因為 `mapToDualState()` 遺漏傳遞 `decision` 欄位。 + +--- + +## 防禦性工程 + +**Memory 來源:** `feedback_defensive_engineering.md` + +### 原則 + +``` +1. 先質疑,後實作 +2. 看到可疑代碼,先問為什麼存在 +3. 不確定的變更,先問統帥 +4. 任何刪除操作,三思後行 +``` + +### 常見陷阱 + +- 「這段代碼看起來沒用」→ 先 grep 確認沒人用 +- 「這個 TODO 很久了」→ 先確認是否已完成 +- 「這個邏輯很奇怪」→ 可能是特殊 case 處理 + +--- + +## 相關 ADR + +- [ADR-003: leWOOOgo 模組架構](adr/ADR-003-lewooogo-module-architecture.md) +- [ADR-006: AI Fallback 策略](adr/ADR-006-ai-fallback-strategy.md) +- [ADR-008: Python 模組化](adr/ADR-008-python-modular-packages.md) +- [ADR-009: OpenClaw Agent Teams](adr/ADR-009-openclaw-agent-teams.md) diff --git a/docs/guidelines/FRONTEND.md b/docs/guidelines/FRONTEND.md new file mode 100644 index 000000000..861e3371f --- /dev/null +++ b/docs/guidelines/FRONTEND.md @@ -0,0 +1,159 @@ +# AWOOOI 前端指引 + +> 前端開發的核心原則與規範 + +## 快速索引 + +| 主題 | 核心原則 | 詳細章節 | +|------|---------|---------| +| i18n | 100% next-intl,零硬編碼 | [→ i18n](#i18n-雙語) | +| 視覺 | Nothing.tech 風格 | [→ 視覺規範](#視覺規範) | +| 狀態 | Zustand (禁止 Redux) | [→ 狀態管理](#狀態管理) | +| 數據 | 真實 API,禁止假數據 | [→ 數據規範](#數據規範) | + +--- + +## i18n 雙語 + +**Memory 來源:** `feedback_i18n_zero_hardcode.md`, `feedback_naming_i18n.md` + +### 鐵律 + +```tsx +// ❌ 禁止 - 任何硬編碼文字 + +Loading... +

System Status

+ +// ✅ 正確 - 100% next-intl + +{t('common.loading')} +

{t('dashboard.systemStatus')}

+``` + +### 支援語言 + +- `zh-TW` - 繁體中文 (預設) +- `en` - English + +### 翻譯檔位置 + +``` +apps/web/src/messages/ +├── zh-TW.json +└── en.json +``` + +### 新增翻譯流程 + +1. 在 `zh-TW.json` 新增 key +2. 在 `en.json` 新增對應翻譯 +3. 在組件使用 `t('your.key')` + +--- + +## 視覺規範 + +**Memory 來源:** `feedback_naming_i18n.md` + +### Nothing.tech 風格 + +``` +- 白玻璃毛玻璃效果 (awoooi-glass) +- 點陣紋理背景 +- DataPincer 數據鉗容器 +- 黑白紅極簡配色 +``` + +### 顏色系統 + +```css +/* 主色 */ +--nothing-black: #000000 +--nothing-white: #ffffff +--claw-red: #ff0000 + +/* 狀態色 */ +--status-healthy: #00ff00 +--status-warning: #ffaa00 +--status-critical: #ff0000 +``` + +### 組件規範 + +```tsx +// DataPincerPanel - 標準容器 + + {children} + + +// DataPincerCard - 卡片容器 + + {content} + +``` + +--- + +## 狀態管理 + +**Memory 來源:** ADR-004 + +### 鐵律 + +```tsx +// ❌ 禁止 - Redux +import { useSelector } from 'react-redux' + +// ✅ 正確 - Zustand +import { useStore } from '@/stores/xxx.store' +``` + +### Store 結構 + +``` +apps/web/src/stores/ +├── agent.store.ts - Agent 狀態 +├── incident.store.ts - Incident 狀態 +└── ui.store.ts - UI 狀態 +``` + +--- + +## 數據規範 + +**Memory 來源:** `feedback_no_fake_data.md` + +### 鐵律 + +```tsx +// ❌ 禁止 - 假數據 +const data = DEMO_DATA +const mockMetrics = generateMockData() + +// ✅ 正確 - 真實 API +const { data } = useRealAPI() +const { metrics } = useGlobalPulseMetrics() +``` + +### 2026-03-23 教訓 + +`GlobalPulseChartDemo` 使用假數據,導致用戶無法看到真實系統狀態。已修正為 `GlobalPulseChart` 連接真實 API。 + +### Loading/Error 處理 + +```tsx +const { data, isLoading, error } = useAPI() + +if (isLoading) return +if (error) return +return +``` + +--- + +## 相關 ADR + +- [ADR-002: Nothing.tech 設計系統](adr/ADR-002-nothing-tech-design-system.md) +- [ADR-004: 狀態管理 (Zustand)](adr/ADR-004-state-management.md) +- [UX-001: Incident Card Fatigue](design/UX-001-incident-card-fatigue.md) diff --git a/docs/guidelines/OPERATIONS.md b/docs/guidelines/OPERATIONS.md new file mode 100644 index 000000000..e432bd075 --- /dev/null +++ b/docs/guidelines/OPERATIONS.md @@ -0,0 +1,165 @@ +# AWOOOI 維運指引 + +> 維運、部署、CI/CD 的核心原則與規範 + +## 快速索引 + +| 主題 | 核心原則 | 詳細章節 | +|------|---------|---------| +| Telegram | 絕對禁止 logOut | [→ Telegram](#telegram-整合) | +| 部署 | Dev + Prod (禁止 UAT) | [→ 部署拓撲](#部署拓撲) | +| CI/CD | self-hosted runner | [→ CI/CD](#cicd) | +| 構建 | Git Clone Only | [→ 構建流程](#構建流程) | + +--- + +## Telegram 整合 + +**Memory 來源:** `feedback_telegram_token_disaster.md`, `feedback_openclaw_security.md` + +### 2026-03-23 災難事件 + +```python +# ❌ 禁止 - 會導致 Token 永久失效! +await bot.log_out() + +# ✅ 正確流程 +1. 先停止舊 Bot 的 Long Polling +2. 確認舊 Bot 完全停止 +3. 再啟動新 Bot +``` + +### Long Polling 規則 + +``` +一個 Bot Token 只能有一個 Long Polling 實例! + +✅ OpenClaw (192.168.0.188) - 唯一 Polling 實例 +❌ AWOOOI API - 只發送,不接收 +``` + +### 心跳監控 + +```python +# 防止 Telegram 靜默盲點 +heartbeat_interval = 30 minutes +silence_threshold = 2 hours + +# 超過 2 小時沒訊息 → 主動告警 +``` + +### 安全規則 + +``` +✅ Webhook Secret 驗證 +✅ Chat ID 白名單 +✅ Rate Limiting +❌ 禁止在 Telegram 傳送敏感資訊 (密碼、Token) +``` + +--- + +## 部署拓撲 + +**Memory 來源:** `feedback_deployment_topology.md`, `feedback_no_uat_environment.md` + +### 環境 + +``` +✅ Dev - 開發環境 (localhost) +✅ Prod - 生產環境 (K3s) +❌ UAT - 禁止 (資源浪費) +``` + +### 五主機架構 + +| Host | IP | 角色 | 服務 | +|------|-----|------|------| +| wooo-nas | 192.168.0.100 | NAS + 監控 | Grafana, ClickHouse | +| wooo-k3s | 192.168.0.101 | K3s Master | API 調度 | +| awoooi-110 | 192.168.0.110 | K3s Worker | GitHub Runner | +| openclaw-188 | 192.168.0.188 | AI 服務 | OpenClaw, Ollama | +| wooo-router | 192.168.0.1 | 路由 | DDNS, 防火牆 | + +### Port 分配 + +``` +:3000 - Next.js (Web) +:8000 - FastAPI (API) +:8088 - OpenClaw Legacy +:11434 - Ollama +:8123 - ClickHouse +:3001 - Grafana +:4317 - OTEL gRPC +``` + +--- + +## CI/CD + +**Memory 來源:** `feedback_github_billing.md` + +### 鐵律 + +```yaml +# ❌ 禁止 - 帳單額度限制 +runs-on: ubuntu-latest + +# ✅ 正確 - self-hosted runner +runs-on: self-hosted +``` + +### Self-Hosted Runner + +``` +位置: 192.168.0.110 (awoooi-110) +名稱: awoooi-110 +狀態: 需確認是否在線 +``` + +### 自動檢查 + +Pre-commit 腳本自動檢查: +```bash +# .claude/hooks/pre-commit-check.sh +grep -r "runs-on: ubuntu-latest" .github/workflows/ +``` + +--- + +## 構建流程 + +**Memory 來源:** `feedback_build_from_git_only.md` + +### 鐵律 + +```bash +# ❌ 禁止 - 直接在伺服器編輯 +vim /path/to/file.py +nano /path/to/config.yaml + +# ✅ 正確 - 只從 Git 部署 +git clone → build → deploy +``` + +### 部署流程 + +``` +1. 本地開發 → 2. git push → 3. CI/CD 構建 → 4. K3s 部署 +``` + +### Docker 構建 + +```yaml +# Next.js 必須 build-time 注入環境變數 +ARG NEXT_PUBLIC_API_URL +ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} +``` + +--- + +## 相關 ADR + +- [ADR-005: BFF 架構](adr/ADR-005-bff-architecture.md) +- [ADR-010: Secrets 管理](adr/ADR-010-secrets-management.md) +- [ADR-011: NetworkPolicy 治理](adr/ADR-011-networkpolicy-governance.md)