All checks were successful
CD Pipeline / deploy (push) Successful in 1m19s
P2 (Inline Keyboard 降價決策): - routes/bot_api_routes.py: POST /bot/api/price-decision/notify - services/telegram_bot_service.py: pa:/pr: callback handlers P3 (OpenClaw 自動觸發): - services/openclaw_strategist_service.py: Gemini 週報末尾輸出 PRICE_DECISIONS_JSON,解析後自動推送 inline keyboard 給 admin Ops 修復(跨專案隔離與容器斷訊根因): - ADR-011 全面規範多專案共存邊界、禁用 --remove-orphans - .gitea/workflows/cd.yaml: sync 模式一次重啟三容器 (原本僅 momo-pro-system,scheduler/telegram-bot 靜默落伍) - run_telegram_bot.py: 從 scripts/tools/ 複製到根目錄 (消滅 docker-compose mount 建空目錄的陷阱) - CLAUDE.md: 補核心容器表、診斷黃金三句、緊急指令 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
202 lines
8.8 KiB
YAML
202 lines
8.8 KiB
YAML
# =============================================================================
|
||
# EwoooC CD Pipeline (Gitea Actions)
|
||
# =============================================================================
|
||
# 流程: Sync Files → 188 Docker Restart → Health Check
|
||
# 部署架構: Docker Compose on ollama@192.168.0.188 (Volume Mount)
|
||
# 加速措施: Python 檔案走 rsync,僅 Dockerfile/requirements 變動才重建 image
|
||
# 參考: AWOOOI cd.yaml pattern (ADR-008 — Docker Compose 非 K8s)
|
||
|
||
name: CD Pipeline
|
||
|
||
on:
|
||
push:
|
||
branches: [main]
|
||
paths:
|
||
# 應用程式碼(volume-mounted)
|
||
- 'app.py'
|
||
- 'auth.py'
|
||
- 'config.py'
|
||
- 'scheduler.py'
|
||
- 'services/**'
|
||
- 'routes/**'
|
||
- 'database/**'
|
||
- 'templates/**'
|
||
- 'static/**'
|
||
# 需重建 image 的檔案
|
||
- 'Dockerfile'
|
||
- 'requirements.txt'
|
||
- 'docker-compose.yml'
|
||
# 工作流程本身
|
||
- '.gitea/workflows/**'
|
||
# docs/、memory/、ADR、k8s/ 等不觸發
|
||
workflow_dispatch:
|
||
# 手動觸發永遠可用(用於補跑、緊急部署)
|
||
|
||
# 新 push 立即取消舊 job,只部署最新版本
|
||
concurrency:
|
||
group: cd-deploy-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
jobs:
|
||
deploy:
|
||
timeout-minutes: 20
|
||
runs-on: ubuntu-latest
|
||
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
fetch-depth: 2
|
||
|
||
- name: 取得 Commit 資訊
|
||
id: commit
|
||
run: |
|
||
echo "short_sha=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
|
||
echo "message=$(git log -1 --pretty=%s | head -c 60)" >> $GITHUB_OUTPUT
|
||
echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT
|
||
echo "actor=${{ github.actor }}" >> $GITHUB_OUTPUT
|
||
|
||
# 偵測是否需重建 Docker image(Dockerfile / requirements.txt / docker-compose.yml 變動)
|
||
- name: 偵測部署類型
|
||
id: deploy_type
|
||
run: |
|
||
CHANGED=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || echo "")
|
||
if echo "$CHANGED" | grep -qE '^(Dockerfile|requirements\.txt|docker-compose\.yml)$'; then
|
||
echo "type=rebuild" >> $GITHUB_OUTPUT
|
||
echo "label=🔨 重建 Docker Image" >> $GITHUB_OUTPUT
|
||
else
|
||
echo "type=sync" >> $GITHUB_OUTPUT
|
||
echo "label=📁 同步 Python 檔案" >> $GITHUB_OUTPUT
|
||
fi
|
||
|
||
# 設定 SSH 金鑰(用於連線到 188)
|
||
- name: 設定 SSH 金鑰
|
||
run: |
|
||
mkdir -p ~/.ssh
|
||
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_deploy
|
||
chmod 600 ~/.ssh/id_deploy
|
||
cat > ~/.ssh/config << 'EOF'
|
||
Host 192.168.0.188
|
||
HostName 192.168.0.188
|
||
User ollama
|
||
IdentityFile ~/.ssh/id_deploy
|
||
StrictHostKeyChecking no
|
||
ConnectTimeout 10
|
||
EOF
|
||
|
||
- name: 通知部署開始
|
||
run: |
|
||
COMMIT_ESC=$(echo "${{ steps.commit.outputs.message }}" | sed 's/&/\&/g; s/</\</g; s/>/\>/g')
|
||
MSG=$(printf '🚀 <b>EwoooC 部署開始</b>\n├ 📝 <code>%s</code>\n├ 🔖 <code>%s</code>\n├ 👤 %s\n└ %s' \
|
||
"${COMMIT_ESC}" \
|
||
"${{ steps.commit.outputs.short_sha }}" \
|
||
"${{ steps.commit.outputs.actor }}" \
|
||
"${{ steps.deploy_type.outputs.label }}")
|
||
curl -fS -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
|
||
-H "Content-Type: application/json" \
|
||
-d "$(jq -n --arg c "${{ secrets.TELEGRAM_CHAT_ID }}" --arg t "$MSG" '{chat_id:$c,text:$t,parse_mode:"HTML"}')"
|
||
|
||
# ── 安裝部署工具 ────────────────────────────────────────────────────
|
||
- name: 安裝 rsync / ssh
|
||
run: |
|
||
apt-get update -qq && apt-get install -y -qq rsync openssh-client
|
||
|
||
# ── 模式 A:僅同步 Python 檔案(最常見,~10s) ────────────────────────
|
||
- name: 同步 Python 檔案至 188
|
||
if: steps.deploy_type.outputs.type == 'sync'
|
||
run: |
|
||
rsync -avz \
|
||
-e "ssh -i ~/.ssh/id_deploy -o StrictHostKeyChecking=no" \
|
||
--exclude='.git/' \
|
||
--exclude='.gitea/' \
|
||
--exclude='data/' \
|
||
--exclude='logs/' \
|
||
--exclude='backups/' \
|
||
--exclude='config/google_credentials.json' \
|
||
--exclude='config/google_token.pickle' \
|
||
--exclude='venv/' \
|
||
--exclude='__pycache__/' \
|
||
--exclude='*.pyc' \
|
||
--exclude='.env' \
|
||
--exclude='*.db' \
|
||
--exclude='*.db-journal' \
|
||
--exclude='docs/' \
|
||
--exclude='memory/' \
|
||
--exclude='k8s/' \
|
||
--exclude='n8n-workflows/' \
|
||
--exclude='aiops-core/' \
|
||
./ ollama@192.168.0.188:/home/ollama/momo-pro/
|
||
|
||
- name: 重啟容器(Sync 模式)
|
||
if: steps.deploy_type.outputs.type == 'sync'
|
||
run: |
|
||
ssh -i ~/.ssh/id_deploy -o StrictHostKeyChecking=no ollama@192.168.0.188 \
|
||
"docker restart momo-pro-system momo-scheduler momo-telegram-bot 2>&1 && \
|
||
echo '✅ 三容器已重啟(app/scheduler/telegram-bot)'"
|
||
|
||
# ── 模式 B:重建 Docker Image(Dockerfile / requirements.txt 變動) ──
|
||
- name: 同步所有檔案並重建 Image
|
||
if: steps.deploy_type.outputs.type == 'rebuild'
|
||
run: |
|
||
# 先同步全部檔案
|
||
rsync -avz \
|
||
-e "ssh -i ~/.ssh/id_deploy -o StrictHostKeyChecking=no" \
|
||
--exclude='.git/' \
|
||
--exclude='data/' \
|
||
--exclude='logs/' \
|
||
--exclude='backups/' \
|
||
--exclude='config/google_credentials.json' \
|
||
--exclude='config/google_token.pickle' \
|
||
--exclude='venv/' \
|
||
--exclude='__pycache__/' \
|
||
--exclude='*.pyc' \
|
||
--exclude='.env' \
|
||
--exclude='*.db' \
|
||
./ ollama@192.168.0.188:/home/ollama/momo-pro/
|
||
# 重建並重啟
|
||
ssh -i ~/.ssh/id_deploy -o StrictHostKeyChecking=no ollama@192.168.0.188 \
|
||
"cd /home/ollama/momo-pro && docker compose build momo-app && docker compose up -d --force-recreate momo-app && echo '✅ Image 重建完成'"
|
||
|
||
# ── 健康檢查(最多重試 5 次,每次間隔 10s) ───────────────────────────
|
||
- name: 健康檢查
|
||
run: |
|
||
echo "⏳ 等待服務啟動(15s)..."
|
||
sleep 15
|
||
for i in $(seq 1 5); do
|
||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://mo.wooo.work/health --max-time 10 || echo "000")
|
||
if [ "$HTTP_CODE" = "200" ]; then
|
||
echo "✅ 健康檢查通過(HTTP $HTTP_CODE)"
|
||
exit 0
|
||
fi
|
||
echo "⏳ 嘗試 $i/5,HTTP $HTTP_CODE,等待 10s..."
|
||
sleep 10
|
||
done
|
||
echo "❌ 健康檢查失敗"
|
||
exit 1
|
||
|
||
# ── 部署成功通知 ──────────────────────────────────────────────────────
|
||
- name: 通知部署成功
|
||
if: success()
|
||
run: |
|
||
END_TIME=$(date +%s)
|
||
DURATION=$((END_TIME - ${{ steps.commit.outputs.start_time }}))
|
||
COMMIT_ESC=$(echo "${{ steps.commit.outputs.message }}" | sed 's/&/\&/g; s/</\</g; s/>/\>/g')
|
||
MSG=$(printf '✅ <b>EwoooC 部署成功</b>\n├ 📝 <code>%s</code>\n├ 🔖 <code>%s</code>\n├ ⏱ 耗時 %ss\n└ 🌐 https://mo.wooo.work' \
|
||
"${COMMIT_ESC}" \
|
||
"${{ steps.commit.outputs.short_sha }}" \
|
||
"${DURATION}")
|
||
curl -fS -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
|
||
-H "Content-Type: application/json" \
|
||
-d "$(jq -n --arg c "${{ secrets.TELEGRAM_CHAT_ID }}" --arg t "$MSG" '{chat_id:$c,text:$t,parse_mode:"HTML"}')"
|
||
|
||
# ── 部署失敗通知 ──────────────────────────────────────────────────────
|
||
- name: 通知部署失敗
|
||
if: failure()
|
||
run: |
|
||
COMMIT_ESC=$(echo "${{ steps.commit.outputs.message }}" | sed 's/&/\&/g; s/</\</g; s/>/\>/g')
|
||
MSG=$(printf '❌ <b>EwoooC 部署失敗</b>\n├ 📝 <code>%s</code>\n├ 🔖 <code>%s</code>\n└ 🔍 請查看 Gitea Actions 日誌' \
|
||
"${COMMIT_ESC}" \
|
||
"${{ steps.commit.outputs.short_sha }}")
|
||
curl -fS -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
|
||
-H "Content-Type: application/json" \
|
||
-d "$(jq -n --arg c "${{ secrets.TELEGRAM_CHAT_ID }}" --arg t "$MSG" '{chat_id:$c,text:$t,parse_mode:"HTML"}')"
|