f1337d9b5b
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 14s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Build & Package / build (push) Failing after 1m28s
Deploy to Production (Local) / Build & Deploy to Production (push) Failing after 1m26s
- **Version naming**: Include date, time, commit hash, and CI run number Format: quantengine_YYYYMMDD_HHMMSS_COMMIT_HASH_RUNNUM - **Cleanup script**: Auto-remove old versions to prevent disk exhaustion - Keep 5 most recent by default - Remove staging/test versions - Can be run weekly via cron or after deployments - Supports dry-run mode for validation Addresses: Disk usage management for long-running CI/CD pipeline Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
379 lines
14 KiB
YAML
379 lines
14 KiB
YAML
name: Deploy to Production (Local)
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: deploy-prod-main
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
DEPLOY_HOST: quant.taxbaik.com
|
|
DEPLOY_USER: kjh2064
|
|
SERVICE_NAME: quantengine
|
|
DOTNET_VERSION: '10.0.x'
|
|
QUANTENGINE_DB_NAME: quantenginedb
|
|
QUANTENGINE_DB_USER: quantengine_app
|
|
TELEGRAM_BOT_TOKEN_DEFAULT: "8734507814:AAFyacLMai8GB4K-hQ_Nd3t3D01A-H1ZdV0"
|
|
TELEGRAM_CHAT_ID_DEFAULT: "-5460205872"
|
|
|
|
jobs:
|
|
build-and-deploy:
|
|
name: Build & Deploy to Production
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
|
|
steps:
|
|
- name: Checkout Code
|
|
uses: actions/checkout@v3
|
|
|
|
- name: Setup .NET
|
|
uses: actions/setup-dotnet@v3
|
|
with:
|
|
dotnet-version: ${{ env.DOTNET_VERSION }}
|
|
|
|
- name: Setup Python
|
|
uses: actions/setup-python@v4
|
|
with:
|
|
python-version: '3.10'
|
|
|
|
- name: Install Python Dependencies
|
|
run: pip install pyyaml openpyxl requests
|
|
|
|
- name: "[GATE] Run Core Validations"
|
|
run: |
|
|
echo "🔐 Running critical CI validations..."
|
|
python3 tools/validate_no_direct_api_trading_v1.py || exit 1
|
|
python3 tools/validate_specs.py || exit 1
|
|
echo "✅ All critical validations passed"
|
|
|
|
- name: Ensure Temp Directory and Mock Packet
|
|
run: |
|
|
mkdir -p Temp
|
|
if [ ! -f Temp/final_decision_packet_active.json ]; then
|
|
echo '{"active_decision": "PASS", "details": "CI dummy packet"}' > Temp/final_decision_packet_active.json
|
|
fi
|
|
|
|
- name: Restore Dependencies
|
|
run: dotnet restore src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj
|
|
|
|
- name: Build Release
|
|
run: |
|
|
dotnet build src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj \
|
|
-c Release \
|
|
--no-restore
|
|
|
|
- name: Run Unit Tests
|
|
run: |
|
|
dotnet test src/dotnet/QuantEngine.Core.Tests/QuantEngine.Core.Tests.csproj \
|
|
-c Release \
|
|
--no-build
|
|
|
|
- name: Publish Release Package
|
|
run: |
|
|
dotnet publish src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj \
|
|
-c Release \
|
|
--no-build \
|
|
-o ./publish
|
|
|
|
- name: Generate Build Info
|
|
run: |
|
|
COMMIT_HASH=$(git rev-parse --short HEAD)
|
|
BUILD_TIME=$(date -d "+9 hours" +'%Y-%m-%d %H:%M:%S KST')
|
|
mkdir -p ./publish/wwwroot
|
|
printf '{\n "version": "1.0.%s-%s",\n "built": "%s"\n}\n' "${{ github.run_number }}" "$COMMIT_HASH" "$BUILD_TIME" > ./publish/wwwroot/version.json
|
|
echo "✓ Generated version info: 1.0.${{ github.run_number }}-$COMMIT_HASH @ $BUILD_TIME"
|
|
|
|
- name: Prepare & Validate QuantEngine DB Env
|
|
run: |
|
|
echo "🔧 Preparing database environment..."
|
|
|
|
DB_PASSWORD="${{ secrets.QUANTENGINE_DB_PASSWORD }}"
|
|
if [ -z "$DB_PASSWORD" ]; then
|
|
echo "⚠️ QUANTENGINE_DB_PASSWORD not set — using empty password"
|
|
fi
|
|
|
|
if [ -z "${{ env.QUANTENGINE_DB_NAME }}" ] || [ -z "${{ env.QUANTENGINE_DB_USER }}" ]; then
|
|
echo "❌ DB configuration environment variables not set"
|
|
exit 1
|
|
fi
|
|
|
|
# 배포 폴더에 환경 설정 생성
|
|
mkdir -p ./deploy
|
|
printf 'ConnectionStrings__DefaultConnection=Host=127.0.0.1;Database=%s;Username=%s;Password=%s;Search Path=quantengine;\n' \
|
|
"${{ env.QUANTENGINE_DB_NAME }}" \
|
|
"${{ env.QUANTENGINE_DB_USER }}" \
|
|
"$DB_PASSWORD" > ./deploy/quantengine.env
|
|
chmod 600 ./deploy/quantengine.env
|
|
|
|
# appsettings.Production.json 생성
|
|
mkdir -p ./publish
|
|
cat <<EOF > ./publish/appsettings.Production.json
|
|
{
|
|
"ConnectionStrings": {
|
|
"DefaultConnection": "Host=127.0.0.1;Database=${{ env.QUANTENGINE_DB_NAME }};Username=${{ env.QUANTENGINE_DB_USER }};Password=${DB_PASSWORD};Search Path=quantengine;"
|
|
}
|
|
}
|
|
EOF
|
|
chmod 600 ./publish/appsettings.Production.json
|
|
|
|
if [ ! -f ./deploy/quantengine.env ] || [ ! -f ./publish/appsettings.Production.json ]; then
|
|
echo "❌ Failed to create database config files"
|
|
exit 1
|
|
fi
|
|
|
|
echo "✓ Database configuration prepared"
|
|
|
|
- name: Copy Deployment Scripts
|
|
run: |
|
|
echo "📋 Copying deployment scripts..."
|
|
cp deploy_gb.sh ./publish/deploy_gb.sh
|
|
mkdir -p ./publish/scripts
|
|
cp scripts/validate_migrations.sh ./publish/scripts/validate_migrations.sh
|
|
chmod +x ./publish/deploy_gb.sh ./publish/scripts/validate_migrations.sh
|
|
echo "✓ Deployment scripts copied"
|
|
|
|
- name: Package Artifact
|
|
run: |
|
|
echo "📦 Creating deployment package..."
|
|
|
|
if ! tar -czf quantengine.tar.gz -C ./publish .; then
|
|
echo "❌ Failed to create package"
|
|
exit 1
|
|
fi
|
|
|
|
PACKAGE_SIZE=$(du -sh quantengine.tar.gz | cut -f1)
|
|
PACKAGE_BYTES=$(stat -c%s quantengine.tar.gz 2>/dev/null || echo "0")
|
|
|
|
if [ -z "$PACKAGE_BYTES" ] || [ "$PACKAGE_BYTES" -lt 1000000 ]; then
|
|
echo "⚠️ Warning: Package seems too small ($PACKAGE_SIZE)"
|
|
fi
|
|
|
|
if [ ! -f quantengine.tar.gz ]; then
|
|
echo "❌ Package file not created"
|
|
exit 1
|
|
fi
|
|
|
|
echo "✓ Package created: $PACKAGE_SIZE"
|
|
tar -tzf quantengine.tar.gz | head -n 5 || true
|
|
|
|
- name: Pre-Deployment Migration Validation
|
|
run: |
|
|
echo "=== Pre-Deployment Database Check ==="
|
|
|
|
# 배포 패키지 임시 추출 (검증용)
|
|
TEMP_DEPLOY="/tmp/quantengine_validate"
|
|
mkdir -p "$TEMP_DEPLOY"
|
|
tar -xzf quantengine.tar.gz -C "$TEMP_DEPLOY"
|
|
|
|
# 마이그레이션 검증 실행
|
|
chmod +x "$TEMP_DEPLOY/scripts/validate_migrations.sh"
|
|
"$TEMP_DEPLOY/scripts/validate_migrations.sh" "$TEMP_DEPLOY"
|
|
|
|
# 정리
|
|
rm -rf "$TEMP_DEPLOY"
|
|
|
|
- name: Local Deploy (Green-Blue)
|
|
id: deploy
|
|
run: |
|
|
set -e
|
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
COMMIT=$(git rev-parse --short HEAD)
|
|
RUN_NUM="${{ github.run_number }}"
|
|
DEPLOY_BASE="/home/kjh2064/deployments"
|
|
ACTIVE_LINK="/home/kjh2064/quantengine_active"
|
|
# Version format: quantengine_YYYYMMDD_HHMMSS_COMMIT_HASH_RUNNUM
|
|
TARGET_DIR="${DEPLOY_BASE}/quantengine_${TIMESTAMP}_${COMMIT}_${RUN_NUM}"
|
|
|
|
TELEGRAM_BOT_TOKEN="${{ secrets.TELEGRAM_BOT_TOKEN }}"
|
|
[ -z "$TELEGRAM_BOT_TOKEN" ] && TELEGRAM_BOT_TOKEN="${{ env.TELEGRAM_BOT_TOKEN_DEFAULT }}"
|
|
TELEGRAM_CHAT_ID="${{ secrets.TELEGRAM_CHAT_ID }}"
|
|
[ -z "$TELEGRAM_CHAT_ID" ] && TELEGRAM_CHAT_ID="${{ env.TELEGRAM_CHAT_ID_DEFAULT }}"
|
|
|
|
send_telegram() {
|
|
local text="$1"
|
|
curl -fsS -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
|
-d "chat_id=${TELEGRAM_CHAT_ID}" \
|
|
--data-urlencode "text=${text}" \
|
|
-d "parse_mode=HTML" >/dev/null || true
|
|
}
|
|
|
|
echo "=== Deploying QuantEngine $COMMIT ($TIMESTAMP) ==="
|
|
|
|
# 배포 디렉토리 생성
|
|
mkdir -p "${DEPLOY_BASE}"
|
|
mkdir -p "${TARGET_DIR}"
|
|
|
|
# 배포 패키지 추출
|
|
echo "📁 Extracting build artifact..."
|
|
tar -xzf quantengine.tar.gz -C "${TARGET_DIR}"
|
|
rm -f quantengine.tar.gz
|
|
|
|
# 환경 파일 설치
|
|
echo "⚙️ Installing environment configuration..."
|
|
mkdir -p /home/kjh2064/.config
|
|
install -m 600 ./deploy/quantengine.env /home/kjh2064/.config/quantengine.env
|
|
|
|
# Green-Blue 배포 실행
|
|
echo "🚀 Executing Green-Blue Deployment..."
|
|
export DEPLOY_FROM_CI=1
|
|
chmod +x "${TARGET_DIR}/deploy_gb.sh"
|
|
"${TARGET_DIR}/deploy_gb.sh"
|
|
|
|
# 이전 버전 정보 저장
|
|
if [ -L "${ACTIVE_LINK}" ]; then
|
|
PREV_VERSION=$(readlink -f "${ACTIVE_LINK}")
|
|
PREV_TIMESTAMP=$(basename "${PREV_VERSION}")
|
|
else
|
|
PREV_TIMESTAMP="none"
|
|
fi
|
|
|
|
echo "timestamp=${TIMESTAMP}" >> $GITHUB_OUTPUT
|
|
echo "commit=${COMMIT}" >> $GITHUB_OUTPUT
|
|
echo "prev_version=${PREV_TIMESTAMP}" >> $GITHUB_OUTPUT
|
|
|
|
- name: Health Check & Auto-Rollback
|
|
run: |
|
|
TIMESTAMP="${{ steps.deploy.outputs.timestamp }}"
|
|
COMMIT="${{ steps.deploy.outputs.commit }}"
|
|
PREV_TIMESTAMP="${{ steps.deploy.outputs.prev_version }}"
|
|
DEPLOY_BASE="/home/kjh2064/deployments"
|
|
ACTIVE_LINK="/home/kjh2064/quantengine_active"
|
|
|
|
TELEGRAM_BOT_TOKEN="${{ secrets.TELEGRAM_BOT_TOKEN }}"
|
|
[ -z "$TELEGRAM_BOT_TOKEN" ] && TELEGRAM_BOT_TOKEN="${{ env.TELEGRAM_BOT_TOKEN_DEFAULT }}"
|
|
TELEGRAM_CHAT_ID="${{ secrets.TELEGRAM_CHAT_ID }}"
|
|
[ -z "$TELEGRAM_CHAT_ID" ] && TELEGRAM_CHAT_ID="${{ env.TELEGRAM_CHAT_ID_DEFAULT }}"
|
|
|
|
send_telegram() {
|
|
local text="$1"
|
|
curl -fsS -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
|
-d "chat_id=${TELEGRAM_CHAT_ID}" \
|
|
--data-urlencode "text=${text}" \
|
|
-d "parse_mode=HTML" >/dev/null || true
|
|
}
|
|
|
|
echo "=== Verifying Loopback Health ==="
|
|
health_check_passed=0
|
|
|
|
for i in 1 2 3; do
|
|
echo " Health check attempt $i..."
|
|
loopback_headers=$(curl -s -D - -o /dev/null -m 5 http://127.0.0.1:5000/ 2>&1)
|
|
|
|
if printf '%s' "$loopback_headers" | grep -qE '^HTTP/1\.[01] (200|30[12]|401) '; then
|
|
echo "✓ Loopback health check passed (auth required)"
|
|
health_check_passed=1
|
|
break
|
|
elif [ $i -lt 3 ]; then
|
|
echo " Waiting 5s for service..."
|
|
sleep 5
|
|
fi
|
|
done
|
|
|
|
if [ $health_check_passed -eq 0 ]; then
|
|
echo "❌ Loopback health check failed after 3 attempts"
|
|
|
|
# 자동 롤백
|
|
if [ "$PREV_TIMESTAMP" != "none" ]; then
|
|
echo "🔄 Attempting automatic rollback to $PREV_TIMESTAMP..."
|
|
PREV_DEPLOY="${DEPLOY_BASE}/quantengine_${PREV_TIMESTAMP}"
|
|
ln -sfn "${PREV_DEPLOY}" "${ACTIVE_LINK}"
|
|
sudo systemctl restart quantengine
|
|
sleep 3
|
|
echo "✓ Rollback completed"
|
|
send_telegram "❌ <b>QuantEngine 배포 실패 (자동 롤백 실행)</b>
|
|
|
|
커밋: <code>${COMMIT}</code>
|
|
롤백 버전: <code>${PREV_TIMESTAMP}</code>
|
|
로그: https://gitea.taxbaik.com/kjh2064/QuantEngineByItz/actions/runs/${{ github.run_id }}"
|
|
else
|
|
echo "⚠️ No previous deployment found for rollback"
|
|
send_telegram "❌ <b>QuantEngine 배포 실패 (롤백 불가)</b>
|
|
|
|
커밋: <code>${COMMIT}</code>
|
|
상태: 이전 버전이 없어 롤백 불가
|
|
로그: https://gitea.taxbaik.com/kjh2064/QuantEngineByItz/actions/runs/${{ github.run_id }}"
|
|
fi
|
|
exit 1
|
|
fi
|
|
|
|
echo "=== Verifying Database Connectivity ==="
|
|
db_status=$(psql -U quantengine_app -d quantenginedb -h 127.0.0.1 -c 'SELECT 1;' 2>&1 | head -1)
|
|
|
|
if echo "$db_status" | grep -q "1"; then
|
|
echo "✓ Database connectivity verified"
|
|
else
|
|
echo "⚠️ Database connectivity check: $db_status"
|
|
fi
|
|
|
|
echo "=== Verifying Public Routes ==="
|
|
public_root_code=$(curl -s -o /dev/null -w "%{http_code}" "https://quant.taxbaik.com/")
|
|
login_code=$(curl -s -o /dev/null -w "%{http_code}" "https://quant.taxbaik.com/Account/Login")
|
|
|
|
echo "https://quant.taxbaik.com/ -> ${public_root_code}"
|
|
echo "https://quant.taxbaik.com/Account/Login -> ${login_code}"
|
|
|
|
if [ "$public_root_code" != "302" ] && [ "$public_root_code" != "200" ] && [ "$public_root_code" != "401" ]; then
|
|
echo "⚠️ Unexpected public root response: $public_root_code"
|
|
fi
|
|
if [ "$login_code" != "200" ] && [ "$login_code" != "302" ]; then
|
|
echo "⚠️ Unexpected login page response: $login_code"
|
|
fi
|
|
|
|
echo "=== Verifying Nginx Configuration ==="
|
|
NGINX_CONF=""
|
|
for f in /etc/nginx/sites-enabled/*; do
|
|
if [ -e "$f" ] && grep -q "location /quantengine" "$f" 2>/dev/null; then
|
|
NGINX_CONF="$f"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ -n "$NGINX_CONF" ]; then
|
|
echo "✓ Nginx configuration found: $NGINX_CONF"
|
|
if nginx -t > /dev/null 2>&1; then
|
|
echo "✓ Nginx syntax validated"
|
|
else
|
|
echo "⚠️ Nginx syntax check failed (service may still work)"
|
|
fi
|
|
else
|
|
echo "⚠️ Nginx configuration not found"
|
|
echo " Expected: /etc/nginx/sites-enabled/* with 'location /quantengine'"
|
|
fi
|
|
|
|
echo "✓ 배포 완료: quantengine_${TIMESTAMP}"
|
|
send_telegram "✅ <b>QuantEngine 배포 완료 (Green-Blue)</b>
|
|
|
|
커밋: <code>${COMMIT}</code>
|
|
시간: <code>${TIMESTAMP}</code>
|
|
대상: <code>${DEPLOY_HOST}</code>"
|
|
|
|
- name: Cleanup Old Deployments
|
|
run: |
|
|
DEPLOY_BASE="/home/kjh2064/deployments"
|
|
echo "Cleaning up obsolete deployments (keeping last 5)..."
|
|
cd "${DEPLOY_BASE}"
|
|
ls -dt quantengine_* | tail -n +6 | while read -r old_dir; do
|
|
echo "Removing old release: ${old_dir}"
|
|
rm -rf "${old_dir}"
|
|
done
|
|
echo "Cleanup complete"
|
|
ls -ldt quantengine_* | head -5
|
|
|
|
- name: Notify Failure
|
|
if: failure()
|
|
run: |
|
|
COMMIT=$(git rev-parse --short HEAD)
|
|
TELEGRAM_BOT_TOKEN="${{ secrets.TELEGRAM_BOT_TOKEN }}"
|
|
[ -z "$TELEGRAM_BOT_TOKEN" ] && TELEGRAM_BOT_TOKEN="${{ env.TELEGRAM_BOT_TOKEN_DEFAULT }}"
|
|
TELEGRAM_CHAT_ID="${{ secrets.TELEGRAM_CHAT_ID }}"
|
|
[ -z "$TELEGRAM_CHAT_ID" ] && TELEGRAM_CHAT_ID="${{ env.TELEGRAM_CHAT_ID_DEFAULT }}"
|
|
|
|
curl -fsS -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
|
-d "chat_id=${TELEGRAM_CHAT_ID}" \
|
|
--data-urlencode "text=❌ QuantEngine 배포 실패\n커밋: ${COMMIT}\n로그: https://gitea.taxbaik.com/kjh2064/QuantEngineByItz/actions/runs/${{ github.run_id }}" \
|
|
-d "parse_mode=HTML" || true
|