diff --git a/.gitea/workflows/deploy-prod.yml b/.gitea/workflows/deploy-prod.yml index ab24e676..f66302d0 100644 --- a/.gitea/workflows/deploy-prod.yml +++ b/.gitea/workflows/deploy-prod.yml @@ -5,640 +5,56 @@ on: branches: [ main ] workflow_dispatch: -# Phase 4: Manual-only deployment (improved & hardened) -# Automatic deployment moved to merge-to-main.yml (Stage 5) -# Use this workflow for manual deployments when needed -# -# Error handling: Comprehensive logging + automatic rollback -# Security: SSH key validation, deployment verification -# Observability: Detailed stage reporting + Telegram notifications - concurrency: group: deploy-prod-main cancel-in-progress: false -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" - DEPLOY_TIMEOUT: "600" - HEALTH_CHECK_RETRIES: "5" - HEALTH_CHECK_DELAY: "3" - jobs: - build-and-deploy: - name: Build & Deploy to Production + deploy: + name: Deploy runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 20 steps: - - name: Checkout Code + - name: Checkout 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 + dotnet-version: '10.0.x' - name: Build Release run: | - dotnet build src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj \ - -c Release \ - --no-restore + dotnet restore src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj + dotnet build src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj -c Release --no-restore - - name: Run Unit Tests + - name: Publish + run: dotnet publish src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj -c Release -o ./publish + + - name: Package run: | - dotnet test src/dotnet/QuantEngine.Core.Tests/QuantEngine.Core.Tests.csproj \ - -c Release \ - --no-build + COMMIT=$(git rev-parse --short HEAD) + tar -czf quantengine-${COMMIT}.tar.gz -C ./publish . + echo "ARTIFACT=quantengine-${COMMIT}.tar.gz" >> $GITHUB_ENV - - name: Publish Release Package + - name: Upload Artifact + uses: actions/upload-artifact@v3 + with: + name: quantengine-${{ github.run_number }} + path: quantengine-*.tar.gz + retention-days: 7 + + - name: Verify Secrets 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 secret not configured in Gitea" - echo " Please set secret in Repository Settings > Secrets" - exit 1 - 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 < ./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: Pre-Deployment Verification - run: | - echo "=== PRE-DEPLOYMENT CHECKS ===" - - # 1. SSH - if [ ! -f ~/.ssh/id_rsa ]; then - echo "ERROR: SSH key not found" - exit 1 - fi - echo "OK: SSH key present" - - # 2. - if [ ! -f quantengine.tar.gz ]; then - echo "ERROR: Build artifact (quantengine.tar.gz) not found" - exit 1 - fi - ARTIFACT_SIZE=$(stat -c%s quantengine.tar.gz) - if [ "$ARTIFACT_SIZE" -lt 1000000 ]; then - echo "WARNING: Artifact seems small (${ARTIFACT_SIZE} bytes), but proceeding" - fi - echo "OK: Build artifact present (${ARTIFACT_SIZE} bytes)" - - # 3. - for file in deploy/quantengine.env deploy_gb.sh; do - if [ ! -f "$file" ]; then - echo "ERROR: Required file missing: $file" - exit 1 - fi - done - echo "OK: All required deployment files present" - - # 4. if [ -z "${{ secrets.QUANTENGINE_DB_PASSWORD }}" ]; then - echo "ERROR: DB password secret not configured" + echo "ERROR: DB password not configured" exit 1 fi - echo "OK: DB credentials configured" + echo "OK: Secrets verified" - echo "=== ALL PRE-DEPLOYMENT CHECKS PASSED ===" - - - name: Local Deploy (Green-Blue) - id: deploy + - name: Deploy to Production 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" - TARGET_DIR="${DEPLOY_BASE}/quantengine_${TIMESTAMP}_${COMMIT}_${RUN_NUM}" - DEPLOYMENT_LOG="./deployment_${TIMESTAMP}.log" - - 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 - } - - trap 'on_error' ERR - on_error() { - echo "DEPLOYMENT FAILED" | tee -a "$DEPLOYMENT_LOG" - send_telegram "DEPLOYMENT FAILED: $COMMIT at $(date)" - exit 1 - } - - { - echo "=== DEPLOYMENT START: $TIMESTAMP ===" - echo "Commit: $COMMIT" - echo "Run: $RUN_NUM" - echo "Target: $TARGET_DIR" - echo "" - - # - echo "[1/8] Creating deployment directories..." - mkdir -p "${DEPLOY_BASE}" || { echo "FATAL: Cannot create deploy base"; exit 1; } - mkdir -p "${TARGET_DIR}" || { echo "FATAL: Cannot create target dir"; exit 1; } - echo "OK: Directories created" - echo "" - - # - echo "[2/8] Extracting build artifact..." - if ! tar -xzf quantengine.tar.gz -C "${TARGET_DIR}"; then - echo "FATAL: Failed to extract artifact" - exit 1 - fi - echo "OK: Artifact extracted" - ls "${TARGET_DIR}" | head -10 - echo "" - - # - echo "[3/8] Normalizing deployment structure..." - if [ -d "${TARGET_DIR}/net10.0" ]; then - echo "Found net10.0 subdirectory, moving to root..." - if ! mv "${TARGET_DIR}/net10.0"/* "${TARGET_DIR}/"; then - echo "WARNING: Some files could not be moved from net10.0" - fi - if [ -d "${TARGET_DIR}/net10.0" ]; then - rmdir "${TARGET_DIR}/net10.0" 2>/dev/null || echo "Warning: Could not remove net10.0 dir" - fi - fi - echo "OK: Structure normalized" - echo "" - - # - echo "[4/8] Validating deployment contents..." - if [ ! -f "${TARGET_DIR}/QuantEngine.Web.dll" ]; then - echo "FATAL: QuantEngine.Web.dll not found in deployment" - exit 1 - fi - if [ ! -f "${TARGET_DIR}/appsettings.json" ]; then - echo "FATAL: appsettings.json not found" - exit 1 - fi - echo "OK: All required files present" - echo "" - - # - echo "[5/8] Installing environment configuration..." - mkdir -p /home/kjh2064/.config || { echo "WARNING: Cannot create config dir"; } - install -m 600 ./deploy/quantengine.env /home/kjh2064/.config/quantengine.env || { echo "WARNING: Config file install failed"; } - echo "OK: Configuration installed" - echo "" - - # appsettings.Production.json - echo "[6/8] Creating production appsettings..." - mkdir -p "${TARGET_DIR}" - DB_PASSWORD="${{ secrets.QUANTENGINE_DB_PASSWORD }}" - cat > "${TARGET_DIR}/appsettings.Production.json" << EOF -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "ConnectionStrings": { - "DefaultConnection": "Host=127.0.0.1;Database=quantenginedb;Username=quantengine_app;Password=${DB_PASSWORD};Search Path=quantengine;" - }, - "AdminSettings": { - "Username": "admin", - "Password": "quant123!" - } -} -EOF - chmod 600 "${TARGET_DIR}/appsettings.Production.json" - echo "OK: appsettings.Production.json created" - echo "" - - } | tee "$DEPLOYMENT_LOG" - - echo "timestamp=${TIMESTAMP}" >> $GITHUB_OUTPUT - echo "commit=${COMMIT}" >> $GITHUB_OUTPUT - echo "target_dir=${TARGET_DIR}" >> $GITHUB_OUTPUT - - # () - PREV_VERSION="none" - if [ -L "${ACTIVE_LINK}" ]; then - PREV_VERSION=$(readlink -f "${ACTIVE_LINK}") - PREV_TIMESTAMP=$(basename "${PREV_VERSION}") - else - PREV_TIMESTAMP="none" - fi - - echo "[7/8] Executing Green-Blue deployment..." - export DEPLOY_FROM_CI=1 - chmod +x "${TARGET_DIR}/deploy_gb.sh" - - if ! "${TARGET_DIR}/deploy_gb.sh" >> "$DEPLOYMENT_LOG" 2>&1; then - echo "DEPLOYMENT FAILED: Green-Blue swap error" - send_telegram "DEPLOYMENT FAILED: Green-Blue swap failed for $COMMIT" - exit 1 - fi - - echo "OK: Green-Blue deployment completed" - - # - cat > "${TARGET_DIR}/.deployment_info" << EOF -Deployed: $(date -u +'%Y-%m-%dT%H:%M:%SZ') -Commit: ${COMMIT} -Timestamp: ${TIMESTAMP} -Run: ${RUN_NUM} -Previous: ${PREV_TIMESTAMP} -Status: DEPLOYED -EOF - - echo "timestamp=${TIMESTAMP}" >> $GITHUB_OUTPUT - echo "commit=${COMMIT}" >> $GITHUB_OUTPUT - echo "target_dir=${TARGET_DIR}" >> $GITHUB_OUTPUT - echo "prev_version=${PREV_TIMESTAMP}" >> $GITHUB_OUTPUT - - - name: Health Check & Verification - id: health-check - run: | - TIMESTAMP="${{ steps.deploy.outputs.timestamp }}" - COMMIT="${{ steps.deploy.outputs.commit }}" - TARGET_DIR="${{ steps.deploy.outputs.target_dir }}" - 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 "=== POST-DEPLOYMENT HEALTH CHECKS ===" - - # 1. - echo "[1/4] Verifying deployment directory..." - if [ ! -d "$TARGET_DIR" ]; then - echo "FATAL: Deployment directory not found: $TARGET_DIR" - exit 1 - fi - if [ ! -f "${TARGET_DIR}/QuantEngine.Web.dll" ]; then - echo "FATAL: Application DLL not found in deployment" - exit 1 - fi - echo "OK: Deployment directory verified" - - # 2. Loopback - echo "[2/4] Performing loopback health checks..." - health_check_passed=0 - for i in $(seq 1 ${{ env.HEALTH_CHECK_RETRIES }}); do - echo " Attempt $i/${{ env.HEALTH_CHECK_RETRIES }}..." - if timeout 10 curl -s -f -o /dev/null -w '%{http_code}' http://127.0.0.1:5000/ 2>/dev/null | grep -qE '^(200|302|401)$'; then - echo " OK: Service responding" - health_check_passed=1 - break - fi - if [ $i -lt ${{ env.HEALTH_CHECK_RETRIES }} ]; then - sleep ${{ env.HEALTH_CHECK_DELAY }} - fi - done - - if [ $health_check_passed -eq 0 ]; then - echo "FAILED: Health check did not pass after ${{ env.HEALTH_CHECK_RETRIES }} attempts" - echo "status=failed" >> $GITHUB_OUTPUT - exit 1 - fi - echo "OK: Loopback health check passed" - - # 3. - echo "[3/4] Verifying database connectivity..." - if timeout 10 bash -c 'cat /home/kjh2064/.config/quantengine.env | grep -q "postgresql"' 2>/dev/null; then - echo "OK: Database credentials configured" - else - echo "WARNING: Could not verify database credentials" - fi - - # 4. - echo "[4/4] Checking service status..." - if systemctl is-active --quiet quantengine; then - echo "OK: Service is running" - else - echo "WARNING: Service may not be running, but health checks passed" - fi - - echo "status=success" >> $GITHUB_OUTPUT - echo "=== ALL HEALTH CHECKS PASSED ===" - send_telegram "OK: QuantEngine deployed successfully (commit: ${COMMIT})" - - - name: Auto-Rollback on Health Check Failure - if: failure() && steps.health-check.outcome == 'failure' - run: | - 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 "=== AUTOMATIC ROLLBACK INITIATED ===" - echo "Health check failed, rolling back to previous version..." - - if [ "$PREV_TIMESTAMP" != "none" ]; then - PREV_DEPLOY="${DEPLOY_BASE}/quantengine_${PREV_TIMESTAMP}" - if [ -d "$PREV_DEPLOY" ]; then - echo "Restoring symlink to: $PREV_DEPLOY" - ln -sfn "${PREV_DEPLOY}" "${ACTIVE_LINK}" - echo "Restarting service..." - systemctl restart quantengine 2>&1 || echo "WARNING: Service restart may have issues" - sleep 3 - echo "Rollback completed" - send_telegram "ROLLBACK: Deployment of ${COMMIT} failed, rolled back to ${PREV_TIMESTAMP}" - else - echo "ERROR: Previous deployment directory not found" - send_telegram "CRITICAL: Rollback failed - previous deployment not found" - exit 1 - fi - else - echo "ERROR: No previous deployment available for rollback" - send_telegram "CRITICAL: Health check failed - no previous deployment to rollback to" - 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 - - - name: Post-Deployment Verification - if: success() - run: | - echo "=== POST-DEPLOYMENT VERIFICATION ===" - - # Public endpoints - echo "[1/3] Verifying public endpoints..." - for endpoint in "/" "/Account/Login"; do - code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 "https://quant.taxbaik.com${endpoint}") - echo " https://quant.taxbaik.com${endpoint} -> $code" - if ! echo "$code" | grep -qE '^(200|302|401)$'; then - echo " WARNING: Unexpected response code" - fi - done - - # Nginx - echo "[2/3] Verifying Nginx configuration..." - if nginx -t 2>&1 | grep -q "successful"; then - echo " OK: Nginx syntax valid" - else - echo " WARNING: Nginx validation may have issues" - fi - - # - echo "[3/3] Creating deployment record..." - DEPLOYMENT_SUMMARY="deployment_summary_${{ steps.deploy.outputs.timestamp }}.txt" - cat > "$DEPLOYMENT_SUMMARY" << EOF -DEPLOYMENT SUCCESSFUL -===================== - -Timestamp: ${{ steps.deploy.outputs.timestamp }} -Commit: ${{ steps.deploy.outputs.commit }} -Target: ${{ steps.deploy.outputs.target_dir }} -Previous: ${{ steps.deploy.outputs.prev_version }} -Status: ACTIVE - -Health Check: PASSED -Service: RUNNING -Database: CONNECTED -Public Endpoints: RESPONDING - -EOF - - echo "OK: Deployment record created" - echo "=== VERIFICATION COMPLETE ===" - - - name: Cleanup Old Deployments - if: always() - run: | - DEPLOY_BASE="/home/kjh2064/deployments" - KEEP_COUNT=5 - - echo "Cleaning up old deployments (keeping $KEEP_COUNT most recent)..." - cd "$DEPLOY_BASE" - - count=$(ls -d quantengine_* 2>/dev/null | wc -l) - if [ $count -gt $KEEP_COUNT ]; then - remove_count=$((count - KEEP_COUNT)) - echo "Removing $remove_count old deployment(s)..." - ls -dt quantengine_* | tail -n +$((KEEP_COUNT + 1)) | while read -r old_dir; do - echo " Removing: $old_dir" - rm -rf "$old_dir" 2>/dev/null || echo " WARNING: Could not remove $old_dir" - done - fi - - echo "Cleanup complete. Current deployments:" - ls -ldt quantengine_* | head -5 | awk '{print $9, "(" $5 " bytes)"}' - - - name: Notify Success - if: success() - run: | - 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=SUCCESS: QuantEngine deployment complete (commit: ${{ steps.deploy.outputs.commit }})" \ - -d "parse_mode=HTML" >/dev/null || true - - - name: Notify Failure - if: failure() - run: | - 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=FAILURE: QuantEngine deployment failed (commit: ${{ steps.deploy.outputs.commit }}) - -Logs: https://gitea.taxbaik.com/kjh2064/QuantEngineByItz/actions/runs/${{ github.run_id }}" \ - -d "parse_mode=HTML" >/dev/null || true - - - 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 + echo "Deployment triggered: ${{ github.run_number }}" + echo "Commit: $(git rev-parse --short HEAD)" + echo "Artifact: ${{ env.ARTIFACT }}" diff --git a/.gitea/workflows/deploy-prod.yml.backup b/.gitea/workflows/deploy-prod.yml.backup new file mode 100644 index 00000000..ab24e676 --- /dev/null +++ b/.gitea/workflows/deploy-prod.yml.backup @@ -0,0 +1,644 @@ +name: Deploy to Production + +on: + push: + branches: [ main ] + workflow_dispatch: + +# Phase 4: Manual-only deployment (improved & hardened) +# Automatic deployment moved to merge-to-main.yml (Stage 5) +# Use this workflow for manual deployments when needed +# +# Error handling: Comprehensive logging + automatic rollback +# Security: SSH key validation, deployment verification +# Observability: Detailed stage reporting + Telegram notifications + +concurrency: + group: deploy-prod-main + cancel-in-progress: false + +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" + DEPLOY_TIMEOUT: "600" + HEALTH_CHECK_RETRIES: "5" + HEALTH_CHECK_DELAY: "3" + +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 secret not configured in Gitea" + echo " Please set secret in Repository Settings > Secrets" + exit 1 + 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 < ./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: Pre-Deployment Verification + run: | + echo "=== PRE-DEPLOYMENT CHECKS ===" + + # 1. SSH + if [ ! -f ~/.ssh/id_rsa ]; then + echo "ERROR: SSH key not found" + exit 1 + fi + echo "OK: SSH key present" + + # 2. + if [ ! -f quantengine.tar.gz ]; then + echo "ERROR: Build artifact (quantengine.tar.gz) not found" + exit 1 + fi + ARTIFACT_SIZE=$(stat -c%s quantengine.tar.gz) + if [ "$ARTIFACT_SIZE" -lt 1000000 ]; then + echo "WARNING: Artifact seems small (${ARTIFACT_SIZE} bytes), but proceeding" + fi + echo "OK: Build artifact present (${ARTIFACT_SIZE} bytes)" + + # 3. + for file in deploy/quantengine.env deploy_gb.sh; do + if [ ! -f "$file" ]; then + echo "ERROR: Required file missing: $file" + exit 1 + fi + done + echo "OK: All required deployment files present" + + # 4. + if [ -z "${{ secrets.QUANTENGINE_DB_PASSWORD }}" ]; then + echo "ERROR: DB password secret not configured" + exit 1 + fi + echo "OK: DB credentials configured" + + echo "=== ALL PRE-DEPLOYMENT CHECKS PASSED ===" + + - 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" + TARGET_DIR="${DEPLOY_BASE}/quantengine_${TIMESTAMP}_${COMMIT}_${RUN_NUM}" + DEPLOYMENT_LOG="./deployment_${TIMESTAMP}.log" + + 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 + } + + trap 'on_error' ERR + on_error() { + echo "DEPLOYMENT FAILED" | tee -a "$DEPLOYMENT_LOG" + send_telegram "DEPLOYMENT FAILED: $COMMIT at $(date)" + exit 1 + } + + { + echo "=== DEPLOYMENT START: $TIMESTAMP ===" + echo "Commit: $COMMIT" + echo "Run: $RUN_NUM" + echo "Target: $TARGET_DIR" + echo "" + + # + echo "[1/8] Creating deployment directories..." + mkdir -p "${DEPLOY_BASE}" || { echo "FATAL: Cannot create deploy base"; exit 1; } + mkdir -p "${TARGET_DIR}" || { echo "FATAL: Cannot create target dir"; exit 1; } + echo "OK: Directories created" + echo "" + + # + echo "[2/8] Extracting build artifact..." + if ! tar -xzf quantengine.tar.gz -C "${TARGET_DIR}"; then + echo "FATAL: Failed to extract artifact" + exit 1 + fi + echo "OK: Artifact extracted" + ls "${TARGET_DIR}" | head -10 + echo "" + + # + echo "[3/8] Normalizing deployment structure..." + if [ -d "${TARGET_DIR}/net10.0" ]; then + echo "Found net10.0 subdirectory, moving to root..." + if ! mv "${TARGET_DIR}/net10.0"/* "${TARGET_DIR}/"; then + echo "WARNING: Some files could not be moved from net10.0" + fi + if [ -d "${TARGET_DIR}/net10.0" ]; then + rmdir "${TARGET_DIR}/net10.0" 2>/dev/null || echo "Warning: Could not remove net10.0 dir" + fi + fi + echo "OK: Structure normalized" + echo "" + + # + echo "[4/8] Validating deployment contents..." + if [ ! -f "${TARGET_DIR}/QuantEngine.Web.dll" ]; then + echo "FATAL: QuantEngine.Web.dll not found in deployment" + exit 1 + fi + if [ ! -f "${TARGET_DIR}/appsettings.json" ]; then + echo "FATAL: appsettings.json not found" + exit 1 + fi + echo "OK: All required files present" + echo "" + + # + echo "[5/8] Installing environment configuration..." + mkdir -p /home/kjh2064/.config || { echo "WARNING: Cannot create config dir"; } + install -m 600 ./deploy/quantengine.env /home/kjh2064/.config/quantengine.env || { echo "WARNING: Config file install failed"; } + echo "OK: Configuration installed" + echo "" + + # appsettings.Production.json + echo "[6/8] Creating production appsettings..." + mkdir -p "${TARGET_DIR}" + DB_PASSWORD="${{ secrets.QUANTENGINE_DB_PASSWORD }}" + cat > "${TARGET_DIR}/appsettings.Production.json" << EOF +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "Host=127.0.0.1;Database=quantenginedb;Username=quantengine_app;Password=${DB_PASSWORD};Search Path=quantengine;" + }, + "AdminSettings": { + "Username": "admin", + "Password": "quant123!" + } +} +EOF + chmod 600 "${TARGET_DIR}/appsettings.Production.json" + echo "OK: appsettings.Production.json created" + echo "" + + } | tee "$DEPLOYMENT_LOG" + + echo "timestamp=${TIMESTAMP}" >> $GITHUB_OUTPUT + echo "commit=${COMMIT}" >> $GITHUB_OUTPUT + echo "target_dir=${TARGET_DIR}" >> $GITHUB_OUTPUT + + # () + PREV_VERSION="none" + if [ -L "${ACTIVE_LINK}" ]; then + PREV_VERSION=$(readlink -f "${ACTIVE_LINK}") + PREV_TIMESTAMP=$(basename "${PREV_VERSION}") + else + PREV_TIMESTAMP="none" + fi + + echo "[7/8] Executing Green-Blue deployment..." + export DEPLOY_FROM_CI=1 + chmod +x "${TARGET_DIR}/deploy_gb.sh" + + if ! "${TARGET_DIR}/deploy_gb.sh" >> "$DEPLOYMENT_LOG" 2>&1; then + echo "DEPLOYMENT FAILED: Green-Blue swap error" + send_telegram "DEPLOYMENT FAILED: Green-Blue swap failed for $COMMIT" + exit 1 + fi + + echo "OK: Green-Blue deployment completed" + + # + cat > "${TARGET_DIR}/.deployment_info" << EOF +Deployed: $(date -u +'%Y-%m-%dT%H:%M:%SZ') +Commit: ${COMMIT} +Timestamp: ${TIMESTAMP} +Run: ${RUN_NUM} +Previous: ${PREV_TIMESTAMP} +Status: DEPLOYED +EOF + + echo "timestamp=${TIMESTAMP}" >> $GITHUB_OUTPUT + echo "commit=${COMMIT}" >> $GITHUB_OUTPUT + echo "target_dir=${TARGET_DIR}" >> $GITHUB_OUTPUT + echo "prev_version=${PREV_TIMESTAMP}" >> $GITHUB_OUTPUT + + - name: Health Check & Verification + id: health-check + run: | + TIMESTAMP="${{ steps.deploy.outputs.timestamp }}" + COMMIT="${{ steps.deploy.outputs.commit }}" + TARGET_DIR="${{ steps.deploy.outputs.target_dir }}" + 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 "=== POST-DEPLOYMENT HEALTH CHECKS ===" + + # 1. + echo "[1/4] Verifying deployment directory..." + if [ ! -d "$TARGET_DIR" ]; then + echo "FATAL: Deployment directory not found: $TARGET_DIR" + exit 1 + fi + if [ ! -f "${TARGET_DIR}/QuantEngine.Web.dll" ]; then + echo "FATAL: Application DLL not found in deployment" + exit 1 + fi + echo "OK: Deployment directory verified" + + # 2. Loopback + echo "[2/4] Performing loopback health checks..." + health_check_passed=0 + for i in $(seq 1 ${{ env.HEALTH_CHECK_RETRIES }}); do + echo " Attempt $i/${{ env.HEALTH_CHECK_RETRIES }}..." + if timeout 10 curl -s -f -o /dev/null -w '%{http_code}' http://127.0.0.1:5000/ 2>/dev/null | grep -qE '^(200|302|401)$'; then + echo " OK: Service responding" + health_check_passed=1 + break + fi + if [ $i -lt ${{ env.HEALTH_CHECK_RETRIES }} ]; then + sleep ${{ env.HEALTH_CHECK_DELAY }} + fi + done + + if [ $health_check_passed -eq 0 ]; then + echo "FAILED: Health check did not pass after ${{ env.HEALTH_CHECK_RETRIES }} attempts" + echo "status=failed" >> $GITHUB_OUTPUT + exit 1 + fi + echo "OK: Loopback health check passed" + + # 3. + echo "[3/4] Verifying database connectivity..." + if timeout 10 bash -c 'cat /home/kjh2064/.config/quantengine.env | grep -q "postgresql"' 2>/dev/null; then + echo "OK: Database credentials configured" + else + echo "WARNING: Could not verify database credentials" + fi + + # 4. + echo "[4/4] Checking service status..." + if systemctl is-active --quiet quantengine; then + echo "OK: Service is running" + else + echo "WARNING: Service may not be running, but health checks passed" + fi + + echo "status=success" >> $GITHUB_OUTPUT + echo "=== ALL HEALTH CHECKS PASSED ===" + send_telegram "OK: QuantEngine deployed successfully (commit: ${COMMIT})" + + - name: Auto-Rollback on Health Check Failure + if: failure() && steps.health-check.outcome == 'failure' + run: | + 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 "=== AUTOMATIC ROLLBACK INITIATED ===" + echo "Health check failed, rolling back to previous version..." + + if [ "$PREV_TIMESTAMP" != "none" ]; then + PREV_DEPLOY="${DEPLOY_BASE}/quantengine_${PREV_TIMESTAMP}" + if [ -d "$PREV_DEPLOY" ]; then + echo "Restoring symlink to: $PREV_DEPLOY" + ln -sfn "${PREV_DEPLOY}" "${ACTIVE_LINK}" + echo "Restarting service..." + systemctl restart quantengine 2>&1 || echo "WARNING: Service restart may have issues" + sleep 3 + echo "Rollback completed" + send_telegram "ROLLBACK: Deployment of ${COMMIT} failed, rolled back to ${PREV_TIMESTAMP}" + else + echo "ERROR: Previous deployment directory not found" + send_telegram "CRITICAL: Rollback failed - previous deployment not found" + exit 1 + fi + else + echo "ERROR: No previous deployment available for rollback" + send_telegram "CRITICAL: Health check failed - no previous deployment to rollback to" + 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 + + - name: Post-Deployment Verification + if: success() + run: | + echo "=== POST-DEPLOYMENT VERIFICATION ===" + + # Public endpoints + echo "[1/3] Verifying public endpoints..." + for endpoint in "/" "/Account/Login"; do + code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 "https://quant.taxbaik.com${endpoint}") + echo " https://quant.taxbaik.com${endpoint} -> $code" + if ! echo "$code" | grep -qE '^(200|302|401)$'; then + echo " WARNING: Unexpected response code" + fi + done + + # Nginx + echo "[2/3] Verifying Nginx configuration..." + if nginx -t 2>&1 | grep -q "successful"; then + echo " OK: Nginx syntax valid" + else + echo " WARNING: Nginx validation may have issues" + fi + + # + echo "[3/3] Creating deployment record..." + DEPLOYMENT_SUMMARY="deployment_summary_${{ steps.deploy.outputs.timestamp }}.txt" + cat > "$DEPLOYMENT_SUMMARY" << EOF +DEPLOYMENT SUCCESSFUL +===================== + +Timestamp: ${{ steps.deploy.outputs.timestamp }} +Commit: ${{ steps.deploy.outputs.commit }} +Target: ${{ steps.deploy.outputs.target_dir }} +Previous: ${{ steps.deploy.outputs.prev_version }} +Status: ACTIVE + +Health Check: PASSED +Service: RUNNING +Database: CONNECTED +Public Endpoints: RESPONDING + +EOF + + echo "OK: Deployment record created" + echo "=== VERIFICATION COMPLETE ===" + + - name: Cleanup Old Deployments + if: always() + run: | + DEPLOY_BASE="/home/kjh2064/deployments" + KEEP_COUNT=5 + + echo "Cleaning up old deployments (keeping $KEEP_COUNT most recent)..." + cd "$DEPLOY_BASE" + + count=$(ls -d quantengine_* 2>/dev/null | wc -l) + if [ $count -gt $KEEP_COUNT ]; then + remove_count=$((count - KEEP_COUNT)) + echo "Removing $remove_count old deployment(s)..." + ls -dt quantengine_* | tail -n +$((KEEP_COUNT + 1)) | while read -r old_dir; do + echo " Removing: $old_dir" + rm -rf "$old_dir" 2>/dev/null || echo " WARNING: Could not remove $old_dir" + done + fi + + echo "Cleanup complete. Current deployments:" + ls -ldt quantengine_* | head -5 | awk '{print $9, "(" $5 " bytes)"}' + + - name: Notify Success + if: success() + run: | + 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=SUCCESS: QuantEngine deployment complete (commit: ${{ steps.deploy.outputs.commit }})" \ + -d "parse_mode=HTML" >/dev/null || true + + - name: Notify Failure + if: failure() + run: | + 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=FAILURE: QuantEngine deployment failed (commit: ${{ steps.deploy.outputs.commit }}) + +Logs: https://gitea.taxbaik.com/kjh2064/QuantEngineByItz/actions/runs/${{ github.run_id }}" \ + -d "parse_mode=HTML" >/dev/null || true + + - 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