Files
awoooi/.gitea/workflows/cd.yaml
OG T b804c574c8
Some checks failed
CD Pipeline / build-and-deploy (push) Failing after 13s
E2E Health Check / e2e-health (push) Successful in 16s
fix(cd): 修復 YAML 語法錯誤 - CD 管道從 77d0fe7 後完全停止觸發
根本原因: Notify 步驟中的 text= 參數包含真實換行符,
Gitea YAML 解析器在 line 51 報錯「could not find expected ':'」,
導致 cd.yaml 無法被解析,整個 CD 管道失效超過 10+ 次 push。

修復: 換行符改用 URL encode %0A,符合 Telegram Bot API 格式。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 10:35:16 +08:00

224 lines
10 KiB
YAML
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 CD Pipeline (Gitea Actions - 方案 B)
# =============================================================================
# 流程: Build → Push to Harbor → Deploy to K8s
# 加速措施:
# 1. Docker Layer Cache → Harbor registry cache
# 2. 內部 Mirror → 192.168.0.110:5001 (Harbor Proxy Cache for DockerHub)
# 2026-03-29 Claude Code (ADR-039) - Retry after creating Harbor project
name: CD Pipeline
on:
push:
branches: [main]
workflow_dispatch:
# 2026-03-30 ogt: 佇列模式 - 等待前一個 run 完成,不取消
concurrency:
group: cd-deploy-${{ github.ref }}
cancel-in-progress: false
env:
HARBOR: 192.168.0.110:5000
# Harbor Proxy Cache (指向 DockerHub 的內部 Mirror避免拉取限額)
HARBOR_MIRROR: 192.168.0.110:5001
# OTEL CI/CD 監控 (2026-03-31 #46c - 遷移到 Gitea)
OTEL_EXPORTER_OTLP_ENDPOINT: http://192.168.0.188:24318
OTEL_SERVICE_NAME: awoooi-cd
OTEL_RESOURCE_ATTRIBUTES: service.version=${{ github.sha }},deployment.environment=production
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# 2026-03-31 ogt: 優化告警格式 - 提高可讀性
- name: Get Commit Info
id: commit
run: |
echo "short_sha=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
echo "message=$(git log -1 --pretty=%s | head -c 50)" >> $GITHUB_OUTPUT
echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT
- name: Notify Pipeline Start
run: |
curl -fS -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
-d chat_id="${{ secrets.TELEGRAM_CHAT_ID }}" \
-d parse_mode="HTML" \
-d "text=🚀 <b>AWOOOI 部署開始</b>%0A├ 📝 ${{ steps.commit.outputs.message }}%0A├ 🔖 <code>${{ steps.commit.outputs.short_sha }}</code>%0A├ 👤 ${{ github.actor }}%0A└ 🌿 main"
# 2026-03-31 ogt: Phase 22.0 CI 測試 (禁止 Mock - feedback_no_mock_testing.md)
- name: Run API Tests
run: |
cd apps/api
pip install -q uv
uv pip install --system -e ".[dev]" -q
pytest tests/ -v --tb=short --timeout=60 2>&1 | tail -50
echo "✅ API 測試通過"
- name: Login to Harbor
uses: docker/login-action@v3
with:
registry: ${{ env.HARBOR }}
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_PASSWORD }}
# ── API 鏡像建置(含 Layer Cache 加速)──────────────────────────────
- name: Build and Push API
run: |
docker build -f apps/api/Dockerfile \
--build-arg BUILDKIT_INLINE_CACHE=1 \
--cache-from ${{ env.HARBOR }}/awoooi/api:latest \
-t ${{ env.HARBOR }}/awoooi/api:${{ github.sha }} \
-t ${{ env.HARBOR }}/awoooi/api:latest \
.
docker push ${{ env.HARBOR }}/awoooi/api:${{ github.sha }}
docker push ${{ env.HARBOR }}/awoooi/api:latest
# 2026-03-31 ogt: 移除中間通知,減少訊息雜訊
# ── Web 鏡像建置(強制無快取)──────────────────────────────
# 2026-03-30 ogt: NEXT_PUBLIC_* 必須用公網域名 (build-time 寫死)
# 內網 IP 會觸發瀏覽器「存取區域網路」權限對話框
# 2026-04-01 Claude Code: 移除 --cache-from加 --no-cache
# 原因: BuildKit inline cache 導致 COPY . . 層被重用CSRF fix 未進入 bundle
- name: Build and Push Web
run: |
docker build -f apps/web/Dockerfile \
--build-arg NEXT_PUBLIC_API_URL=https://awoooi.wooo.work \
--no-cache \
-t ${{ env.HARBOR }}/awoooi/web:${{ github.sha }} \
-t ${{ env.HARBOR }}/awoooi/web:latest \
.
docker push ${{ env.HARBOR }}/awoooi/web:${{ github.sha }}
docker push ${{ env.HARBOR }}/awoooi/web:latest
# 2026-03-31 ogt: 移除中間通知
# 2026-03-31 ogt: P0-1 Secrets 自動注入 (ADR-035 強制)
# 2026-03-31 ogt: 加入 AI API Keys (修復 mock_fallback 問題)
- name: Inject K8s Secrets
env:
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
TG_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TG_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
run: |
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh -o StrictHostKeyChecking=no -i ~/.ssh/deploy_key wooo@192.168.0.121 << SECRETS
set -e
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
# 注入 Telegram Secrets (ADR-035 鐵律)
sudo kubectl patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
{"op":"replace","path":"/data/OPENCLAW_TG_BOT_TOKEN","value":"'$(echo -n "${TG_BOT_TOKEN}" | base64)'"},
{"op":"replace","path":"/data/OPENCLAW_TG_CHAT_ID","value":"'$(echo -n "${TG_CHAT_ID}" | base64)'"}
]' || echo "⚠️ Telegram Secrets patch 跳過"
# 2026-03-31 ogt: 注入 AI API Keys (修復 NVIDIA/Gemini mock_fallback)
# NVIDIA NIM (免費 tier)
if [ -n "${NVIDIA_API_KEY}" ] && [ "${NVIDIA_API_KEY}" != "" ]; then
sudo kubectl patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
{"op":"replace","path":"/data/NVIDIA_API_KEY","value":"'$(echo -n "${NVIDIA_API_KEY}" | base64)'"}
]' && echo "✅ NVIDIA_API_KEY 已注入" || echo "⚠️ NVIDIA_API_KEY patch 失敗"
else
echo "⚠️ NVIDIA_API_KEY 未設定,跳過"
fi
# Gemini (備援)
if [ -n "${GEMINI_API_KEY}" ] && [ "${GEMINI_API_KEY}" != "" ]; then
sudo kubectl patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[
{"op":"replace","path":"/data/GEMINI_API_KEY","value":"'$(echo -n "${GEMINI_API_KEY}" | base64)'"}
]' && echo "✅ GEMINI_API_KEY 已注入" || echo "⚠️ GEMINI_API_KEY patch 失敗"
else
echo "⚠️ GEMINI_API_KEY 未設定,跳過"
fi
echo "✅ 所有 Secrets 注入完成"
SECRETS
# 2026-03-31 ogt: Phase 22 修復 - CD 必須 apply ConfigMap (之前只 set image)
- name: Apply K8s ConfigMap
env:
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
run: |
cat k8s/awoooi-prod/04-configmap.yaml | \
ssh -o StrictHostKeyChecking=no -i ~/.ssh/deploy_key wooo@192.168.0.121 \
"export KUBECONFIG=/etc/rancher/k3s/k3s.yaml && sudo kubectl apply -f -"
echo "✅ ConfigMap 已更新"
- name: Deploy to K8s
env:
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
run: |
ssh -o StrictHostKeyChecking=no -i ~/.ssh/deploy_key wooo@192.168.0.121 << 'DEPLOY'
set -e
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
# 2026-03-30 ogt: sudoers NOPASSWD 已設定,無需密碼
sudo kubectl set image deployment/awoooi-api \
api=192.168.0.110:5000/awoooi/api:${{ github.sha }} \
-n awoooi-prod
sudo kubectl set image deployment/awoooi-web \
web=192.168.0.110:5000/awoooi/web:${{ github.sha }} \
-n awoooi-prod
sudo kubectl set image deployment/awoooi-worker \
worker=192.168.0.110:5000/awoooi/api:${{ github.sha }} \
-n awoooi-prod
sudo kubectl rollout status deployment/awoooi-api -n awoooi-prod --timeout=120s
sudo kubectl rollout status deployment/awoooi-web -n awoooi-prod --timeout=120s
sudo kubectl rollout status deployment/awoooi-worker -n awoooi-prod --timeout=120s
echo "✅ 部署完成"
DEPLOY
# 2026-03-31 ogt: 移除中間通知
# ── Health Check ─────────────────────────────────────────────────────
- name: Health Check
env:
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
run: |
ssh -o StrictHostKeyChecking=no -i ~/.ssh/deploy_key wooo@192.168.0.121 << 'CHECK'
sleep 10
for i in 1 2 3; do
HTTP_CODE=$(curl -s -w "%{http_code}" -o /dev/null --connect-timeout 10 "http://localhost:32334/api/v1/health")
if [ "$HTTP_CODE" = "200" ]; then
echo "✅ API 健康檢查通過"
exit 0
fi
echo "⏳ 嘗試 #$i: HTTP $HTTP_CODE等待 10s..."
sleep 10
done
echo "❌ API 健康檢查失敗"
exit 1
CHECK
- name: Notify Health Check Success
run: |
END_TIME=$(date +%s)
DURATION=$((END_TIME - ${{ steps.commit.outputs.start_time }}))
MINUTES=$((DURATION / 60))
SECONDS=$((DURATION % 60))
curl -fS -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
-d chat_id="${{ secrets.TELEGRAM_CHAT_ID }}" \
-d parse_mode="HTML" \
-d "text=✅ <b>AWOOOI 部署完成</b>%0A├ 📝 ${{ steps.commit.outputs.message }}%0A├ 🔖 <code>${{ steps.commit.outputs.short_sha }}</code>%0A├ ⏱️ 耗時: ${MINUTES}m ${SECONDS}s%0A├ 📦 API: ✅ Web: ✅%0A└ 🩺 Health: ✅"
- name: Notify Pipeline Failure
if: failure()
run: |
curl -fS -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
-d chat_id="${{ secrets.TELEGRAM_CHAT_ID }}" \
-d parse_mode="HTML" \
-d "text=❌ <b>AWOOOI 部署失敗</b>%0A├ 📝 ${{ steps.commit.outputs.message }}%0A├ 🔖 <code>${{ steps.commit.outputs.short_sha }}</code>%0A├ 👤 ${{ github.actor }}%0A└ 🔗 <a href=\"http://192.168.0.110:3001/wooo/awoooi/actions\">查看日誌</a>"