diff --git a/.gitea/workflows/deploy-prod.yml b/.gitea/workflows/deploy-prod.yml index fcf9671..9aecde9 100644 --- a/.gitea/workflows/deploy-prod.yml +++ b/.gitea/workflows/deploy-prod.yml @@ -7,18 +7,16 @@ on: env: DEPLOY_HOST: 172.17.0.1 - # NOTE: Gitea와 운영서버가 같은 호스트에 있음 (hz-prod-01) - # 구조: 공인 IP 178.104.200.7/quant → Nginx reverse proxy → localhost:5000 (quantengine) - # 배포: .NET DLL을 /home/kjh2064/quantengine_active에 배포 - # Nginx 설정: /etc/nginx/sites-available/gitea-ip.conf (이미 구성됨) DEPLOY_USER: kjh2064 DEPLOY_PATH: /home/kjh2064/quantengine_active SERVICE_NAME: quantengine DOTNET_VERSION: '10.0.x' + TELEGRAM_BOT_TOKEN_DEFAULT: "8734507814:AAFyacLMai8GB4K-hQ_Nd3t3D01A-h1ZdV0" + TELEGRAM_CHAT_ID_DEFAULT: "-5460205872" jobs: - build-and-test: - name: Build Release Package + build-and-deploy: + name: Build & Deploy to Production runs-on: ubuntu-latest steps: @@ -42,12 +40,19 @@ jobs: - name: "[GATE] Run Core Validations" run: | - # CI 게이트: 핵심 검증 먼저 실행 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 + # 빈 패킷 객체를 생성하여 dotnet test/run 시 IO Exception 방어 + 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 @@ -64,7 +69,6 @@ jobs: dotnet test tests/unit \ -c Release \ --no-build \ - --logger "trx;LogFileName=test-results.trx" \ || echo "⚠️ Some tests failed (non-blocking for web service)" fi @@ -75,329 +79,107 @@ jobs: --no-build \ -o ./publish-output - echo "📦 Package size:" - du -sh ./publish-output - - - name: Create Deployment Archive - run: | - cd publish-output - tar -czf ../quant-engine-release-${{ github.run_number }}.tar.gz . - cd .. - ls -lh quant-engine-release-${{ github.run_number }}.tar.gz - - - name: Upload Artifact - uses: actions/upload-artifact@v3 - with: - name: quant-engine-release - path: quant-engine-release-${{ github.run_number }}.tar.gz - retention-days: 30 - - deploy-to-prod: - name: Deploy to Production Server - needs: build-and-test - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' && github.event_name == 'push' - - steps: - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Download Artifact - uses: actions/download-artifact@v3 - with: - name: quant-engine-release - - name: Setup SSH run: | mkdir -p ~/.ssh chmod 700 ~/.ssh - echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 + # SSH_PRIVATE_KEY가 평문 PEM이든 base64든 유연하게 처리 + if echo "${{ secrets.SSH_PRIVATE_KEY }}" | grep -q "BEGIN"; then + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 + else + echo "${{ secrets.SSH_PRIVATE_KEY }}" | base64 -d > ~/.ssh/id_ed25519 || echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 + fi chmod 600 ~/.ssh/id_ed25519 ssh-keyscan -H ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true - - name: Create Backup + - name: Package Artifact run: | - echo "📦 Creating backup of current deployment..." - ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} << 'EOF' - set -e - BACKUP_DIR="/home/kjh2064/quantengine_backup" - BACKUP_NAME="quantengine_$(date +%Y%m%d_%H%M%S)" + tar -czf quant_engine_deploy.tgz -C ./publish-output . + echo "✓ Package size: $(du -sh quant_engine_deploy.tgz | cut -f1)" - # Create backup without stopping the service (minimize downtime) - mkdir -p $BACKUP_DIR - if [ -d ${{ env.DEPLOY_PATH }} ]; then - cp -r ${{ env.DEPLOY_PATH }} "$BACKUP_DIR/$BACKUP_NAME" - echo "✅ Backup created: $BACKUP_DIR/$BACKUP_NAME" + - name: Deploy & Verify on Server + run: | + set -e + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + COMMIT=$(git rev-parse --short HEAD) + DEPLOY_HOST="${{ env.DEPLOY_HOST }}" + DEPLOY_USER="${{ env.DEPLOY_USER }}" + + # 텔레그램 설정 바인딩 (Secret에 없을 경우 기본값 백업 사용) + 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 }}" - # Keep only last 5 backups - BACKUP_COUNT=$(ls -1 $BACKUP_DIR | wc -l) - if [ "$BACKUP_COUNT" -gt 5 ]; then - OLD_BACKUPS=$(ls -1t $BACKUP_DIR | tail -n +6) - for backup in $OLD_BACKUPS; do - rm -rf "$BACKUP_DIR/$backup" - done - echo "🧹 Old backups cleaned" - fi - else - echo "⚠️ No existing deployment found" + 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 + } + + notify_failure() { + local exit_code=$? + send_telegram "❌ QuantEngine 배포 실패 + + 커밋: ${COMMIT} + 시간: ${TIMESTAMP} + 단계: deploy-to-prod (SSH Execution)" + exit "$exit_code" + } + + trap notify_failure ERR + + echo "=== Deploying QuantEngine $COMMIT ($TIMESTAMP) ===" + + # 1. 아티팩트 복사 + scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \ + quant_engine_deploy.tgz "$DEPLOY_USER@$DEPLOY_HOST:/tmp/quantengine_${TIMESTAMP}.tgz" + + # 2. 원격 배포 명령어 통합 (SSH 1회 연결) + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 \ + -o ServerAliveInterval=10 \ + "$DEPLOY_USER@$DEPLOY_HOST" bash << REMOTE + set -e + DEPLOY_HOME="/home/kjh2064" + DEPLOY_DIR="\$DEPLOY_HOME/deployments/quantengine_${TIMESTAMP}" + + echo "--- [1/4] 압축 해제 ---" + mkdir -p "\$DEPLOY_DIR" + tar -xzf "/tmp/quantengine_${TIMESTAMP}.tgz" -C "\$DEPLOY_DIR" + rm -f "/tmp/quantengine_${TIMESTAMP}.tgz" + + echo "--- [2/4] 심볼릭 링크 전환 ---" + ln -sfn "\$DEPLOY_DIR" "${{ env.DEPLOY_PATH }}" + + echo "--- [3/4] 서비스 재시작 ---" + sudo /usr/bin/systemctl restart ${{ env.SERVICE_NAME }} + + echo "--- [4/4] 헬스 체크 ---" + ATTEMPTS=20 + for i in \\\$(seq 1 \\\$ATTEMPTS); do + STATUS=\\\$(curl -sf -o /dev/null -w '%{http_code}' http://127.0.0.1:5000/ 2>/dev/null || echo "000") + if [ "\\\$STATUS" = "200" ]; then + echo "✓ 헬스체크 성공 (시도 \\\$i/\\\$ATTEMPTS, HTTP 200)" + # 구 배포 폴더 정리 (최근 5개만 보존) + ls -1dt \$DEPLOY_HOME/deployments/quantengine_* 2>/dev/null | tail -n +6 | xargs rm -rf 2>/dev/null || true + exit 0 fi - EOF - - - name: Deploy Package - run: | - echo "📤 Deploying package to production..." - - ARCHIVE_NAME=$(ls -1 quant-engine-release-*.tar.gz | head -1) - - # Create temporary directory on remote - ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} \ - "mkdir -p /tmp/quant-deploy && chmod 777 /tmp/quant-deploy" - - # Transfer archive - scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 "$ARCHIVE_NAME" \ - ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }}:/tmp/quant-deploy/ - - echo "✅ Package transferred" - - - name: Extract and Install - run: | - echo "📦 Extracting and installing..." - ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} << 'EOF' - set -e - - DEPLOY_PATH="${{ env.DEPLOY_PATH }}" - ARCHIVE_NAME=$(ls -1 /tmp/quant-deploy/quant-engine-release-*.tar.gz | head -1) - - # Create deployment directory - mkdir -p "$DEPLOY_PATH" - - # Extract new package - tar -xzf "$ARCHIVE_NAME" -C "$DEPLOY_PATH" - echo "✅ Package extracted to $DEPLOY_PATH" - - # Verify key files - if [ -f "$DEPLOY_PATH/QuantEngine.Web.dll" ]; then - echo "✅ QuantEngine.Web.dll verified" - else - echo "❌ QuantEngine.Web.dll not found!" + if [ "\\\$i" -eq "\\\$ATTEMPTS" ]; then + echo "=== FATAL: 서비스가 헬스체크 응답을 하지 않음 ===" >&2 + systemctl is-active ${{ env.SERVICE_NAME }} >&2 || true + journalctl -u ${{ env.SERVICE_NAME }} --no-pager -n 50 >&2 exit 1 fi - - # Cleanup temp - rm -rf /tmp/quant-deploy - EOF - - - name: Restart Service - run: | - echo "🔄 Restarting quantengine service to apply changes (Downtime minimal)..." - ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} << 'EOF' - set -e - - # Restart service - echo "⏹️ Restarting quantengine service..." - sudo systemctl restart ${{ env.SERVICE_NAME }} + echo " 대기 중... (\\\$i/\\\$ATTEMPTS, HTTP \\\$STATUS)" sleep 3 - - # Check status - if sudo systemctl is-active --quiet ${{ env.SERVICE_NAME }}; then - echo "✅ ${{ env.SERVICE_NAME }} restarted successfully" - sudo systemctl status ${{ env.SERVICE_NAME }} | head -5 - else - echo "❌ ${{ env.SERVICE_NAME }} failed to start" - sudo systemctl status ${{ env.SERVICE_NAME }} - exit 1 - fi - EOF - - - name: Health Check - run: | - echo "🧪 Running health checks on remote host..." - - # Wait for service to be ready (localhost:5000/quant/ through Kestrel inside remote host) - for i in {1..30}; do - HTTP_CODE=$(ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} \ - 'curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:5000/quant/' || echo "000") - - if [ "$HTTP_CODE" = "200" ]; then - echo "✅ Health check passed (HTTP $HTTP_CODE inside remote host)" - break - fi - - echo "⏳ Waiting for service... (attempt $i/30, HTTP $HTTP_CODE)" - sleep 2 done + REMOTE - if [ "$HTTP_CODE" != "200" ]; then - echo "❌ Health check failed after 60 seconds" - echo "Service logs:" - ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} \ - "sudo journalctl -u ${{ env.SERVICE_NAME }} -n 20" || true - exit 1 - fi - - - name: Verify Deployment - run: | - echo "📊 Verifying deployment..." - - # Check MudBlazor is loaded (via public IP) - PUBLIC_IP="178.104.200.7" - MUDBLAZOR_CHECK=$(curl -s "http://$PUBLIC_IP/quant/" | grep -c "MudBlazor" || echo "0") - - if [ "$MUDBLAZOR_CHECK" -gt "0" ]; then - echo "✅ MudBlazor UI loaded successfully" - else - echo "⚠️ MudBlazor might not be loaded correctly" - fi - - # Get page title - PAGE_TITLE=$(curl -s "http://$PUBLIC_IP/quant/" | grep -o ".*" | head -1) - echo "📄 Page title: $PAGE_TITLE" - - - name: Generate Deployment Report - if: always() - run: | - cat > deployment-report.txt << EOF - ═══════════════════════════════════════════════════════ - Quant Engine v9 Deployment Report - ═══════════════════════════════════════════════════════ - - Deployment Date: $(date -u '+%Y-%m-%d %H:%M:%S UTC') - Run Number: ${{ github.run_number }} - Commit: ${{ github.sha }} - Branch: ${{ github.ref }} - - 🎯 Target Environment - Server: hz-prod-01 - Internal IP: ${{ env.DEPLOY_HOST }} - Public IP: 178.104.200.7 - Deploy Path: ${{ env.DEPLOY_PATH }} - Service: ${{ env.SERVICE_NAME }} - - 📊 Deployment Status: COMPLETED - - ✅ Release Build: Successful - ✅ Package Created: 24MB+ - ✅ Backup Created: /home/kjh2064/quantengine_backup/ - ✅ Package Deployed: ${{ env.DEPLOY_PATH }} - ✅ Service Started: ${{ env.SERVICE_NAME }} - ✅ Health Check: PASS (localhost:5000) - ✅ MudBlazor UI: Verified via public IP - - 🌐 Access Information - Public URL: http://178.104.200.7/quant/ - Service Port: 127.0.0.1:5000 - Nginx Config: /etc/nginx/sites-available/gitea-ip.conf - - 📝 Service Architecture - - Nginx (reverse proxy) listens on port 80/443 - - /quant/ path → localhost:5000 (quantengine service) - - quantengine runs as user kjh2064 - - WorkingDirectory: /home/kjh2064/quantengine_active - - 🔍 Monitoring & Logs - - Service: sudo systemctl status ${{ env.SERVICE_NAME }} - - Logs: sudo journalctl -u ${{ env.SERVICE_NAME }} -f - - Nginx: sudo tail -f /var/log/nginx/error.log - - Deployment Log: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - - 🔄 Rollback Command (if needed): - ssh kjh2064@${{ env.DEPLOY_HOST }} 'LATEST=\$(ls -t /home/kjh2064/quantengine_backup | head -1); cp -r /home/kjh2064/quantengine_backup/\$LATEST/* /home/kjh2064/quantengine_active/ && sudo systemctl restart ${{ env.SERVICE_NAME }}' - - ═══════════════════════════════════════════════════════ - EOF - cat deployment-report.txt - - - name: Upload Deployment Report - uses: actions/upload-artifact@v3 - if: always() - with: - name: deployment-report - path: deployment-report.txt - retention-days: 90 - - - name: Notify Telegram - if: always() - run: | - STATUS=${{ job.status }} - if [ "$STATUS" = "success" ]; then - EMOJI="✅" - TEXT="*Quant Engine v9 Deployment SUCCESS* $EMOJI%0A• Run: #${{ github.run_number }}%0A• Commit: ${{ github.sha }}%0A• Service: ${{ env.SERVICE_NAME }}%0A• URL: http://178.104.200.7/quant/" - else - EMOJI="❌" - TEXT="*Quant Engine v9 Deployment FAILED* $EMOJI%0A• Run: #${{ github.run_number }}%0A• Commit: ${{ github.sha }}%0A• Service: ${{ env.SERVICE_NAME }}%0A• URL: http://178.104.200.7/quant/" - fi - - curl -s -X POST "https://api.telegram.org/bot8734507814:AAFyacLMai8GB4K-hQ_Nd3t3D01A-h1ZdV0/sendMessage" \ - -d "chat_id=-5460205872" \ - -d "text=$TEXT" \ - -d "parse_mode=Markdown" - - post-deployment: - name: Post-Deployment Checks - needs: deploy-to-prod - runs-on: ubuntu-latest - if: success() - - steps: - - name: Performance Baseline - run: | - echo "📈 Collecting performance metrics via Public IP..." - - # Page load time - START=$(date +%s%N) - curl -s http://178.104.200.7/quant/ > /dev/null - END=$(date +%s%N) - LOAD_TIME=$(( (END - START) / 1000000 )) - - echo "⏱️ Page load time: ${LOAD_TIME}ms" - - if [ $LOAD_TIME -lt 2000 ]; then - echo "✅ Load time acceptable (< 2s)" - else - echo "⚠️ Load time slightly slow (> 2s), but acceptable" - fi - - - name: Create Deployment Checklist - run: | - cat > deployment-checklist.txt << 'EOF' - ✅ Quant Engine v9 Deployment Complete - - Web Service: - [✓] Release build successful (24MB) - [✓] Deployed to: http://178.104.200.7/quant/ - [✓] nginx restarted - [✓] Health check: HTTP 200 OK - [✓] MudBlazor UI verified - [✓] Page load time: < 2s - - Backup & Recovery: - [✓] Backup created: /var/www/quant_backup/ - [✓] 5 previous backups retained - [✓] Rollback ready - - Next Steps: - [ ] Monitor nginx logs: ssh kjh2064@178.104.200.7 'sudo tail -f /var/log/nginx/error.log' - [ ] Check dashboard: http://178.104.200.7/quant/ - [ ] Verify all components loaded - [ ] Test responsive design (mobile/tablet) - [ ] Monitor performance metrics - - GAS Deployment (Manual): - [ ] Deploy gas_data_feed.gs to Google Apps Script - [ ] Deploy live_outcome_ledger.gs - [ ] Test signal tracking - - Documentation: - [ ] DEPLOYMENT_GUIDE.md - [ ] DEPLOYMENT_STEPS.md - [ ] UI_COMPLETENESS_REPORT.md - [ ] V9_HARDENING_IMPLEMENTATION_ROADMAP.md - EOF - cat deployment-checklist.txt - - - name: Upload Checklist - uses: actions/upload-artifact@v3 - with: - name: post-deployment-checklist - path: deployment-checklist.txt - retention-days: 30 + echo "✓ 배포 완료: quantengine_${TIMESTAMP} @ $DEPLOY_HOST" + send_telegram "✅ QuantEngine 배포 완료 + + 커밋: ${COMMIT} + 시간: ${TIMESTAMP} + 대상: ${DEPLOY_HOST}" diff --git a/.gitea/workflows/snapshot_admin_deploy.yml b/.gitea/workflows/snapshot_admin_deploy.yml index 98dfa0d..b6cc790 100644 --- a/.gitea/workflows/snapshot_admin_deploy.yml +++ b/.gitea/workflows/snapshot_admin_deploy.yml @@ -10,6 +10,12 @@ concurrency: group: snapshot-admin-deploy-main cancel-in-progress: true +env: + DEPLOY_HOST: 178.104.200.7 + DEPLOY_USER: kjh2064 + TELEGRAM_BOT_TOKEN_DEFAULT: "8734507814:AAFyacLMai8GB4K-hQ_Nd3t3D01A-h1ZdV0" + TELEGRAM_CHAT_ID_DEFAULT: "-5460205872" + jobs: build-and-deploy: runs-on: ubuntu-latest @@ -33,38 +39,84 @@ jobs: echo "[deploy] compressing publish output" tar -czf quantengine.tar.gz -C ./publish . - - name: Deploy to Host via Local SSH - env: - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + - name: Setup SSH run: | - echo "[deploy] setting up SSH and deploying shadow copy" mkdir -p ~/.ssh - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519 + chmod 700 ~/.ssh + if echo "${{ secrets.SSH_PRIVATE_KEY }}" | grep -q "BEGIN"; then + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 + else + echo "${{ secrets.SSH_PRIVATE_KEY }}" | base64 -d > ~/.ssh/id_ed25519 || echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 + fi chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan -H ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true - # Upload artifact and deploy script to host - ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 kjh2064@178.104.200.7 "mkdir -p /home/kjh2064/tmp" - scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 quantengine.tar.gz kjh2064@178.104.200.7:/home/kjh2064/tmp/quantengine.tar.gz - - # Execute hot deploy script - ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 kjh2064@178.104.200.7 "chmod +x /home/kjh2064/tmp/deploy.sh 2>/dev/null || true" - scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 tools/deploy_quantengine.sh kjh2064@178.104.200.7:/home/kjh2064/tmp/deploy.sh - ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 kjh2064@178.104.200.7 "chmod +x /home/kjh2064/tmp/deploy.sh && /home/kjh2064/tmp/deploy.sh" - - - name: Verify Public Routes + - name: Deploy & Verify on Server run: | set -e - root_html=$(curl -s "http://178.104.200.7/quant/") - ops_html=$(curl -s "http://178.104.200.7/quant/operations") + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + COMMIT=$(git rev-parse --short HEAD) + DEPLOY_HOST="${{ env.DEPLOY_HOST }}" + DEPLOY_USER="${{ env.DEPLOY_USER }}" + + 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 + } + + notify_failure() { + local exit_code=$? + send_telegram "❌ Snapshot Admin 배포 실패 + + 커밋: ${COMMIT} + 시간: ${TIMESTAMP} + 단계: snapshot_admin_deploy (Deploy Execution)" + exit "$exit_code" + } + + trap notify_failure ERR + + echo "=== Deploying Snapshot Admin $COMMIT ($TIMESTAMP) ===" + + # 1. 원격지 임시 폴더 생성 및 업로드 + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 "$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p /home/kjh2064/tmp" + scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 quantengine.tar.gz "$DEPLOY_USER@$DEPLOY_HOST:/home/kjh2064/tmp/quantengine.tar.gz" + scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 tools/deploy_quantengine.sh "$DEPLOY_USER@$DEPLOY_HOST:/home/kjh2064/tmp/deploy.sh" + + # 2. 배포 스크립트 실행 + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 "$DEPLOY_USER@$DEPLOY_HOST" "chmod +x /home/kjh2064/tmp/deploy.sh && /home/kjh2064/tmp/deploy.sh" + + # 3. 배포 성공 검증 + echo "=== Verifying Public Routes ===" + root_html=$(curl -sf "http://${DEPLOY_HOST}/quant/" 2>/dev/null || echo "") + ops_html=$(curl -sf "http://${DEPLOY_HOST}/quant/operations" 2>/dev/null || echo "") + root_code=$(printf '%s' "$root_html" | grep -q "Quant Engine" && echo 200 || echo 500) ops_code=$(printf '%s' "$ops_html" | grep -q "Operational Report" && echo 200 || echo 500) + echo "/quant/ -> ${root_code}" echo "/quant/operations -> ${ops_code}" + if [ "$root_code" != "200" ]; then - echo "Deployment content check failed for /quant/" + echo "Deployment content check failed for /quant/" >&2 exit 1 fi if [ "$ops_code" != "200" ]; then - echo "Deployment content check failed for /quant/operations" + echo "Deployment content check failed for /quant/operations" >&2 exit 1 fi + + echo "✓ 배포 완료: quantengine_${TIMESTAMP} @ $DEPLOY_HOST" + send_telegram "✅ Snapshot Admin 배포 완료 + + 커밋: ${COMMIT} + 시간: ${TIMESTAMP} + 대상: ${DEPLOY_HOST}"