CI/CD Gitea v1.0
Some checks failed
Deploy to Staging / Build Images (push) Failing after 7s
Deploy to Staging / Deploy to Staging (push) Has been skipped
Deploy to Staging / Verify Staging (push) Has been skipped
Deploy to Staging / Notify Staging Ready (push) Has been skipped
Deploy to Staging / Notify Staging Failure (push) Failing after 6s
Some checks failed
Deploy to Staging / Build Images (push) Failing after 7s
Deploy to Staging / Deploy to Staging (push) Has been skipped
Deploy to Staging / Verify Staging (push) Has been skipped
Deploy to Staging / Notify Staging Ready (push) Has been skipped
Deploy to Staging / Notify Staging Failure (push) Failing after 6s
This commit is contained in:
260
.gitea/workflows/production.yaml
Normal file
260
.gitea/workflows/production.yaml
Normal file
@@ -0,0 +1,260 @@
|
||||
# MotoVaultPro Production Deployment Workflow
|
||||
# Manual trigger only - run after verifying staging
|
||||
# Blue-green deployment with auto-rollback
|
||||
|
||||
name: Deploy to Production
|
||||
run-name: Production Deploy - ${{ inputs.image_tag || 'latest' }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
image_tag:
|
||||
description: 'Image tag to deploy (defaults to latest)'
|
||||
required: false
|
||||
default: 'latest'
|
||||
|
||||
env:
|
||||
REGISTRY: git.motovaultpro.com
|
||||
DEPLOY_PATH: /opt/motovaultpro
|
||||
COMPOSE_FILE: docker-compose.yml
|
||||
COMPOSE_BLUE_GREEN: docker-compose.blue-green.yml
|
||||
HEALTH_CHECK_TIMEOUT: "60"
|
||||
|
||||
jobs:
|
||||
# ============================================
|
||||
# VALIDATE - Determine target stack
|
||||
# ============================================
|
||||
validate:
|
||||
name: Validate Prerequisites
|
||||
runs-on: mvp-prod
|
||||
outputs:
|
||||
target_stack: ${{ steps.determine-stack.outputs.target_stack }}
|
||||
backend_image: ${{ steps.set-images.outputs.backend_image }}
|
||||
frontend_image: ${{ steps.set-images.outputs.frontend_image }}
|
||||
steps:
|
||||
- name: Check Docker availability
|
||||
run: |
|
||||
docker info > /dev/null 2>&1 || (echo "ERROR - Docker not accessible" && exit 1)
|
||||
docker compose version > /dev/null 2>&1 || (echo "ERROR - Docker Compose not available" && exit 1)
|
||||
|
||||
- name: Check deployment path
|
||||
run: test -d "$DEPLOY_PATH" || (echo "ERROR - DEPLOY_PATH not found" && exit 1)
|
||||
|
||||
- name: Login to Gitea Container Registry
|
||||
run: |
|
||||
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login -u "${{ secrets.REGISTRY_USER }}" --password-stdin "$REGISTRY"
|
||||
|
||||
- name: Set image tags
|
||||
id: set-images
|
||||
run: |
|
||||
TAG="${{ inputs.image_tag }}"
|
||||
echo "backend_image=$REGISTRY/egullickson/backend:$TAG" >> $GITHUB_OUTPUT
|
||||
echo "frontend_image=$REGISTRY/egullickson/frontend:$TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Determine target stack
|
||||
id: determine-stack
|
||||
run: |
|
||||
STATE_FILE="$DEPLOY_PATH/config/deployment/state.json"
|
||||
if [ -f "$STATE_FILE" ] && command -v jq &> /dev/null; then
|
||||
ACTIVE_STACK=$(jq -r '.active_stack // "blue"' "$STATE_FILE")
|
||||
if [ "$ACTIVE_STACK" = "blue" ]; then
|
||||
echo "target_stack=green" >> $GITHUB_OUTPUT
|
||||
echo "Deploying to GREEN stack (BLUE is currently active)"
|
||||
else
|
||||
echo "target_stack=blue" >> $GITHUB_OUTPUT
|
||||
echo "Deploying to BLUE stack (GREEN is currently active)"
|
||||
fi
|
||||
else
|
||||
echo "target_stack=green" >> $GITHUB_OUTPUT
|
||||
echo "No state file found, defaulting to GREEN stack"
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# DEPLOY PROD - Blue-green deployment
|
||||
# ============================================
|
||||
deploy-prod:
|
||||
name: Deploy to Production
|
||||
runs-on: mvp-prod
|
||||
needs: validate
|
||||
env:
|
||||
TARGET_STACK: ${{ needs.validate.outputs.target_stack }}
|
||||
BACKEND_IMAGE: ${{ needs.validate.outputs.backend_image }}
|
||||
FRONTEND_IMAGE: ${{ needs.validate.outputs.frontend_image }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to registry
|
||||
run: |
|
||||
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login -u "${{ secrets.REGISTRY_USER }}" --password-stdin "$REGISTRY"
|
||||
|
||||
- name: Inject secrets
|
||||
run: |
|
||||
cd "$DEPLOY_PATH"
|
||||
chmod +x scripts/inject-secrets.sh
|
||||
./scripts/inject-secrets.sh
|
||||
env:
|
||||
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
|
||||
AUTH0_CLIENT_SECRET: ${{ secrets.AUTH0_CLIENT_SECRET }}
|
||||
AUTH0_MANAGEMENT_CLIENT_ID: ${{ secrets.AUTH0_MANAGEMENT_CLIENT_ID }}
|
||||
AUTH0_MANAGEMENT_CLIENT_SECRET: ${{ secrets.AUTH0_MANAGEMENT_CLIENT_SECRET }}
|
||||
GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
|
||||
GOOGLE_MAPS_MAP_ID: ${{ secrets.GOOGLE_MAPS_MAP_ID }}
|
||||
CF_DNS_API_TOKEN: ${{ secrets.CF_DNS_API_TOKEN }}
|
||||
RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }}
|
||||
|
||||
- name: Initialize data directories
|
||||
run: |
|
||||
cd "$DEPLOY_PATH"
|
||||
sudo mkdir -p data/backups data/documents
|
||||
sudo chown -R 1001:1001 data/backups data/documents
|
||||
sudo chmod 755 data/backups data/documents
|
||||
|
||||
- name: Pull new images
|
||||
run: |
|
||||
docker pull $BACKEND_IMAGE
|
||||
docker pull $FRONTEND_IMAGE
|
||||
|
||||
- name: Start target stack
|
||||
run: |
|
||||
cd "$DEPLOY_PATH"
|
||||
export BACKEND_IMAGE=$BACKEND_IMAGE
|
||||
export FRONTEND_IMAGE=$FRONTEND_IMAGE
|
||||
docker compose -f $COMPOSE_FILE -f $COMPOSE_BLUE_GREEN up -d \
|
||||
mvp-frontend-$TARGET_STACK mvp-backend-$TARGET_STACK
|
||||
|
||||
- name: Wait for stack initialization
|
||||
run: sleep 10
|
||||
|
||||
- name: Run health check
|
||||
run: |
|
||||
cd "$DEPLOY_PATH"
|
||||
chmod +x scripts/ci/health-check.sh
|
||||
./scripts/ci/health-check.sh $TARGET_STACK $HEALTH_CHECK_TIMEOUT
|
||||
|
||||
- name: Switch traffic
|
||||
run: |
|
||||
cd "$DEPLOY_PATH"
|
||||
chmod +x scripts/ci/switch-traffic.sh
|
||||
./scripts/ci/switch-traffic.sh $TARGET_STACK instant
|
||||
|
||||
- name: Update deployment state
|
||||
run: |
|
||||
cd "$DEPLOY_PATH"
|
||||
STATE_FILE="config/deployment/state.json"
|
||||
if [ -f "$STATE_FILE" ] && command -v jq &> /dev/null; then
|
||||
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
jq --arg commit "${{ inputs.image_tag }}" \
|
||||
--arg ts "$TIMESTAMP" \
|
||||
'.last_deployment = $ts | .last_deployment_commit = $commit | .last_deployment_status = "success" | .rollback_available = true' \
|
||||
"$STATE_FILE" > "${STATE_FILE}.tmp" && mv "${STATE_FILE}.tmp" "$STATE_FILE"
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# VERIFY PROD - External health check
|
||||
# ============================================
|
||||
verify-prod:
|
||||
name: Verify Production
|
||||
runs-on: mvp-prod
|
||||
needs: [validate, deploy-prod]
|
||||
env:
|
||||
TARGET_STACK: ${{ needs.validate.outputs.target_stack }}
|
||||
steps:
|
||||
- name: Wait for routing propagation
|
||||
run: sleep 5
|
||||
|
||||
- name: External health check
|
||||
run: |
|
||||
for i in 1 2 3 4 5 6; do
|
||||
if curl -sf https://motovaultpro.com/api/health > /dev/null 2>&1; then
|
||||
echo "OK: Production external health check passed"
|
||||
exit 0
|
||||
fi
|
||||
if [ $i -eq 6 ]; then
|
||||
echo "ERROR: Production external health check failed after 6 attempts"
|
||||
exit 1
|
||||
fi
|
||||
echo "Attempt $i/6: Waiting 10s..."
|
||||
sleep 10
|
||||
done
|
||||
|
||||
- name: Verify container status
|
||||
run: |
|
||||
for service in mvp-frontend-$TARGET_STACK mvp-backend-$TARGET_STACK; do
|
||||
status=$(docker inspect --format='{{.State.Status}}' $service 2>/dev/null || echo "not found")
|
||||
health=$(docker inspect --format='{{.State.Health.Status}}' $service 2>/dev/null || echo "unknown")
|
||||
if [ "$status" != "running" ] || [ "$health" != "healthy" ]; then
|
||||
echo "ERROR: $service is not healthy (status: $status, health: $health)"
|
||||
docker logs $service --tail 50 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
echo "OK: $service is running and healthy"
|
||||
done
|
||||
|
||||
# ============================================
|
||||
# ROLLBACK - Auto-rollback on failure
|
||||
# ============================================
|
||||
rollback:
|
||||
name: Auto Rollback
|
||||
runs-on: mvp-prod
|
||||
needs: [validate, deploy-prod, verify-prod]
|
||||
if: failure()
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Execute rollback
|
||||
run: |
|
||||
cd "$DEPLOY_PATH"
|
||||
chmod +x scripts/ci/auto-rollback.sh
|
||||
./scripts/ci/auto-rollback.sh "Production verification failed - automatic rollback"
|
||||
|
||||
- name: Update state
|
||||
run: |
|
||||
cd "$DEPLOY_PATH"
|
||||
STATE_FILE="config/deployment/state.json"
|
||||
if [ -f "$STATE_FILE" ] && command -v jq &> /dev/null; then
|
||||
jq '.last_deployment_status = "rolled_back"' "$STATE_FILE" > "${STATE_FILE}.tmp" && mv "${STATE_FILE}.tmp" "$STATE_FILE"
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# NOTIFY SUCCESS
|
||||
# ============================================
|
||||
notify-success:
|
||||
name: Notify Success
|
||||
runs-on: mvp-prod
|
||||
needs: [validate, verify-prod]
|
||||
if: success()
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Send success notification
|
||||
run: |
|
||||
cd "$DEPLOY_PATH"
|
||||
chmod +x scripts/ci/notify.sh
|
||||
./scripts/ci/notify.sh success "Production deployment successful - ${{ inputs.image_tag }} is now live" ${{ inputs.image_tag }}
|
||||
env:
|
||||
DEPLOY_NOTIFY_EMAIL: ${{ vars.DEPLOY_NOTIFY_EMAIL }}
|
||||
RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }}
|
||||
|
||||
# ============================================
|
||||
# NOTIFY FAILURE
|
||||
# ============================================
|
||||
notify-failure:
|
||||
name: Notify Failure
|
||||
runs-on: mvp-prod
|
||||
needs: [validate, deploy-prod, verify-prod, rollback]
|
||||
if: failure()
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Send failure notification
|
||||
run: |
|
||||
cd "$DEPLOY_PATH"
|
||||
chmod +x scripts/ci/notify.sh
|
||||
./scripts/ci/notify.sh failure "Production deployment failed for ${{ inputs.image_tag }}" ${{ inputs.image_tag }}
|
||||
env:
|
||||
DEPLOY_NOTIFY_EMAIL: ${{ vars.DEPLOY_NOTIFY_EMAIL }}
|
||||
RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }}
|
||||
Reference in New Issue
Block a user