From 7976dcee599e2432720ce6bedd57755049989af1 Mon Sep 17 00:00:00 2001 From: Carlos Date: Tue, 23 Dec 2025 15:00:19 +0100 Subject: [PATCH 1/2] feat: Update deployment workflow with health checks and auto-rollback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure the deployment workflow into distinct phases: 1. Pre-deployment checks - validate environment, save state 2. Deploy - update code, deps, migrations, reload service 3. Verify - health check with retries 4. Rollback - automatic recovery on failure 5. Report - always log final status Key improvements: - Health verification before considering deployment successful - Automatic rollback if health check fails - Backwards compatible - falls back to legacy flow if scripts missing - Deployment lock cleanup in final step - Clear logging at each phase The workflow now uses the deployment scripts from install/deploy/ when available, but includes inline fallback logic for backwards compatibility during the transition period. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .github/workflows/sp-deployment-pipeline.yml | 178 ++++++++++++++++--- 1 file changed, 152 insertions(+), 26 deletions(-) diff --git a/.github/workflows/sp-deployment-pipeline.yml b/.github/workflows/sp-deployment-pipeline.yml index 00b614fd..06082d13 100644 --- a/.github/workflows/sp-deployment-pipeline.yml +++ b/.github/workflows/sp-deployment-pipeline.yml @@ -10,6 +10,8 @@ on: env: DEPLOY_BRANCH: master + INSTALL_FOLDER: /var/www/sample-platform + SAMPLE_REPOSITORY: /repository jobs: deploy: @@ -17,9 +19,10 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'success' }} permissions: id-token: write - contents: read # required for actions/checkout + contents: read + steps: - - name: Deployment with ssh commands using ssh key + - name: Pre-deployment checks uses: appleboy/ssh-action@master with: host: ${{ vars.PLATFORM_DOMAIN }} @@ -27,37 +30,160 @@ jobs: key: ${{ secrets.SSH_KEY_PRIVATE }} port: 22 script_stop: true - command_timeout: 10m + command_timeout: 2m + envs: INSTALL_FOLDER,SAMPLE_REPOSITORY,DEPLOY_BRANCH script: | - echo "defining directories" - INSTALL_FOLDER="/var/www/sample-platform" - SAMPLE_REPOSITORY="/repository" + echo "=== Pre-deployment checks ===" + cd $INSTALL_FOLDER - echo "jump to app folder" + # Check if deployment scripts exist (for backwards compatibility) + if [ -f "install/deploy/pre_deploy.sh" ]; then + sudo INSTALL_FOLDER="$INSTALL_FOLDER" \ + SAMPLE_REPOSITORY="$SAMPLE_REPOSITORY" \ + DEPLOY_BRANCH="$DEPLOY_BRANCH" \ + bash install/deploy/pre_deploy.sh + else + echo "Deployment scripts not found, using legacy validation" + # Basic validation + test -f config.py || { echo "ERROR: config.py not found"; exit 1; } + # Save current commit for potential manual rollback + git rev-parse HEAD > /tmp/previous_commit.txt + echo "Current commit saved: $(cat /tmp/previous_commit.txt)" + fi + + - name: Deploy application + uses: appleboy/ssh-action@master + with: + host: ${{ vars.PLATFORM_DOMAIN }} + username: ${{ vars.SSH_USER }} + key: ${{ secrets.SSH_KEY_PRIVATE }} + port: 22 + script_stop: true + command_timeout: 10m + envs: INSTALL_FOLDER,SAMPLE_REPOSITORY,DEPLOY_BRANCH + script: | + echo "=== Deploying application ===" cd $INSTALL_FOLDER - echo "checkout branch" - sudo git restore . - sudo git checkout ${{env.DEPLOY_BRANCH}} - sudo git fetch origin ${{env.DEPLOY_BRANCH}} + # Check if deployment scripts exist + if [ -f "install/deploy/deploy.sh" ]; then + sudo INSTALL_FOLDER="$INSTALL_FOLDER" \ + SAMPLE_REPOSITORY="$SAMPLE_REPOSITORY" \ + DEPLOY_BRANCH="$DEPLOY_BRANCH" \ + bash install/deploy/deploy.sh + else + echo "Using legacy deployment" + # Legacy deployment (will be removed after scripts are merged) + sudo git restore . + sudo git checkout $DEPLOY_BRANCH + sudo git fetch origin $DEPLOY_BRANCH + sudo git reset --hard origin/$DEPLOY_BRANCH + sudo git clean -f -d + sudo git pull origin $DEPLOY_BRANCH + + sudo python -m pip install -r requirements.txt + sudo FLASK_APP=./run.py flask db upgrade + + sudo cp "install/ci-vm/ci-linux/ci/bootstrap" "${SAMPLE_REPOSITORY}/TestData/ci-linux/bootstrap" 2>/dev/null || true + sudo cp "install/ci-vm/ci-linux/ci/runCI" "${SAMPLE_REPOSITORY}/TestData/ci-linux/runCI" 2>/dev/null || true + sudo cp "install/ci-vm/ci-windows/ci/runCI.bat" "${SAMPLE_REPOSITORY}/TestData/ci-windows/runCI.bat" 2>/dev/null || true - echo "avoid merge conflicts" - sudo git reset --hard origin/${{env.DEPLOY_BRANCH}} - sudo git clean -f -d + sudo systemctl reload platform + fi + + - name: Verify deployment + id: health_check + uses: appleboy/ssh-action@master + with: + host: ${{ vars.PLATFORM_DOMAIN }} + username: ${{ vars.SSH_USER }} + key: ${{ secrets.SSH_KEY_PRIVATE }} + port: 22 + script_stop: false + command_timeout: 2m + envs: INSTALL_FOLDER + script: | + echo "=== Verifying deployment ===" + cd $INSTALL_FOLDER - echo "update app from git" - sudo git pull origin ${{env.DEPLOY_BRANCH}} + # Check if deployment scripts exist + if [ -f "install/deploy/post_deploy.sh" ]; then + sudo INSTALL_FOLDER="$INSTALL_FOLDER" bash install/deploy/post_deploy.sh + else + echo "Using legacy health check" + # Legacy health check - just verify service is running + sleep 5 + if systemctl is-active --quiet platform; then + echo "Platform service is running" + # Try to hit the homepage + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1/ 2>/dev/null || echo "000") + if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 400 ]; then + echo "Homepage responding with HTTP $HTTP_CODE" + exit 0 + else + echo "ERROR: Homepage returned HTTP $HTTP_CODE" + exit 1 + fi + else + echo "ERROR: Platform service is not running" + systemctl status platform || true + exit 1 + fi + fi - echo "update dependencies" - sudo python -m pip install -r requirements.txt + - name: Rollback on failure + if: failure() && steps.health_check.outcome == 'failure' + uses: appleboy/ssh-action@master + with: + host: ${{ vars.PLATFORM_DOMAIN }} + username: ${{ vars.SSH_USER }} + key: ${{ secrets.SSH_KEY_PRIVATE }} + port: 22 + script_stop: false + command_timeout: 5m + envs: INSTALL_FOLDER,SAMPLE_REPOSITORY + script: | + echo "=== ROLLBACK INITIATED ===" + cd $INSTALL_FOLDER - echo "run migrations" - sudo FLASK_APP=./run.py flask db upgrade + # Check if deployment scripts exist + if [ -f "install/deploy/rollback.sh" ]; then + sudo INSTALL_FOLDER="$INSTALL_FOLDER" \ + SAMPLE_REPOSITORY="$SAMPLE_REPOSITORY" \ + bash install/deploy/rollback.sh + else + echo "Using legacy rollback" + # Legacy rollback + if [ -f "/tmp/previous_commit.txt" ]; then + PREV_COMMIT=$(cat /tmp/previous_commit.txt) + echo "Rolling back to commit: $PREV_COMMIT" + sudo git checkout "$PREV_COMMIT" + sudo python -m pip install -r requirements.txt + sudo systemctl reload platform + echo "Rollback complete" + else + echo "ERROR: No previous commit saved, cannot rollback" + echo "MANUAL INTERVENTION REQUIRED" + fi + fi - echo "update runCI script files" - sudo cp "install/ci-vm/ci-linux/ci/bootstrap" "${SAMPLE_REPOSITORY}/TestData/ci-linux/bootstrap" - sudo cp "install/ci-vm/ci-linux/ci/runCI" "${SAMPLE_REPOSITORY}/TestData/ci-linux/runCI" - sudo cp "install/ci-vm/ci-windows/ci/runCI.bat" "${SAMPLE_REPOSITORY}/TestData/ci-windows/runCI.bat" + - name: Report deployment status + if: always() + uses: appleboy/ssh-action@master + with: + host: ${{ vars.PLATFORM_DOMAIN }} + username: ${{ vars.SSH_USER }} + key: ${{ secrets.SSH_KEY_PRIVATE }} + port: 22 + script_stop: false + command_timeout: 30s + envs: INSTALL_FOLDER + script: | + echo "=== Deployment Summary ===" + cd $INSTALL_FOLDER + echo "Current commit: $(git rev-parse HEAD)" + echo "Branch: $(git branch --show-current)" + echo "Service status: $(systemctl is-active platform 2>/dev/null || echo 'unknown')" - echo "reload server" - sudo systemctl reload platform + # Cleanup lock file if it exists + rm -f /tmp/sp-deploy.lock From 85afc6225e88e542a77d2d706c14ef30f9f93c76 Mon Sep 17 00:00:00 2001 From: Carlos Date: Tue, 23 Dec 2025 15:20:19 +0100 Subject: [PATCH 2/2] security: Pin appleboy/ssh-action to specific commit SHA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pin the ssh-action dependency to v1.2.4 (commit 823bd89e131d8d508129f9443cad5855e9ba96f0) instead of using @master to address SonarCloud security hotspot. Using a branch reference like @master is a security risk as the action could be updated with malicious code at any time. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .github/workflows/sp-deployment-pipeline.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/sp-deployment-pipeline.yml b/.github/workflows/sp-deployment-pipeline.yml index 06082d13..6b4f9597 100644 --- a/.github/workflows/sp-deployment-pipeline.yml +++ b/.github/workflows/sp-deployment-pipeline.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Pre-deployment checks - uses: appleboy/ssh-action@master + uses: appleboy/ssh-action@823bd89e131d8d508129f9443cad5855e9ba96f0 # v1.2.4 with: host: ${{ vars.PLATFORM_DOMAIN }} username: ${{ vars.SSH_USER }} @@ -52,7 +52,7 @@ jobs: fi - name: Deploy application - uses: appleboy/ssh-action@master + uses: appleboy/ssh-action@823bd89e131d8d508129f9443cad5855e9ba96f0 # v1.2.4 with: host: ${{ vars.PLATFORM_DOMAIN }} username: ${{ vars.SSH_USER }} @@ -93,7 +93,7 @@ jobs: - name: Verify deployment id: health_check - uses: appleboy/ssh-action@master + uses: appleboy/ssh-action@823bd89e131d8d508129f9443cad5855e9ba96f0 # v1.2.4 with: host: ${{ vars.PLATFORM_DOMAIN }} username: ${{ vars.SSH_USER }} @@ -133,7 +133,7 @@ jobs: - name: Rollback on failure if: failure() && steps.health_check.outcome == 'failure' - uses: appleboy/ssh-action@master + uses: appleboy/ssh-action@823bd89e131d8d508129f9443cad5855e9ba96f0 # v1.2.4 with: host: ${{ vars.PLATFORM_DOMAIN }} username: ${{ vars.SSH_USER }} @@ -169,7 +169,7 @@ jobs: - name: Report deployment status if: always() - uses: appleboy/ssh-action@master + uses: appleboy/ssh-action@823bd89e131d8d508129f9443cad5855e9ba96f0 # v1.2.4 with: host: ${{ vars.PLATFORM_DOMAIN }} username: ${{ vars.SSH_USER }}