name: Rollback to Previous Version run-name: πŸ”™ Rollback by @${{ gitea.actor }} # ─────────────────────────────────────────────────────── # Owner: DevOps (Architekt) # Trigger: EXCLUSIVELY manual (workflow_dispatch). # # This workflow reverts the deploy path to the code at a # given git tag/ref, then rebuilds and redeploys the stack. # # Strategy: git checkout β†’ docker compose up -d --build # This is a "full restart rollback" β€” safest for containerized # apps where DB schema changes may need the matching API binary. # # DB migrations: the API runs MigrateAsync on startup. If the # rollback-tag's migration history is a prefix of the current DB, # EF Core handles this gracefully (no-op for already-applied # migrations). If the tag predates a destructive migration, manual # DB intervention is needed β€” that's an edge case surfaced to DevOps. # ─────────────────────────────────────────────────────── concurrency: group: deploy-production cancel-in-progress: false on: workflow_dispatch: inputs: target_tag: description: 'Git tag to roll back to (e.g. v0.2.49)' required: true type: string confirm: description: 'Type "ROLLBACK" to confirm' required: true type: string jobs: rollback: name: Rollback Nexus runs-on: ubuntu-latest env: DEPLOY_PATH: /opt/openclaw/data/openclaw/workspace/nexus ENV_TMPFILE: /tmp/nexus-rollback-env ENV_POSTGRES_PASSWORD: ${{ secrets.ENV_POSTGRES_PASSWORD }} ENV_JWT_KEY: ${{ secrets.ENV_JWT_KEY }} ENV_OWNER_PASSWORD: ${{ secrets.ENV_OWNER_PASSWORD }} ENV_OPENCLAW_TOKEN: ${{ secrets.ENV_OPENCLAW_TOKEN }} steps: # ═══════════════════════════════════════════════════ # Step 0: Safety gate β€” require explicit confirmation # ═══════════════════════════════════════════════════ - name: Safety Gate run: | if [ "${{ inputs.confirm }}" != "ROLLBACK" ]; then echo "❌ Rollback aborted: confirmation string must be 'ROLLBACK'" echo " You entered: '${{ inputs.confirm }}'" exit 1 fi echo "βœ… Rollback confirmed β€” proceeding to ${{ inputs.target_tag }}" # ═══════════════════════════════════════════════════ # Step 1: Checkout target tag # ═══════════════════════════════════════════════════ - name: Checkout target tag uses: actions/checkout@v4 with: ref: refs/tags/${{ inputs.target_tag }} fetch-depth: 0 fetch-tags: true # ═══════════════════════════════════════════════════ # Step 2: Verify tag exists # ═══════════════════════════════════════════════════ - name: Verify tag run: | set -euo pipefail ACTUAL_TAG=$(git describe --tags --exact-match 2>/dev/null || echo "") if [ -z "$ACTUAL_TAG" ]; then echo "❌ Tag '${{ inputs.target_tag }}' not found in repository" echo " Available tags:" git tag -l 'v*' | sort -V | tail -20 exit 1 fi echo "βœ… Checked out: $ACTUAL_TAG" echo " Commit: $(git rev-parse --short HEAD)" echo " Message: $(git log -1 --oneline)" # Read version from VERSION file at this tag if [ -f VERSION ]; then VERSION=$(cat VERSION | tr -d '[:space:]') echo " VERSION: $VERSION" fi # ═══════════════════════════════════════════════════ # Step 3: Prepare .env from secrets (safe temp file) # ═══════════════════════════════════════════════════ - name: Prepare .env (secrets β†’ temp file) run: | set -euo pipefail cat > "${ENV_TMPFILE}" </dev/null || rm -f "${ENV_TMPFILE}" echo "🧹 Temp .env removed" fi # ═══════════════════════════════════════════════════ # Step 7: Health Check # ═══════════════════════════════════════════════════ - name: Health Check run: | echo "πŸ₯ Health check after rollback..." RETRY=0 MAX=6 WAIT=1 while [ $RETRY -lt $MAX ]; do RETRY=$((RETRY + 1)) if curl -sf --max-time 10 https://nexus.noveria.net/health; then echo "" echo "βœ… Health check passed (attempt $RETRY/$MAX)" exit 0 fi echo "⏳ Attempt $RETRY/$MAX failed, waiting ${WAIT}s..." sleep $WAIT NEXT=$((WAIT + RETRY)) [ $NEXT -le 15 ] && WAIT=$NEXT || WAIT=15 done echo "❌ Health check failed after $MAX attempts" exit 1 # ═══════════════════════════════════════════════════ # Step 8: Smoke Test # ═══════════════════════════════════════════════════ - name: Smoke Test run: | echo "πŸ” Smoke test after rollback..." PASS=0 FAIL=0 BASE="https://nexus.noveria.net" check() { local path="$1" label="$2" expected="${3:-200}" local code code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "${BASE}${path}") printf " %-25s HTTP %s" "${label}:" "${code}" if [ "$code" = "$expected" ]; then echo " βœ…" PASS=$((PASS + 1)) else echo " ❌ (expected $expected)" FAIL=$((FAIL + 1)) fi } check "/dashboard" "Dashboard" 200 check "/health" "Health API" 200 check "/api/v1/operations/snapshot" "Operations API (auth)" 401 echo "" echo "Results: $PASS passed, $FAIL failed" if [ "$FAIL" -gt 0 ]; then echo "❌ Smoke test failed!" exit 1 fi echo "βœ… Rollback to ${{ inputs.target_tag }} successful" # ═══════════════════════════════════════════════════ # Step 9: Rollback Summary # ═══════════════════════════════════════════════════ - name: Rollback Summary if: always() run: | echo "" echo "═══════════════════════════════════════" echo " πŸ”™ Rollback Summary" echo "═══════════════════════════════════════" echo " Rolled to: ${{ inputs.target_tag }}" echo " Triggered: @${{ gitea.actor }}" echo " Status: ${{ job.status }}" echo "═══════════════════════════════════════" # ═══════════════════════════════════════════════════ # Step 10: Failure β†’ Reviewer Handoff # ═══════════════════════════════════════════════════ - name: πŸ”΄ Rollback Failed β€” Reviewer Handoff if: failure() run: | echo "" echo "β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”" echo "β”‚ πŸ”΄ ROLLBACK FAILED β€” Reviewer muss fixen β”‚" echo "β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€" echo "β”‚ β”‚" echo "β”‚ Target: ${{ inputs.target_tag }}" echo "β”‚ Job: ${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_id }}" echo "β”‚ β”‚" echo "β”‚ β†’ DevOps (Architekt) analysiert den Fehler β”‚" echo "β”‚ β†’ Reviewer (Code-Fixer) behebt das Problem β”‚" echo "β”‚ β†’ DevOps verifiziert mit neuem Deploy β”‚" echo "β”‚ β”‚" echo "β”‚ Letzter bekannter funktionierender Stand: β”‚" echo "β”‚ β†’ 'git log --oneline -5' zeigt letzte Commits β”‚" echo "β”‚ β†’ Manuellen Rollback erwΓ€gen: β”‚" echo "β”‚ cd /opt/openclaw/data/openclaw/workspace/nexus β”‚" echo "β”‚ docker compose up -d (vorheriger Stand) β”‚" echo "β”‚ β”‚" echo "β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜"