Files
awoooi/.github/workflows/cd.yaml
OG T e25d7bd13f feat(sentry): add Sentry DSN to CI/CD build process
- Add NEXT_PUBLIC_SENTRY_DSN to CI/CD workflows (build-time injection)
- Add SENTRY_DSN build arg to web Dockerfile
- Sentry Self-Hosted deployed on 192.168.0.110:9000
- GeoIP database configured (MaxMind GeoLite2-City 61MB)
- awoooi-web project: http://da02...@192.168.0.110:9000/2
- awoooi-api project: http://8c4a...@192.168.0.110:9000/3

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-24 15:33:36 +08:00

256 lines
9.9 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.
name: CD
on:
push:
branches: [main]
paths-ignore:
- 'docs/**'
- '*.md'
workflow_dispatch:
inputs:
skip_api:
description: '跳過 API 建構 (只改前端時)'
type: boolean
default: false
skip_web:
description: '跳過 Web 建構 (只改後端時)'
type: boolean
default: false
# 沿用 AIOPS 設計: 新 commit 自動取消舊 workflow
concurrency:
group: cd-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
REGISTRY: 192.168.0.110:5000
IMAGE_PREFIX: library/awoooi
# 本地快取路徑 (比 Registry cache 更快)
LOCAL_CACHE_DIR: /home/wooo/build-cache/awoooi
jobs:
# ==================== 變更偵測 ====================
detect-changes:
name: Detect Changes
runs-on: self-hosted
outputs:
api_changed: ${{ steps.changes.outputs.api }}
web_changed: ${{ steps.changes.outputs.web }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Check changed files
id: changes
run: |
# 取得變更的檔案
CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "")
echo "Changed files: $CHANGED_FILES"
# 偵測 API 變更
if echo "$CHANGED_FILES" | grep -qE "^apps/api/|^packages/"; then
echo "api=true" >> $GITHUB_OUTPUT
else
echo "api=false" >> $GITHUB_OUTPUT
fi
# 偵測 Web 變更
if echo "$CHANGED_FILES" | grep -qE "^apps/web/|^packages/"; then
echo "web=true" >> $GITHUB_OUTPUT
else
echo "web=false" >> $GITHUB_OUTPUT
fi
# ==================== Build API ====================
build-api:
name: Build & Push API
runs-on: self-hosted
needs: detect-changes
if: |
github.event_name == 'workflow_dispatch' && !inputs.skip_api ||
github.event_name == 'push' && needs.detect-changes.outputs.api_changed == 'true' ||
github.event_name == 'push' && needs.detect-changes.outputs.api_changed == 'false' && needs.detect-changes.outputs.web_changed == 'false'
outputs:
image_tag: ${{ steps.tag.outputs.tag }}
steps:
- uses: actions/checkout@v4
- name: Generate image tag
id: tag
run: |
SHA=$(git rev-parse --short HEAD)
RUN_ID=${{ github.run_id }}
echo "tag=${SHA}-${RUN_ID}" >> $GITHUB_OUTPUT
- name: Login to Harbor
run: |
echo "${{ secrets.HARBOR_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u ${{ secrets.HARBOR_USER }} --password-stdin
# 🚀 沿用 AIOPS: 原生 BuildKit (無中間層損耗)
- name: Build & Push API (Native BuildKit)
env:
DOCKER_BUILDKIT: 1
run: |
echo "🐳 使用原生 Docker BuildKit 建構 API..."
docker build \
--push \
--tag ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-api:${{ steps.tag.outputs.tag }} \
--file apps/api/Dockerfile \
.
echo "✅ API 映像: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-api:${{ steps.tag.outputs.tag }}"
# ==================== Build Web ====================
build-web:
name: Build & Push Web
runs-on: self-hosted
needs: detect-changes
if: |
github.event_name == 'workflow_dispatch' && !inputs.skip_web ||
github.event_name == 'push' && needs.detect-changes.outputs.web_changed == 'true' ||
github.event_name == 'push' && needs.detect-changes.outputs.api_changed == 'false' && needs.detect-changes.outputs.web_changed == 'false'
outputs:
image_tag: ${{ steps.tag.outputs.tag }}
steps:
- uses: actions/checkout@v4
- name: Generate image tag
id: tag
run: |
SHA=$(git rev-parse --short HEAD)
RUN_ID=${{ github.run_id }}
echo "tag=${SHA}-${RUN_ID}" >> $GITHUB_OUTPUT
- name: Login to Harbor
run: |
echo "${{ secrets.HARBOR_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u ${{ secrets.HARBOR_USER }} --password-stdin
# 🚀 沿用 AIOPS: 恢復本地 Next.js 快取
- name: Restore Next.js cache
run: |
mkdir -p apps/web/.next/cache
if [ -d "${{ env.LOCAL_CACHE_DIR }}/nextjs" ]; then
cp -r ${{ env.LOCAL_CACHE_DIR }}/nextjs/* apps/web/.next/cache/ 2>/dev/null || true
echo "✅ Next.js 快取已恢復"
fi
# 🚀 沿用 AIOPS: 原生 BuildKit
- name: Build & Push Web (Native BuildKit)
env:
DOCKER_BUILDKIT: 1
run: |
echo "🎨 使用原生 Docker BuildKit 建構 Web..."
docker build \
--push \
--build-arg NEXT_PUBLIC_API_URL=https://awoooi.wooo.work \
--build-arg NEXT_PUBLIC_SENTRY_DSN=http://da02d4e5d6542e4d1ed6b2dd6542efeb@192.168.0.110:9000/2 \
--tag ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-web:${{ steps.tag.outputs.tag }} \
--file apps/web/Dockerfile \
.
echo "✅ Web 映像: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-web:${{ steps.tag.outputs.tag }}"
# 🚀 沿用 AIOPS: 儲存本地快取
- name: Save Next.js cache
run: |
mkdir -p ${{ env.LOCAL_CACHE_DIR }}/nextjs
if [ -d "apps/web/.next/cache" ]; then
cp -r apps/web/.next/cache/* ${{ env.LOCAL_CACHE_DIR }}/nextjs/ 2>/dev/null || true
echo "✅ Next.js 快取已儲存"
fi
# ==================== Deploy to Production ====================
# Memory 鐵律: 禁止 UAT只有 Dev + Prod
deploy-prod:
name: Deploy to Production
runs-on: self-hosted
needs: [detect-changes, build-api, build-web]
# 允許部分 build 被跳過
if: always() && (needs.build-api.result == 'success' || needs.build-api.result == 'skipped') && (needs.build-web.result == 'success' || needs.build-web.result == 'skipped')
environment: production
steps:
- uses: actions/checkout@v4
- name: Setup Kubeconfig
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBE_CONFIG_PROD }}" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
- name: Install kubectl and Kustomize
run: |
mkdir -p $HOME/.local/bin
# Install kubectl (110 主機應已預裝)
if ! command -v kubectl &> /dev/null; then
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
mv kubectl $HOME/.local/bin/
fi
# Install kustomize (110 主機應已預裝)
if ! command -v kustomize &> /dev/null; then
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
mv kustomize $HOME/.local/bin/
fi
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Generate image tag
id: tag
run: |
SHA=$(git rev-parse --short HEAD)
RUN_ID=${{ github.run_id }}
echo "tag=${SHA}-${RUN_ID}" >> $GITHUB_OUTPUT
- name: Verify Kubeconfig
run: |
export PATH="$HOME/.local/bin:$PATH"
echo "Checking kubeconfig..."
kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'
echo ""
kubectl cluster-info
- name: Deploy with Kustomize
run: |
export PATH="$HOME/.local/bin:$PATH"
cd k8s/awoooi-prod
# 使用 kustomize edit set image: OLD_IMAGE=NEW_IMAGE
# OLD_IMAGE 必須與 deployment YAML 中的 image 欄位完全匹配
kustomize edit set image \
"192.168.0.110:5000/library/web:IMAGE_TAG_PLACEHOLDER=${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-web:${{ steps.tag.outputs.tag }}" \
"192.168.0.110:5000/library/api:IMAGE_TAG_PLACEHOLDER=${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-api:${{ steps.tag.outputs.tag }}"
kubectl apply -k .
- name: Wait for rollout
run: |
export PATH="$HOME/.local/bin:$PATH"
kubectl rollout status deployment/awoooi-web -n awoooi-prod --timeout=300s
kubectl rollout status deployment/awoooi-api -n awoooi-prod --timeout=300s
- name: Health check
run: |
export PATH="$HOME/.local/bin:$PATH"
sleep 10
# 使用 kubectl 驗證 Pod 健康 (避免 runner DNS 問題)
echo "🔍 檢查 API Pod 狀態..."
kubectl get pods -n awoooi-prod -l app=awoooi-api -o jsonpath='{.items[*].status.phase}' | grep -q Running
echo "✅ API Pod Running"
# 透過 kubectl exec 測試內部健康端點
API_POD=$(kubectl get pods -n awoooi-prod -l app=awoooi-api -o jsonpath='{.items[0].metadata.name}')
kubectl exec -n awoooi-prod $API_POD -- curl -sf http://localhost:8000/api/v1/health || exit 1
echo "✅ API 內部健康檢查通過"
- name: Notify Telegram on Success
if: success()
run: |
curl -s -X POST "https://api.telegram.org/bot${{ secrets.OPENCLAW_TG_BOT_TOKEN }}/sendMessage" \
-d chat_id="${{ secrets.OPENCLAW_TG_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.OPENCLAW_TG_BOT_TOKEN }}/sendMessage" \
-d chat_id="${{ secrets.OPENCLAW_TG_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"