name: Deploy to Production run-name: ๐Ÿš€ Deploy by @${{ gitea.actor }} # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # Owner: DevOps (Architekt) # CD v3 โ€” 2026-06-13 # # Triggers: # 1. AUTOMATIC after successful CI on main (workflow_run) # โ†’ Uses safe defaults: patch bump, all services, main ref. # โ†’ Commits marked with [skip ci] are filtered at job level # (prevents version-bump loops). # 2. MANUAL via workflow_dispatch with full parameter control. # # Concurrency: one deploy at a time. # Queued deploys wait โ€” no race conditions with parallel builds. # # Version-Bump / CI Loop Prevention: # The version-bump commit includes "[skip ci]" in its message, # which Gitea Actions respects. The auto-trigger additionally # checks for "[skip ci]" as a second safety layer. Together # they guarantee that a version-bump commit does NOT trigger # another CI โ†’ Deploy โ†’ Bump โ†’ CI cycle. # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ concurrency: group: deploy-production cancel-in-progress: false on: # โ”€โ”€ Auto-Trigger: after successful CI on main โ”€โ”€ workflow_run: workflows: ["CI - Build & Test"] types: [completed] branches: [main] # โ”€โ”€ Manual Trigger (full control) โ”€โ”€ workflow_dispatch: inputs: version_bump: description: 'Version bump type' required: true default: 'patch' type: choice options: - patch - minor - major service: description: 'Service to deploy (empty = all)' required: false default: '' type: string no_cache: description: 'Disable Docker build cache' required: false default: false type: boolean git_ref: description: 'Git ref to deploy (branch, tag, or commit SHA; default: main)' required: false default: 'main' type: string jobs: deploy: name: Deploy Nexus runs-on: ubuntu-latest if: | (github.event_name == 'workflow_dispatch') || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && !contains(github.event.workflow_run.head_commit.message, '[skip ci]')) # โ”€โ”€ Env for the deploy target path โ”€โ”€ env: DEPLOY_PATH: /opt/openclaw/data/openclaw/workspace/nexus ENV_TMPFILE: /tmp/nexus-deploy-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 1: Checkout # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - name: Checkout uses: actions/checkout@v4 with: ref: ${{ github.event_name == 'workflow_dispatch' && inputs.git_ref || 'main' }} fetch-depth: 0 fetch-tags: true # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # Step 2: Set up Git identity # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - name: Configure Git run: | git config user.email "devops@noveria.net" git config user.name "DevOps" # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # Step 3: Resolve deploy version # # Deploying main: DevOps may bump VERSION and create a tag. # Deploying any other ref: deploy exactly that ref, but DO NOT # mutate main or create a version-bump commit on another branch. # # For auto-deploys (workflow_run): always "patch" bump on main. # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - name: Resolve Version id: version run: | set -euo pipefail # Determine bump type (auto-deploy โ†’ patch; manual โ†’ user choice) BUMP_TYPE="${{ github.event_name == 'workflow_dispatch' && inputs.version_bump || 'patch' }}" # Read current version if [ ! -f VERSION ]; then echo "โŒ VERSION file not found" exit 1 fi CURRENT=$(cat VERSION | tr -d '[:space:]') if ! echo "$CURRENT" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then echo "โŒ Invalid semver in VERSION: '$CURRENT'" exit 1 fi MAJOR=$(echo "$CURRENT" | cut -d. -f1) MINOR=$(echo "$CURRENT" | cut -d. -f2) PATCH=$(echo "$CURRENT" | cut -d. -f3) case "$BUMP_TYPE" in major) NEW_MAJOR=$((MAJOR + 1)); NEW_MINOR=0; NEW_PATCH=0 ;; minor) NEW_MAJOR=$MAJOR; NEW_MINOR=$((MINOR + 1)); NEW_PATCH=0 ;; patch) NEW_MAJOR=$MAJOR; NEW_MINOR=$MINOR; NEW_PATCH=$((PATCH + 1)) ;; *) echo "โŒ Unknown bump type: $BUMP_TYPE"; exit 1 ;; esac # Determine git ref โ€” auto-deploy always uses main DEPLOY_REF="${{ github.event_name == 'workflow_dispatch' && inputs.git_ref || 'main' }}" if [ -z "$DEPLOY_REF" ] || [ "$DEPLOY_REF" = "main" ] || [ "$DEPLOY_REF" = "refs/heads/main" ]; then NEW_VERSION="${NEW_MAJOR}.${NEW_MINOR}.${NEW_PATCH}" echo "$NEW_VERSION" > VERSION git add VERSION git commit -m "chore: bump version to ${NEW_VERSION} [skip ci]" git tag -a "v${NEW_VERSION}" -m "Release v${NEW_VERSION}" git push origin HEAD:main --tags echo "version=$NEW_VERSION" >> "$GITEA_OUTPUT" echo "mutated_main=true" >> "$GITEA_OUTPUT" echo "๐Ÿ“ฆ Main deploy: version $CURRENT -> v${NEW_VERSION} (bump: $BUMP_TYPE, trigger: ${{ github.event_name }})" else echo "version=$CURRENT" >> "$GITEA_OUTPUT" echo "mutated_main=false" >> "$GITEA_OUTPUT" echo "๐Ÿ“ฆ Non-main deploy from '$DEPLOY_REF': using committed VERSION $CURRENT without git mutation" fi # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # Step 4: Build .env from secrets (SAFE) # # Secrets are written to /tmp/nexus-deploy-env โ€” NEVER # to a file inside the workspace that gets rsync'd to # the host. The temp file is deleted immediately after # compose operations complete. # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - name: Prepare .env (secrets โ†’ temp file) run: | set -euo pipefail cat > "${ENV_TMPFILE}" < /tmp/nexus-deploy-env if [ -n '${SERVICE_ARG}' ]; then echo '๐Ÿš€ Deploying service: ${SERVICE_ARG}' docker compose --env-file /tmp/nexus-deploy-env build ${BUILD_ARGS} ${SERVICE_ARG} docker compose --env-file /tmp/nexus-deploy-env up -d --wait --force-recreate ${SERVICE_ARG} else echo '๐Ÿš€ Deploying all services' docker compose --env-file /tmp/nexus-deploy-env build ${BUILD_ARGS} docker compose --env-file /tmp/nexus-deploy-env up -d --wait --force-recreate fi " < "${ENV_TMPFILE}" echo "โœ… Docker compose up completed" # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # Step 7: Clean up temp .env # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - name: Clean up temp .env if: always() run: | if [ -f "${ENV_TMPFILE}" ]; then shred -u "${ENV_TMPFILE}" 2>/dev/null || rm -f "${ENV_TMPFILE}" echo "๐Ÿงน Temp .env removed" fi # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # Step 8: Health Check (exponential backoff) # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - name: Health Check run: | echo "๐Ÿฅ Health check..." 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 # Fibonacci-ish backoff: 1,2,3,5,8,13 NEXT=$((WAIT + RETRY)) [ $NEXT -le 15 ] && WAIT=$NEXT || WAIT=15 done echo "โŒ Health check failed after $MAX attempts" exit 1 # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # Step 9: Smoke Test # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - name: Smoke Test run: | echo "๐Ÿ” Smoke test..." 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 "โœ… Smoke test passed โ€” v${{ steps.version.outputs.version }} is live" # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # Step 10: Deployment Summary # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - name: Deployment Summary if: always() run: | TRIGGER="${{ github.event_name == 'workflow_run' && 'Auto (CI success)' || 'Manual (workflow_dispatch)' }}" VERSION_BUMP="${{ github.event_name == 'workflow_dispatch' && inputs.version_bump || 'patch (auto)' }}" echo "" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo " ๐Ÿ“ฆ Deploy Summary" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" echo " Version: v${{ steps.version.outputs.version }}" echo " Git ref: ${{ github.event_name == 'workflow_dispatch' && inputs.git_ref || 'main' }}" echo " Main bump: ${{ steps.version.outputs.mutated_main }}" echo " Service: ${{ github.event_name == 'workflow_dispatch' && inputs.service || 'all' }}" echo " Trigger: ${TRIGGER}" echo " Bump type: ${VERSION_BUMP}" echo " Actor: @${{ gitea.actor }}" echo " Status: ${{ job.status }}" echo "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # Step 11: Failure โ†’ Reviewer Handoff # # On failure: DevOps (Architekt) analyses the log, # notifies Reviewer (Code-Fixer) with the exact error. # This output provides a ready-to-copy message. # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - name: ๐Ÿ”ด Failure โ€” Reviewer Handoff if: failure() run: | echo "" echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" echo "โ”‚ ๐Ÿ”ด DEPLOY FAILED โ€” Reviewer muss fixen โ”‚" echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" echo "โ”‚ โ”‚" echo "โ”‚ Version: v${{ steps.version.outputs.version }}" 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 "โ”‚ Rollback: Trigger 'Rollback to Previous Version' โ”‚" echo "โ”‚ workflow manuell in Gitea Actions. โ”‚" echo "โ”‚ โ”‚" echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜"