name: Build & Deploy KLZ Cables on: push: branches: - main tags: - 'v*' jobs: # ────────────────────────────────────────────────────────────────────────────── # JOB 1: Prepare & Determine Environment # ────────────────────────────────────────────────────────────────────────────── prepare: name: 🔍 Prepare Environment runs-on: docker outputs: target: ${{ steps.determine.outputs.target }} image_tag: ${{ steps.determine.outputs.image_tag }} env_file: ${{ steps.determine.outputs.env_file }} traefik_host: ${{ steps.determine.outputs.traefik_host }} is_prod: ${{ steps.determine.outputs.is_prod }} gotify_title: ${{ steps.determine.outputs.gotify_title }} gotify_priority: ${{ steps.determine.outputs.gotify_priority }} short_sha: ${{ steps.determine.outputs.short_sha }} commit_msg: ${{ steps.determine.outputs.commit_msg }} steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: 🔍 Environment & Version ermitteln id: determine run: | TAG="${{ github.ref_name }}" SHORT_SHA="${{ github.sha }}" SHORT_SHA="${SHORT_SHA:0:9}" COMMIT_MSG=$(git log -1 --pretty=%s || echo "No commit message available") if [[ "${{ github.ref_type }}" == "branch" && "$TAG" == "main" ]]; then TARGET="testing" IMAGE_TAG="main-${SHORT_SHA}" ENV_FILE=".env.testing" TRAEFIK_HOST="\`testing.klz-cables.com\`" IS_PROD="false" GOTIFY_TITLE="🧪 Testing-Deploy" GOTIFY_PRIORITY=4 elif [[ "${{ github.ref_type }}" == "tag" ]]; then if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then TARGET="production" IMAGE_TAG="$TAG" ENV_FILE=".env.prod" TRAEFIK_HOST="\`klz-cables.com\`, \`www.klz-cables.com\`" IS_PROD="true" GOTIFY_TITLE="🚀 Production-Release" GOTIFY_PRIORITY=6 elif [[ "$TAG" =~ -rc\. || "$TAG" =~ -beta\. || "$TAG" =~ -alpha\. ]]; then TARGET="staging" IMAGE_TAG="$TAG" ENV_FILE=".env.staging" TRAEFIK_HOST="\`staging.klz-cables.com\`" IS_PROD="false" GOTIFY_TITLE="🧪 Staging-Deploy (Pre-Release)" GOTIFY_PRIORITY=5 else TARGET="skip" GOTIFY_TITLE="❓ Unbekannter Tag" GOTIFY_PRIORITY=3 fi else TARGET="skip" fi echo "target=$TARGET" >> $GITHUB_OUTPUT echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT echo "env_file=$ENV_FILE" >> $GITHUB_OUTPUT echo "traefik_host=$TRAEFIK_HOST" >> $GITHUB_OUTPUT echo "is_prod=$IS_PROD" >> $GITHUB_OUTPUT echo "gotify_title=$GOTIFY_TITLE" >> $GITHUB_OUTPUT echo "gotify_priority=$GOTIFY_PRIORITY" >> $GITHUB_OUTPUT echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT echo "commit_msg=$COMMIT_MSG" >> $GITHUB_OUTPUT # ────────────────────────────────────────────────────────────────────────────── # JOB 2: Quality Assurance (Lint & Test) # ────────────────────────────────────────────────────────────────────────────── qa: name: 🧪 Quality Assurance needs: prepare if: needs.prepare.outputs.target != 'skip' runs-on: docker steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 cache: 'npm' - name: Install dependencies run: npm ci - name: 🔍 Lint & Typecheck run: | npm run lint npm run typecheck - name: 🧪 Test run: npm run test # ────────────────────────────────────────────────────────────────────────────── # JOB 3: Build & Push Docker Image # ────────────────────────────────────────────────────────────────────────────── build: name: 🏗️ Build & Push needs: [prepare, qa] runs-on: docker steps: - name: Checkout repository uses: actions/checkout@v4 - name: 🔐 Registry Login run: | echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin - name: 🏗️ Docker Image bauen & pushen env: IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }} TARGET: ${{ needs.prepare.outputs.target }} NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.target == 'production' && secrets.NEXT_PUBLIC_BASE_URL || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_BASE_URL || secrets.TESTING_NEXT_PUBLIC_BASE_URL || secrets.NEXT_PUBLIC_BASE_URL) }} NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ needs.prepare.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.TESTING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID) }} NEXT_PUBLIC_UMAMI_SCRIPT_URL: ${{ needs.prepare.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.TESTING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL) }} DIRECTUS_URL: ${{ needs.prepare.outputs.target == 'production' && 'https://cms.klz-cables.com' || (needs.prepare.outputs.target == 'staging' && 'https://cms-staging.klz-cables.com' || 'https://cms-testing.klz-cables.com') }} run: | echo "🏗️ Building → $TARGET / $IMAGE_TAG" 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" \ --build-arg DIRECTUS_URL="$DIRECTUS_URL" \ -t registry.infra.mintel.me/mintel/klz-cables.com:$IMAGE_TAG \ --push . # ────────────────────────────────────────────────────────────────────────────── # JOB 4: Deploy via SSH # ────────────────────────────────────────────────────────────────────────────── deploy: name: 🚀 Deploy needs: [prepare, build] runs-on: docker env: TARGET: ${{ needs.prepare.outputs.target }} IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }} ENV_FILE: ${{ needs.prepare.outputs.env_file }} TRAEFIK_HOST: ${{ needs.prepare.outputs.traefik_host }} NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.target == 'production' && secrets.NEXT_PUBLIC_BASE_URL || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_BASE_URL || secrets.TESTING_NEXT_PUBLIC_BASE_URL || secrets.NEXT_PUBLIC_BASE_URL) }} NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ needs.prepare.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.TESTING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID) }} NEXT_PUBLIC_UMAMI_SCRIPT_URL: ${{ needs.prepare.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.TESTING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL) }} SENTRY_DSN: ${{ needs.prepare.outputs.target == 'production' && secrets.SENTRY_DSN || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_SENTRY_DSN || secrets.TESTING_SENTRY_DSN || secrets.SENTRY_DSN) }} MAIL_HOST: ${{ secrets.MAIL_HOST }} MAIL_PORT: ${{ secrets.MAIL_PORT }} MAIL_USERNAME: ${{ secrets.MAIL_USERNAME }} MAIL_PASSWORD: ${{ secrets.MAIL_PASSWORD }} MAIL_FROM: ${{ secrets.MAIL_FROM }} MAIL_RECIPIENTS: ${{ secrets.MAIL_RECIPIENTS }} DIRECTUS_URL: ${{ needs.prepare.outputs.target == 'production' && 'https://cms.klz-cables.com' || (needs.prepare.outputs.target == 'staging' && 'https://cms-staging.klz-cables.com' || 'https://cms-testing.klz-cables.com') }} DIRECTUS_KEY: ${{ secrets.DIRECTUS_KEY }} DIRECTUS_SECRET: ${{ secrets.DIRECTUS_SECRET }} DIRECTUS_ADMIN_EMAIL: ${{ secrets.DIRECTUS_ADMIN_EMAIL }} DIRECTUS_ADMIN_PASSWORD: ${{ secrets.DIRECTUS_ADMIN_PASSWORD }} DIRECTUS_DB_NAME: ${{ secrets.DIRECTUS_DB_NAME || 'directus' }} DIRECTUS_DB_USER: ${{ secrets.DIRECTUS_DB_USER || 'directus' }} DIRECTUS_DB_PASSWORD: ${{ secrets.DIRECTUS_DB_PASSWORD }} DIRECTUS_API_TOKEN: ${{ secrets.DIRECTUS_API_TOKEN }} steps: - name: Checkout repository uses: actions/checkout@v4 - name: 🚀 Deploy to ${{ env.TARGET }} run: | echo "Deploying $TARGET → $IMAGE_TAG" mkdir -p ~/.ssh echo "${{ secrets.ALPHA_SSH_KEY }}" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 ssh-keyscan -H alpha.mintel.me >> ~/.ssh/known_hosts 2>/dev/null cat > /tmp/klz-cables.env << EOF # Generated by CI - $TARGET - $(date -u) 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 # Directus DIRECTUS_URL=$DIRECTUS_URL DIRECTUS_KEY=$DIRECTUS_KEY DIRECTUS_SECRET=$DIRECTUS_SECRET DIRECTUS_ADMIN_EMAIL=$DIRECTUS_ADMIN_EMAIL DIRECTUS_ADMIN_PASSWORD=$DIRECTUS_ADMIN_PASSWORD DIRECTUS_DB_NAME=$DIRECTUS_DB_NAME DIRECTUS_DB_USER=$DIRECTUS_DB_USER DIRECTUS_DB_PASSWORD=$DIRECTUS_DB_PASSWORD DIRECTUS_API_TOKEN=$DIRECTUS_API_TOKEN IMAGE_TAG=$IMAGE_TAG TRAEFIK_HOST=$TRAEFIK_HOST ENV_FILE=$ENV_FILE EOF scp -o StrictHostKeyChecking=accept-new /tmp/klz-cables.env root@alpha.mintel.me:/home/deploy/sites/klz-cables.com/$ENV_FILE scp -o StrictHostKeyChecking=accept-new docker-compose.yml root@alpha.mintel.me:/home/deploy/sites/klz-cables.com/docker-compose.yml ssh -o StrictHostKeyChecking=accept-new root@alpha.mintel.me IMAGE_TAG="$IMAGE_TAG" ENV_FILE="$ENV_FILE" TRAEFIK_HOST="$TRAEFIK_HOST" bash << 'EOF' set -e cd /home/deploy/sites/klz-cables.com 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 image: $IMAGE_TAG" docker compose --env-file "$ENV_FILE" pull echo "→ Starting containers..." docker compose --env-file "$ENV_FILE" up -d docker system prune -f --filter "until=168h" echo "→ Waiting 15s for warmup..." sleep 15 echo "→ Container status:" docker compose --env-file "$ENV_FILE" ps if ! docker compose --env-file "$ENV_FILE" ps | grep -q "Up"; then echo "❌ Fehler: Container nicht Up!" docker compose --env-file "$ENV_FILE" logs --tail=150 exit 1 fi echo "✅ Deployment erfolgreich!" EOF rm -f /tmp/klz-cables.env - name: 🎨 Branding Setup if: success() env: DIRECTUS_URL: ${{ env.DIRECTUS_URL }} run: | echo "🎨 Applying KLZ Branding to $TARGET..." # Load the locally generated env but use the server URL NEXT_PUBLIC_BASE_URL="${{ env.NEXT_PUBLIC_BASE_URL }}" \ DIRECTUS_ADMIN_EMAIL="${{ secrets.DIRECTUS_ADMIN_EMAIL }}" \ DIRECTUS_ADMIN_PASSWORD="${{ secrets.DIRECTUS_ADMIN_PASSWORD }}" \ npx tsx scripts/setup-directus-branding.ts # ────────────────────────────────────────────────────────────────────────────── # JOB 5: Notifications # ────────────────────────────────────────────────────────────────────────────── notifications: name: 🔔 Notifications needs: [prepare, qa, build, deploy] if: always() runs-on: docker steps: - name: 📊 Deployment Summary run: | echo "┌──────────────────────────────┐" echo "│ Deployment Summary │" echo "├──────────────────────────────┤" echo "│ Status: ${{ needs.deploy.result }} │" echo "│ Umgebung: ${{ needs.prepare.outputs.target || 'skipped' }} │" echo "│ Version: ${{ needs.prepare.outputs.image_tag }} │" echo "│ Commit: ${{ needs.prepare.outputs.short_sha }} │" echo "│ Message: ${{ needs.prepare.outputs.commit_msg }} │" echo "└──────────────────────────────┘" - name: 🔔 Gotify - Success if: needs.deploy.result == 'success' run: | curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \ -F "title=${{ needs.prepare.outputs.gotify_title }}" \ -F "message=Erfolgreich deployt auf **${{ needs.prepare.outputs.target }}**\n\nVersion: **${{ needs.prepare.outputs.image_tag }}**\nCommit: ${{ needs.prepare.outputs.short_sha }} (${{ needs.prepare.outputs.commit_msg }})\nVon: ${{ github.actor }}\nRun: ${{ github.run_id }}" \ -F "priority=${{ needs.prepare.outputs.gotify_priority }}" || true - name: 🔔 Gotify - Failure if: needs.deploy.result == 'failure' || needs.build.result == 'failure' || needs.qa.result == 'failure' run: | curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \ -F "title=❌ Deployment FEHLGESCHLAGEN – ${{ needs.prepare.outputs.target || 'unknown' }}" \ -F "message=**Fehler beim Deploy auf ${{ needs.prepare.outputs.target }}**\n\nVersion: ${{ needs.prepare.outputs.image_tag || '?' }}\nCommit: ${{ needs.prepare.outputs.short_sha || '?' }}\nVon: ${{ github.actor }}\nRun: ${{ github.run_id }}\n\nBitte Logs prüfen!" \ -F "priority=8" || true