Some checks failed
CD Pipeline / deploy (push) Failing after 59s
- 建立 Gitea Actions CD pipeline (.gitea/workflows/cd.yaml) - 部署模式: rsync Python 檔案至 188 → docker restart (volume mount) - Dockerfile/requirements 變動時自動重建 Docker image - 部署通知: Telegram (開始/成功/失敗) - 健康檢查: https://mo.wooo.work/health (最多 5 次重試) - 同步最新 CLAUDE.md / ADR-008 / memory (2026-04-19) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
305 lines
8.4 KiB
Bash
Executable File
305 lines
8.4 KiB
Bash
Executable File
#!/bin/bash
|
|
# ============================================================
|
|
# MOMO Pro System - UAT 一鍵部署腳本
|
|
# ============================================================
|
|
# 用法: ./deploy_to_uat.sh [選項]
|
|
# -f, --full 完整同步 (包含所有檔案)
|
|
# -q, --quick 快速更新 (僅 Python/HTML)
|
|
# -r, --restart 僅重啟容器
|
|
# -h, --help 顯示說明
|
|
#
|
|
# 預設行為: 快速更新 + 重啟容器
|
|
# ============================================================
|
|
|
|
set -e # 發生錯誤時停止執行
|
|
|
|
# === 配置 ===
|
|
UAT_HOST="wooo@192.168.0.110"
|
|
UAT_PATH="/home/wooo/momo_pro_system"
|
|
LOCAL_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
CONTAINER_NAME="momo-pro-system"
|
|
|
|
# === 顏色輸出 ===
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# === 函數定義 ===
|
|
|
|
print_header() {
|
|
echo ""
|
|
echo -e "${BLUE}============================================================${NC}"
|
|
echo -e "${BLUE} MOMO Pro System - UAT 部署工具${NC}"
|
|
echo -e "${BLUE}============================================================${NC}"
|
|
echo -e " 目標主機: ${GREEN}${UAT_HOST}${NC}"
|
|
echo -e " 目標路徑: ${GREEN}${UAT_PATH}${NC}"
|
|
echo ""
|
|
}
|
|
|
|
print_step() {
|
|
echo -e "${YELLOW}[步驟]${NC} $1"
|
|
}
|
|
|
|
print_success() {
|
|
echo -e "${GREEN}[成功]${NC} $1"
|
|
}
|
|
|
|
print_error() {
|
|
echo -e "${RED}[錯誤]${NC} $1"
|
|
}
|
|
|
|
show_help() {
|
|
echo "用法: $0 [選項]"
|
|
echo ""
|
|
echo "選項:"
|
|
echo " -f, --full 完整同步 (rsync 整個專案,排除 data/logs/venv)"
|
|
echo " -q, --quick 快速更新 (僅同步 Python 和 HTML 檔案)"
|
|
echo " -r, --restart 僅重啟容器 (不同步檔案)"
|
|
echo " -m, --monitor 啟動監控服務 (Grafana/Prometheus/Loki)"
|
|
echo " -v, --verify 驗證部署狀態"
|
|
echo " -h, --help 顯示此說明"
|
|
echo ""
|
|
echo "範例:"
|
|
echo " $0 # 預設: 快速更新 + 重啟"
|
|
echo " $0 -f # 完整同步 + 重啟"
|
|
echo " $0 -r # 僅重啟容器"
|
|
echo " $0 -v # 驗證部署狀態"
|
|
}
|
|
|
|
# 檢查 SSH 連線
|
|
check_ssh() {
|
|
print_step "檢查 SSH 連線..."
|
|
if ssh -o ConnectTimeout=5 ${UAT_HOST} "echo 'SSH OK'" > /dev/null 2>&1; then
|
|
print_success "SSH 連線正常"
|
|
return 0
|
|
else
|
|
print_error "無法連線到 ${UAT_HOST}"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# 快速更新 (僅 Python/HTML)
|
|
quick_sync() {
|
|
print_step "快速同步 Python 和 HTML 檔案..."
|
|
|
|
# 同步 routes 目錄
|
|
echo " - 同步 routes/*.py"
|
|
scp -q ${LOCAL_PATH}/routes/*.py ${UAT_HOST}:${UAT_PATH}/routes/
|
|
|
|
# 同步根目錄的 Python 檔案
|
|
echo " - 同步根目錄 Python 檔案"
|
|
scp -q ${LOCAL_PATH}/app.py ${UAT_HOST}:${UAT_PATH}/
|
|
scp -q ${LOCAL_PATH}/auth.py ${UAT_HOST}:${UAT_PATH}/
|
|
scp -q ${LOCAL_PATH}/config.py ${UAT_HOST}:${UAT_PATH}/
|
|
scp -q ${LOCAL_PATH}/auto_import_routes.py ${UAT_HOST}:${UAT_PATH}/ 2>/dev/null || true
|
|
scp -q ${LOCAL_PATH}/vendor_routes.py ${UAT_HOST}:${UAT_PATH}/ 2>/dev/null || true
|
|
scp -q ${LOCAL_PATH}/crawler_management_routes.py ${UAT_HOST}:${UAT_PATH}/ 2>/dev/null || true
|
|
|
|
# 同步 services 目錄
|
|
echo " - 同步 services/*.py"
|
|
scp -q ${LOCAL_PATH}/services/*.py ${UAT_HOST}:${UAT_PATH}/services/
|
|
|
|
# 同步 database 目錄
|
|
echo " - 同步 database/*.py"
|
|
scp -q ${LOCAL_PATH}/database/*.py ${UAT_HOST}:${UAT_PATH}/database/
|
|
|
|
# 同步 templates 目錄
|
|
echo " - 同步 templates/"
|
|
rsync -az --delete -e ssh \
|
|
${LOCAL_PATH}/templates/ \
|
|
${UAT_HOST}:${UAT_PATH}/templates/
|
|
|
|
# 同步根目錄的 HTML 檔案
|
|
echo " - 同步根目錄 HTML 檔案"
|
|
scp -q ${LOCAL_PATH}/*.html ${UAT_HOST}:${UAT_PATH}/ 2>/dev/null || true
|
|
|
|
print_success "快速同步完成"
|
|
}
|
|
|
|
# 完整同步 (rsync)
|
|
full_sync() {
|
|
print_step "完整同步專案檔案..."
|
|
|
|
rsync -avz --progress -e ssh \
|
|
--exclude='venv' \
|
|
--exclude='__pycache__' \
|
|
--exclude='*.pyc' \
|
|
--exclude='.git' \
|
|
--exclude='backups' \
|
|
--exclude='logs/*.log' \
|
|
--exclude='data/*.db' \
|
|
--exclude='data/*.db-*' \
|
|
--exclude='.env' \
|
|
--exclude='*.tar.gz' \
|
|
${LOCAL_PATH}/ \
|
|
${UAT_HOST}:${UAT_PATH}/
|
|
|
|
print_success "完整同步完成"
|
|
}
|
|
|
|
# 重啟容器
|
|
restart_container() {
|
|
print_step "重啟 Docker 容器 (${CONTAINER_NAME})..."
|
|
|
|
ssh ${UAT_HOST} "docker restart ${CONTAINER_NAME}"
|
|
|
|
# 等待容器啟動
|
|
echo " - 等待容器啟動..."
|
|
sleep 5
|
|
|
|
# 檢查容器狀態
|
|
local status=$(ssh ${UAT_HOST} "docker ps --filter name=${CONTAINER_NAME} --format '{{.Status}}' | head -1")
|
|
|
|
if [[ $status == *"healthy"* ]] || [[ $status == *"Up"* ]]; then
|
|
print_success "容器已啟動: ${status}"
|
|
else
|
|
print_error "容器狀態異常: ${status}"
|
|
echo " 查看日誌: ssh ${UAT_HOST} 'docker logs ${CONTAINER_NAME} --tail 50'"
|
|
fi
|
|
}
|
|
|
|
# 重建容器 (當 docker-compose.yml 有變更時)
|
|
recreate_container() {
|
|
print_step "重建 Docker 容器..."
|
|
|
|
ssh ${UAT_HOST} "cd ${UAT_PATH} && \
|
|
docker rm -f ${CONTAINER_NAME} 2>/dev/null || true && \
|
|
docker-compose up -d momo-app"
|
|
|
|
# 等待容器啟動
|
|
echo " - 等待容器啟動..."
|
|
sleep 8
|
|
|
|
local status=$(ssh ${UAT_HOST} "docker ps --filter name=${CONTAINER_NAME} --format '{{.Status}}' | head -1")
|
|
print_success "容器已重建: ${status}"
|
|
}
|
|
|
|
# 啟動監控服務
|
|
start_monitoring() {
|
|
print_step "啟動監控服務..."
|
|
|
|
ssh ${UAT_HOST} "cd ${UAT_PATH} && docker-compose --profile monitoring up -d"
|
|
|
|
print_success "監控服務已啟動"
|
|
echo " - Grafana: http://192.168.0.110:3000"
|
|
echo " - Prometheus: http://192.168.0.110:9090"
|
|
}
|
|
|
|
# 驗證部署
|
|
verify_deployment() {
|
|
print_step "驗證部署狀態..."
|
|
|
|
echo ""
|
|
echo " [容器狀態]"
|
|
ssh ${UAT_HOST} "docker ps --filter name=momo --format 'table {{.Names}}\t{{.Status}}' | head -10"
|
|
|
|
echo ""
|
|
echo " [健康檢查]"
|
|
local health=$(curl -s -o /dev/null -w "%{http_code}" "https://mo.wooo.work/health" 2>/dev/null || echo "000")
|
|
if [ "$health" = "200" ]; then
|
|
echo -e " /health: ${GREEN}200 OK${NC}"
|
|
else
|
|
echo -e " /health: ${RED}${health}${NC}"
|
|
fi
|
|
|
|
echo ""
|
|
echo " [登入保護驗證] (應為 302)"
|
|
for path in "/" "/auto_import" "/daily_sales" "/sales_analysis"; do
|
|
local code=$(curl -s -o /dev/null -w "%{http_code}" "https://mo.wooo.work${path}" 2>/dev/null || echo "000")
|
|
if [ "$code" = "302" ]; then
|
|
echo -e " ${path}: ${GREEN}${code}${NC}"
|
|
else
|
|
echo -e " ${path}: ${RED}${code}${NC}"
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
echo " [公開端點] (應為 200)"
|
|
for path in "/health" "/metrics"; do
|
|
local code=$(curl -s -o /dev/null -w "%{http_code}" "https://mo.wooo.work${path}" 2>/dev/null || echo "000")
|
|
if [ "$code" = "200" ]; then
|
|
echo -e " ${path}: ${GREEN}${code}${NC}"
|
|
else
|
|
echo -e " ${path}: ${RED}${code}${NC}"
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
print_success "驗證完成"
|
|
}
|
|
|
|
# === 主程式 ===
|
|
|
|
print_header
|
|
|
|
# 解析參數
|
|
MODE="quick" # 預設模式
|
|
RESTART=true
|
|
VERIFY=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-f|--full)
|
|
MODE="full"
|
|
shift
|
|
;;
|
|
-q|--quick)
|
|
MODE="quick"
|
|
shift
|
|
;;
|
|
-r|--restart)
|
|
MODE="restart_only"
|
|
shift
|
|
;;
|
|
-m|--monitor)
|
|
MODE="monitor"
|
|
shift
|
|
;;
|
|
-v|--verify)
|
|
VERIFY=true
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "未知選項: $1"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# 執行
|
|
check_ssh
|
|
|
|
case $MODE in
|
|
"quick")
|
|
quick_sync
|
|
restart_container
|
|
;;
|
|
"full")
|
|
full_sync
|
|
recreate_container
|
|
;;
|
|
"restart_only")
|
|
restart_container
|
|
;;
|
|
"monitor")
|
|
start_monitoring
|
|
;;
|
|
esac
|
|
|
|
if [ "$VERIFY" = true ] || [ "$MODE" != "monitor" ]; then
|
|
verify_deployment
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${GREEN}============================================================${NC}"
|
|
echo -e "${GREEN} 部署完成!${NC}"
|
|
echo -e "${GREEN}============================================================${NC}"
|
|
echo ""
|