name: Deploy to Production on: push: branches: [ main ] workflow_dispatch: concurrency: group: deploy-prod-main cancel-in-progress: false env: DEPLOY_HOST: 178.104.200.7 DEPLOY_USER: kjh2064 DEPLOY_PORT: 22 SERVICE_NAME: quantengine DOTNET_VERSION: '10.0.x' jobs: build: name: Build Release runs-on: ubuntu-latest timeout-minutes: 15 outputs: artifact-name: ${{ steps.metadata.outputs.artifact }} commit-hash: ${{ steps.metadata.outputs.commit }} steps: - name: Checkout uses: actions/checkout@v3 - name: Setup .NET uses: actions/setup-dotnet@v3 with: dotnet-version: ${{ env.DOTNET_VERSION }} - name: Generate Metadata id: metadata run: | COMMIT=$(git rev-parse --short HEAD) TIMESTAMP=$(date +%Y%m%d_%H%M%S) ARTIFACT="quantengine-${TIMESTAMP}-${COMMIT}.tar.gz" echo "artifact=${ARTIFACT}" >> $GITHUB_OUTPUT echo "commit=${COMMIT}" >> $GITHUB_OUTPUT echo "timestamp=${TIMESTAMP}" >> $GITHUB_OUTPUT - name: Restore & Build run: | dotnet restore src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj dotnet build src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj -c Release --no-restore -p:ContinuousIntegrationBuild=true - name: Publish run: | dotnet publish src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj \ -c Release \ -o ./publish \ --no-restore \ --no-build - name: Package Artifact run: | tar -czf "${{ steps.metadata.outputs.artifact }}" -C ./publish . ls -lh "${{ steps.metadata.outputs.artifact }}" file "${{ steps.metadata.outputs.artifact }}" - name: Upload Artifact uses: actions/upload-artifact@v3 with: name: build-${{ github.run_number }} path: quantengine-*.tar.gz retention-days: 7 pre-deploy-check: name: Pre-Deployment Verification runs-on: ubuntu-latest needs: build timeout-minutes: 5 steps: - name: Verify SSH Key run: | if [ -z "${{ secrets.SSH_KEY }}" ]; then echo "ERROR: SSH_KEY secret not configured" exit 1 fi echo "OK: SSH key configured" - name: Verify DB Secrets run: | if [ -z "${{ secrets.QUANTENGINE_DB_PASSWORD }}" ]; then echo "ERROR: QUANTENGINE_DB_PASSWORD secret not configured" exit 1 fi echo "OK: DB password configured" - name: Verify Artifact run: | if [ "${{ needs.build.outputs.artifact-name }}" = "" ]; then echo "ERROR: Build artifact not found" exit 1 fi echo "OK: Artifact: ${{ needs.build.outputs.artifact-name }}" - name: SSH Connectivity Test run: | mkdir -p ~/.ssh echo "${{ secrets.SSH_KEY }}" > ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key ssh-keyscan -p ${{ env.DEPLOY_PORT }} ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null if ssh -i ~/.ssh/deploy_key -p ${{ env.DEPLOY_PORT }} ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} 'echo OK' 2>/dev/null; then echo "OK: SSH connectivity verified" else echo "ERROR: Cannot connect via SSH" exit 1 fi deploy: name: Deploy to Production runs-on: ubuntu-latest needs: [ build, pre-deploy-check ] timeout-minutes: 20 steps: - name: Checkout uses: actions/checkout@v3 - name: Download Artifact uses: actions/download-artifact@v3 with: name: build-${{ github.run_number }} - name: Setup SSH run: | mkdir -p ~/.ssh echo "${{ secrets.SSH_KEY }}" > ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key ssh-keyscan -p ${{ env.DEPLOY_PORT }} ${{ env.DEPLOY_HOST }} >> ~/.ssh/known_hosts 2>/dev/null - name: Deploy to Server id: deploy run: | set -e ARTIFACT="${{ needs.build.outputs.artifact-name }}" COMMIT="${{ needs.build.outputs.commit-hash }}" TIMESTAMP=$(date +%Y%m%d_%H%M%S) DEPLOY_DIR="/home/${{ env.DEPLOY_USER }}/deployments/quantengine_${TIMESTAMP}_${COMMIT}" echo "Artifact: $ARTIFACT" echo "Deploy Dir: $DEPLOY_DIR" # Upload artifact scp -i ~/.ssh/deploy_key -P ${{ env.DEPLOY_PORT }} \ "$ARTIFACT" ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }}:/tmp/ # Deploy via SSH ssh -i ~/.ssh/deploy_key -p ${{ env.DEPLOY_PORT }} ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} << 'DEPLOY_SCRIPT' set -e ARTIFACT="${{ needs.build.outputs.artifact-name }}" DEPLOY_DIR="${{ env.DEPLOY_DIR }}" # Create deployment directory mkdir -p "$DEPLOY_DIR" # Extract artifact tar -xzf "/tmp/$ARTIFACT" -C "$DEPLOY_DIR" # Update symlink ln -sfn "$DEPLOY_DIR" ~/quantengine_active # Health check sleep 2 if curl -sf http://127.0.0.1:5000/Account/Login > /dev/null; then echo "OK: Health check passed" else echo "WARNING: Health check may have issues" fi echo "Deployment completed: $DEPLOY_DIR" DEPLOY_SCRIPT echo "deploy-dir=${DEPLOY_DIR}" >> $GITHUB_OUTPUT - name: Health Check id: health timeout-minutes: 2 run: | for i in {1..10}; do if curl -sf http://${{ env.DEPLOY_HOST }}:5000/Account/Login > /dev/null 2>&1; then echo "✓ Health check passed (attempt $i)" exit 0 fi echo "Attempt $i/10..." sleep 3 done echo "ERROR: Health check failed after 10 attempts" exit 1 - name: Verify Deployment run: | ssh -i ~/.ssh/deploy_key -p ${{ env.DEPLOY_PORT }} ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} << 'VERIFY_SCRIPT' ACTIVE=$(readlink ~/quantengine_active) echo "=== Deployment Verification ===" echo "Active deployment: $ACTIVE" ls -lhd "$ACTIVE" echo "" echo "=== Service Status ===" systemctl is-active quantengine.service VERIFY_SCRIPT post-deploy: name: Post-Deployment Reporting runs-on: ubuntu-latest if: always() needs: [ build, deploy ] steps: - name: Deployment Summary run: | echo "=== Deployment Summary ===" echo "Run: ${{ github.run_number }}" echo "Commit: ${{ needs.build.outputs.commit-hash }}" echo "Artifact: ${{ needs.build.outputs.artifact-name }}" echo "Status: ${{ job.status }}" - name: Success Notification if: success() run: | echo "✅ Deployment successful" echo "Server: ${{ env.DEPLOY_HOST }}" echo "Service: ${{ env.SERVICE_NAME }}" - name: Failure Notification if: failure() run: | echo "❌ Deployment failed" echo "Check logs for details" exit 1