name: Build & Deploy on: push: branches: [main, staging] jobs: build-and-deploy: runs-on: docker steps: # ═══════════════════════════════════════════════════════════════════════════════ # LOGGING: Workflow Start - Full Transparency # ═══════════════════════════════════════════════════════════════════════════════ - name: 📋 Log Workflow Start run: | echo "🚀 Starting deployment for ${{ github.repository }} (${{ github.ref }})" echo " • Branch: ${{ github.ref_name }}" echo " • Commit: ${{ github.sha }}" echo " • Timestamp: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" - name: Checkout repository uses: actions/checkout@v4 # ═══════════════════════════════════════════════════════════════════════════════ # LOGGING: Registry Login Phase # ═══════════════════════════════════════════════════════════════════════════════ - name: 🔐 Login to private registry run: | echo "🔐 Authenticating with registry.infra.mintel.me..." echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin # ═══════════════════════════════════════════════════════════════════════════════ # LOGGING: Build Phase # ═══════════════════════════════════════════════════════════════════════════════ - name: 🏗️ Build Docker image env: NEXT_PUBLIC_BASE_URL: ${{ github.ref_name == 'main' && secrets.NEXT_PUBLIC_BASE_URL || (secrets.STAGING_NEXT_PUBLIC_BASE_URL || secrets.NEXT_PUBLIC_BASE_URL) }} NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ github.ref_name == 'main' && secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID || (secrets.STAGING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID) }} NEXT_PUBLIC_UMAMI_SCRIPT_URL: ${{ github.ref_name == 'main' && secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL || (secrets.STAGING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL) }} run: | echo "🏗️ Building Docker image (linux/arm64) for branch ${{ github.ref_name }}..." docker buildx build \ --pull \ --platform linux/arm64 \ --build-arg NEXT_PUBLIC_BASE_URL="$NEXT_PUBLIC_BASE_URL" \ --build-arg NEXT_PUBLIC_UMAMI_WEBSITE_ID="$NEXT_PUBLIC_UMAMI_WEBSITE_ID" \ --build-arg NEXT_PUBLIC_UMAMI_SCRIPT_URL="$NEXT_PUBLIC_UMAMI_SCRIPT_URL" \ -t registry.infra.mintel.me/${{ github.repository }}:${{ github.sha }} \ -t registry.infra.mintel.me/${{ github.repository }}:latest \ --push . # ═══════════════════════════════════════════════════════════════════════════════ # LOGGING: Deployment Phase # ═══════════════════════════════════════════════════════════════════════════════ - name: 🚀 Deploy to server env: NEXT_PUBLIC_BASE_URL: ${{ github.ref_name == 'main' && secrets.NEXT_PUBLIC_BASE_URL || (secrets.STAGING_NEXT_PUBLIC_BASE_URL || secrets.NEXT_PUBLIC_BASE_URL) }} NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ github.ref_name == 'main' && secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID || (secrets.STAGING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID) }} NEXT_PUBLIC_UMAMI_SCRIPT_URL: ${{ github.ref_name == 'main' && secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL || (secrets.STAGING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL) }} SENTRY_DSN: ${{ github.ref_name == 'main' && secrets.SENTRY_DSN || (secrets.STAGING_SENTRY_DSN || secrets.SENTRY_DSN) }} MAIL_HOST: ${{ github.ref_name == 'main' && secrets.MAIL_HOST || (secrets.STAGING_MAIL_HOST || secrets.MAIL_HOST) }} MAIL_PORT: ${{ github.ref_name == 'main' && secrets.MAIL_PORT || (secrets.STAGING_MAIL_PORT || secrets.MAIL_PORT) }} MAIL_USERNAME: ${{ github.ref_name == 'main' && secrets.MAIL_USERNAME || (secrets.STAGING_MAIL_USERNAME || secrets.MAIL_USERNAME) }} MAIL_PASSWORD: ${{ github.ref_name == 'main' && secrets.MAIL_PASSWORD || (secrets.STAGING_MAIL_PASSWORD || secrets.MAIL_PASSWORD) }} MAIL_FROM: ${{ github.ref_name == 'main' && secrets.MAIL_FROM || (secrets.STAGING_MAIL_FROM || secrets.MAIL_FROM) }} MAIL_RECIPIENTS: ${{ github.ref_name == 'main' && secrets.MAIL_RECIPIENTS || (secrets.STAGING_MAIL_RECIPIENTS || secrets.MAIL_RECIPIENTS) }} run: | BRANCH=${{ github.ref_name }} # Derive domain from NEXT_PUBLIC_BASE_URL (strip https:// and trailing slash) DOMAIN=$(echo "$NEXT_PUBLIC_BASE_URL" | sed -E 's|https?://||' | sed -E 's|/.*||') if [ "$BRANCH" = "main" ]; then ENV_FILE=.env.prod # For production, we want both root and www TRAEFIK_HOST="\`$DOMAIN\`, \`www.$DOMAIN\`" else ENV_FILE=.env.staging TRAEFIK_HOST="\`$DOMAIN\`" fi echo "🚀 Deploying branch $BRANCH to $ENV_FILE..." echo "🌐 Domain: $DOMAIN" # Setup SSH mkdir -p ~/.ssh echo "${{ secrets.SSH_KEY }}" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts 2>/dev/null # Create .env file content cat > /tmp/app.env << EOF # ============================================================================ # Environment Configuration ($BRANCH) # ============================================================================ # Auto-generated by CI/CD workflow # ============================================================================ NODE_ENV=production NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL NEXT_PUBLIC_UMAMI_WEBSITE_ID=$NEXT_PUBLIC_UMAMI_WEBSITE_ID NEXT_PUBLIC_UMAMI_SCRIPT_URL=$NEXT_PUBLIC_UMAMI_SCRIPT_URL SENTRY_DSN=$SENTRY_DSN MAIL_HOST=$MAIL_HOST MAIL_PORT=$MAIL_PORT MAIL_USERNAME=$MAIL_USERNAME MAIL_PASSWORD=$MAIL_PASSWORD MAIL_FROM=$MAIL_FROM MAIL_RECIPIENTS=$MAIL_RECIPIENTS # Deployment variables for docker-compose IMAGE_TAG=${{ github.sha }} TRAEFIK_HOST=$TRAEFIK_HOST ENV_FILE=$ENV_FILE EOF APP_DIR="/home/deploy/sites/${{ github.event.repository.name }}" ssh -o StrictHostKeyChecking=accept-new root@${{ secrets.SSH_HOST }} "mkdir -p $APP_DIR" scp -o StrictHostKeyChecking=accept-new /tmp/app.env root@${{ secrets.SSH_HOST }}:$APP_DIR/$ENV_FILE scp -o StrictHostKeyChecking=accept-new docker-compose.yml root@${{ secrets.SSH_HOST }}:$APP_DIR/docker-compose.yml ssh -o StrictHostKeyChecking=accept-new root@${{ secrets.SSH_HOST }} bash << EOF set -e cd $APP_DIR chmod 600 $ENV_FILE chown deploy:deploy $ENV_FILE echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin echo "📥 Pulling images..." IMAGE_TAG=${{ github.sha }} ENV_FILE=$ENV_FILE TRAEFIK_HOST="$TRAEFIK_HOST" docker compose --env-file $ENV_FILE pull echo "🚀 Starting containers..." IMAGE_TAG=${{ github.sha }} ENV_FILE=$ENV_FILE TRAEFIK_HOST="$TRAEFIK_HOST" docker compose --env-file $ENV_FILE up -d echo "🧹 Cleaning up old images..." docker system prune -f echo "⏳ Giving the app a few seconds to warm up..." sleep 10 echo "🔍 Checking container status..." docker compose --env-file $ENV_FILE ps if ! docker compose --env-file $ENV_FILE ps | grep -q "Up"; then echo "❌ Container failed to start" docker compose --env-file $ENV_FILE logs --tail=100 exit 1 fi echo "✅ Deployment complete!" EOF rm -f /tmp/app.env # ═══════════════════════════════════════════════════════════════════════════════ # LOGGING: Workflow Summary # ═══════════════════════════════════════════════════════════════════════════════ - name: 📊 Workflow Summary if: always() run: | echo "📊 Status: ${{ job.status }}" echo "🎯 Target: ${{ secrets.SSH_HOST }}" echo "🌿 Branch: ${{ github.ref_name }}" # ═══════════════════════════════════════════════════════════════════════════════ # NOTIFICATION: Gotify # ═══════════════════════════════════════════════════════════════════════════════ - name: 🔔 Gotify Notification (Success) if: success() run: | echo "Sending success notification to Gotify..." curl -k -s -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \ -F "title=✅ Deployment Success: ${{ github.repository }}" \ -F "message=The deployment of ${{ github.repository }} (branch: ${{ github.ref_name }}) was successful. Commit: ${{ github.sha }} Actor: ${{ github.actor }} Run ID: ${{ github.run_id }}" \ -F "priority=5" - name: 🔔 Gotify Notification (Failure) if: failure() run: | echo "Sending failure notification to Gotify..." curl -k -s -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \ -F "title=❌ Deployment Failed: ${{ github.repository }}" \ -F "message=The deployment of ${{ github.repository }} (branch: ${{ github.ref_name }}) failed! Commit: ${{ github.sha }} Actor: ${{ github.actor }} Run ID: ${{ github.run_id }} Please check the logs for details." \ -F "priority=8"