name: Build & Deploy KLZ Cables on: push: branches: - main tags: - 'v*' workflow_dispatch: inputs: skip_long_checks: description: 'Skip tests? (true/false)' required: false default: 'false' concurrency: group: ${{ github.workflow }}-${{ (github.ref_type == 'tag' && !contains(github.ref_name, '-')) && 'prod' || (github.ref_type == 'tag' && 'staging' || 'testing') }} cancel-in-progress: true 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 }} traefik_host_rule: ${{ steps.determine.outputs.traefik_host_rule }} primary_host: ${{ steps.determine.outputs.primary_host }} next_public_base_url: ${{ steps.determine.outputs.next_public_base_url }} directus_url: ${{ steps.determine.outputs.directus_url }} directus_host: ${{ steps.determine.outputs.directus_host }} project_name: ${{ steps.determine.outputs.project_name }} 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 }} container: image: catthehacker/ubuntu:act-latest steps: - name: 🧹 Maintenance (High Density Cleanup) shell: bash run: | echo "Purging old build layers and dangling images..." docker image prune -f docker builder prune -f --filter "until=6h" - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 2 - name: 🔍 Environment & Version ermitteln id: determine shell: bash run: | TAG="${{ github.ref_name }}" SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-9) IMAGE_TAG="sha-${SHORT_SHA}" COMMIT_MSG=$(git log -1 --pretty=%s || echo "No commit message available") if [[ "${{ github.ref_type }}" == "branch" && "$TAG" == "main" ]]; then if [[ "$COMMIT_MSG" =~ ^chore: ]]; then TARGET="skip" GOTIFY_TITLE="ℹ️ Skip Deploy (Chore)" GOTIFY_PRIORITY=2 else TARGET="testing" IMAGE_TAG="main-${SHORT_SHA}" ENV_FILE=".env.testing" TRAEFIK_HOST="testing.klz-cables.com" NEXT_PUBLIC_BASE_URL="https://testing.klz-cables.com" DIRECTUS_URL="https://cms.testing.klz-cables.com" DIRECTUS_HOST="cms.testing.klz-cables.com" PROJECT_NAME="klz-cables-testing" IS_PROD="false" GOTIFY_TITLE="🧪 Testing-Deploy" GOTIFY_PRIORITY=4 fi 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" NEXT_PUBLIC_BASE_URL="https://klz-cables.com" DIRECTUS_URL="https://cms.klz-cables.com" DIRECTUS_HOST="cms.klz-cables.com" PROJECT_NAME="klz-cables-prod" 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" NEXT_PUBLIC_BASE_URL="https://staging.klz-cables.com" DIRECTUS_URL="https://cms.staging.klz-cables.com" DIRECTUS_HOST="cms.staging.klz-cables.com" PROJECT_NAME="klz-cables-staging" 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 if [[ "$TRAEFIK_HOST" == *","* ]]; then # Multi-domain: Host(`a.com`) || Host(`b.com`) TRAEFIK_HOST_RULE=$(echo "$TRAEFIK_HOST" | sed 's/,/ /g' | awk '{for(i=1;i<=NF;i++) printf "Host(`%s`)%s", $i, (i==NF?"":" || ")}') PRIMARY_HOST=$(echo "$TRAEFIK_HOST" | cut -d',' -f1 | sed 's/ //g') else # Single domain: Host(`domain.com`) TRAEFIK_HOST_RULE="Host(\`$TRAEFIK_HOST\`)" PRIMARY_HOST="$TRAEFIK_HOST" fi { echo "target=$TARGET" echo "image_tag=$IMAGE_TAG" echo "env_file=$ENV_FILE" echo "traefik_host=$TRAEFIK_HOST" echo "traefik_host_rule=$TRAEFIK_HOST_RULE" echo "primary_host=$PRIMARY_HOST" echo "next_public_base_url=$NEXT_PUBLIC_BASE_URL" echo "directus_url=$DIRECTUS_URL" echo "directus_host=$DIRECTUS_HOST" echo "project_name=$PROJECT_NAME" echo "is_prod=$IS_PROD" echo "gotify_title=$GOTIFY_TITLE" echo "gotify_priority=$GOTIFY_PRIORITY" echo "short_sha=$SHORT_SHA" 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 container: image: catthehacker/ubuntu:act-latest steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 1 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 - name: Install dependencies run: npm ci --legacy-peer-deps - name: 🧪 Run Checks in Parallel if: github.event.inputs.skip_long_checks != 'true' run: | npm run lint & LINT_PID=$! npm run typecheck & TYPE_PID=$! npm run test & TEST_PID=$! # Wait for all and fail if any fail wait $LINT_PID || exit 1 wait $TYPE_PID || exit 1 wait $TEST_PID || exit 1 # ────────────────────────────────────────────────────────────────────────────── # JOB 3: Build & Push Docker Image # ────────────────────────────────────────────────────────────────────────────── build-app: name: 🏗️ Build App needs: prepare if: ${{ needs.prepare.outputs.target != 'skip' }} runs-on: docker container: image: catthehacker/ubuntu:act-latest steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 1 - name: 🐳 Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: 🔐 Registry Login run: | echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin - name: 🏗️ App bauen & pushen env: IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }} TARGET: ${{ needs.prepare.outputs.target }} NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_base_url }} DIRECTUS_URL: ${{ needs.prepare.outputs.directus_url }} run: | docker buildx build \ --pull \ --platform linux/arm64 \ --build-arg NEXT_PUBLIC_BASE_URL="$NEXT_PUBLIC_BASE_URL" \ --build-arg NEXT_PUBLIC_TARGET="$TARGET" \ --build-arg DIRECTUS_URL="$DIRECTUS_URL" \ -t registry.infra.mintel.me/mintel/klz-cables.com:$IMAGE_TAG \ --cache-from type=registry,ref=registry.infra.mintel.me/mintel/klz-cables.com:buildcache \ --cache-to type=registry,ref=registry.infra.mintel.me/mintel/klz-cables.com:buildcache,mode=max \ --push . # ────────────────────────────────────────────────────────────────────────────── # JOB 4: Deploy via SSH # ────────────────────────────────────────────────────────────────────────────── deploy: name: 🚀 Deploy needs: [prepare, build-app, qa] if: ${{ needs.prepare.outputs.target != 'skip' }} runs-on: docker container: image: catthehacker/ubuntu:act-latest 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.primary_host }} NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_base_url }} 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) }} UMAMI_API_ENDPOINT: ${{ 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: ${{ secrets.SENTRY_DSN || vars.SENTRY_DSN }} MAIL_HOST: ${{ secrets.MAIL_HOST || vars.MAIL_HOST || (needs.prepare.outputs.target == 'production' && (secrets.MAIL_HOST || vars.MAIL_HOST) || (needs.prepare.outputs.target == 'staging' && (secrets.STAGING_MAIL_HOST || vars.STAGING_MAIL_HOST) || (secrets.TESTING_MAIL_HOST || vars.TESTING_MAIL_HOST) || (secrets.MAIL_HOST || vars.MAIL_HOST))) }} MAIL_PORT: ${{ secrets.MAIL_PORT || vars.MAIL_PORT || (needs.prepare.outputs.target == 'production' && (secrets.MAIL_PORT || vars.MAIL_PORT) || (needs.prepare.outputs.target == 'staging' && (secrets.STAGING_MAIL_PORT || vars.STAGING_MAIL_PORT) || (secrets.TESTING_MAIL_PORT || vars.TESTING_MAIL_PORT) || (secrets.MAIL_PORT || vars.MAIL_PORT))) }} MAIL_USERNAME: ${{ secrets.MAIL_USERNAME || vars.MAIL_USERNAME || (needs.prepare.outputs.target == 'production' && (secrets.MAIL_USERNAME || vars.MAIL_USERNAME) || (needs.prepare.outputs.target == 'staging' && (secrets.STAGING_MAIL_USERNAME || vars.STAGING_MAIL_USERNAME) || (secrets.TESTING_MAIL_USERNAME || vars.TESTING_MAIL_USERNAME) || (secrets.MAIL_USERNAME || vars.MAIL_USERNAME))) }} MAIL_PASSWORD: ${{ secrets.MAIL_PASSWORD || (needs.prepare.outputs.target == 'production' && secrets.MAIL_PASSWORD || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_MAIL_PASSWORD || secrets.TESTING_MAIL_PASSWORD || secrets.MAIL_PASSWORD)) }} MAIL_FROM: ${{ secrets.MAIL_FROM || vars.MAIL_FROM || (needs.prepare.outputs.target == 'production' && (secrets.MAIL_FROM || vars.MAIL_FROM) || (needs.prepare.outputs.target == 'staging' && (secrets.STAGING_MAIL_FROM || vars.STAGING_MAIL_FROM) || (secrets.TESTING_MAIL_FROM || vars.TESTING_MAIL_FROM) || (secrets.MAIL_FROM || vars.MAIL_FROM))) }} MAIL_RECIPIENTS: ${{ secrets.MAIL_RECIPIENTS || vars.MAIL_RECIPIENTS || (needs.prepare.outputs.target == 'production' && (secrets.MAIL_RECIPIENTS || vars.MAIL_RECIPIENTS) || (needs.prepare.outputs.target == 'staging' && (secrets.STAGING_MAIL_RECIPIENTS || vars.STAGING_MAIL_RECIPIENTS) || (secrets.TESTING_MAIL_RECIPIENTS || vars.TESTING_MAIL_RECIPIENTS) || (secrets.MAIL_RECIPIENTS || vars.MAIL_RECIPIENTS))) }} DIRECTUS_URL: ${{ needs.prepare.outputs.directus_url }} DIRECTUS_HOST: ${{ needs.prepare.outputs.directus_host }} PROJECT_NAME: ${{ needs.prepare.outputs.project_name }} DIRECTUS_KEY: ${{ secrets.DIRECTUS_KEY || (needs.prepare.outputs.target == 'production' && secrets.DIRECTUS_KEY || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_DIRECTUS_KEY || secrets.TESTING_DIRECTUS_KEY || secrets.DIRECTUS_KEY)) }} DIRECTUS_SECRET: ${{ secrets.DIRECTUS_SECRET || (needs.prepare.outputs.target == 'production' && secrets.DIRECTUS_SECRET || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_DIRECTUS_SECRET || secrets.TESTING_DIRECTUS_SECRET || secrets.DIRECTUS_SECRET)) }} DIRECTUS_ADMIN_EMAIL: ${{ secrets.DIRECTUS_ADMIN_EMAIL || (needs.prepare.outputs.target == 'production' && secrets.DIRECTUS_ADMIN_EMAIL || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_DIRECTUS_ADMIN_EMAIL || secrets.TESTING_DIRECTUS_ADMIN_EMAIL || secrets.DIRECTUS_ADMIN_EMAIL)) }} DIRECTUS_ADMIN_PASSWORD: ${{ secrets.DIRECTUS_ADMIN_PASSWORD || (needs.prepare.outputs.target == 'production' && secrets.DIRECTUS_ADMIN_PASSWORD || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_DIRECTUS_ADMIN_PASSWORD || secrets.TESTING_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 || (needs.prepare.outputs.target == 'production' && secrets.DIRECTUS_DB_PASSWORD || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_DIRECTUS_DB_PASSWORD || secrets.TESTING_DIRECTUS_DB_PASSWORD || secrets.DIRECTUS_DB_PASSWORD)) }} DIRECTUS_API_TOKEN: ${{ secrets.DIRECTUS_API_TOKEN || (needs.prepare.outputs.target == 'production' && secrets.DIRECTUS_API_TOKEN || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_DIRECTUS_API_TOKEN || secrets.TESTING_DIRECTUS_API_TOKEN || secrets.DIRECTUS_API_TOKEN)) }} GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }} steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 1 - name: 🚀 Deploy to ${{ env.TARGET }} shell: bash 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 # Generated by CI - $TARGET - $(date -u) # Determine dynamic values before writing the file LOG_LEVEL=$( [[ "$TARGET" == "testing" || "$TARGET" == "development" ]] && echo "debug" || echo "info" ) COOKIE_DOMAIN=.$(echo $NEXT_PUBLIC_BASE_URL | sed 's|https://||') cat > /tmp/klz-cables.env << EOF # Generated by CI - $TARGET - $(date -u) IMAGE_TAG=$IMAGE_TAG NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL UMAMI_WEBSITE_ID=$UMAMI_WEBSITE_ID UMAMI_API_ENDPOINT=$UMAMI_API_ENDPOINT SENTRY_DSN=$SENTRY_DSN LOG_LEVEL=$LOG_LEVEL 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_HOST=$DIRECTUS_HOST 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 INTERNAL_DIRECTUS_URL=http://directus:8055 GATEKEEPER_PASSWORD=$GATEKEEPER_PASSWORD TARGET=$TARGET SENTRY_ENVIRONMENT=$TARGET PROJECT_NAME=$PROJECT_NAME COOKIE_DOMAIN=$COOKIE_DOMAIN TRAEFIK_HOST=$TRAEFIK_HOST EOF # Append complex variables that contain backticks using printf to avoid shell expansion hits printf "AUTH_MIDDLEWARE=%s\n" "$( [[ "$TARGET" == "production" ]] && echo "${PROJECT_NAME}-compress" || echo "${PROJECT_NAME}-auth,${PROJECT_NAME}-compress" )" >> /tmp/klz-cables.env printf "TRAEFIK_HOST_RULE='%s'\n" '${{ needs.prepare.outputs.traefik_host_rule }}' >> /tmp/klz-cables.env # 1. Cleanup and Create Directories on server BEFORE SCP ssh -o StrictHostKeyChecking=accept-new root@alpha.mintel.me bash << 'EOF' set -e mkdir -p /home/deploy/sites/klz-cables.com/varnish mkdir -p /home/deploy/sites/klz-cables.com/directus/uploads /home/deploy/sites/klz-cables.com/directus/extensions if [ -d "/home/deploy/sites/klz-cables.com/varnish/default.vcl" ]; then echo "🧹 Removing directory 'varnish/default.vcl' created by Docker..." rm -rf /home/deploy/sites/klz-cables.com/varnish/default.vcl fi chown -R deploy:deploy /home/deploy/sites/klz-cables.com/directus /home/deploy/sites/klz-cables.com/varnish EOF # 2. Transfer files 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 scp -r -o StrictHostKeyChecking=accept-new varnish root@alpha.mintel.me:/home/deploy/sites/klz-cables.com/ ssh -o StrictHostKeyChecking=accept-new root@alpha.mintel.me IMAGE_TAG="$IMAGE_TAG" ENV_FILE="$ENV_FILE" PROJECT_NAME="$PROJECT_NAME" 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 -p "$PROJECT_NAME" --env-file "$ENV_FILE" pull echo "→ Starting containers..." docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" up -d --remove-orphans docker system prune -f --filter "until=24h" echo "→ Waiting 15s for warmup..." sleep 15 echo "→ Container status:" docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" ps if ! docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" ps | grep -q "Up"; then echo "❌ Fehler: Container nicht Up!" docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" logs --tail=150 exit 1 fi echo "→ Verifying Varnish Backend Health..." docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" exec -T varnish varnishadm backend.list if ! docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" exec -T varnish varnishadm backend.list | grep -q "healthy"; then echo "❌ Fehler: Varnish Backend ist SICK!" docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" logs varnish exit 1 fi echo "✅ Varnish Backend ist Healthy." # ────────────────────────────────────────────────────────────────────────────── # JOB 5: PageSpeed Test # ────────────────────────────────────────────────────────────────────────────── pagespeed: name: ⚡ PageSpeed needs: [prepare, deploy] if: | always() && needs.prepare.outputs.target != 'skip' && needs.deploy.result == 'success' && github.event.inputs.skip_long_checks != 'true' runs-on: docker container: image: catthehacker/ubuntu:act-latest # outputs: # report_url: ${{ steps.save.outputs.report_url }} steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 1 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 - name: Install dependencies run: npm ci --legacy-peer-deps - name: 🔍 Install Chromium (Native & ARM64) run: | apt-get update apt-get install -y gnupg wget ca-certificates # Detect OS OS_ID=$(. /etc/os-release && echo $ID) CODENAME=$(. /etc/os-release && echo $VERSION_CODENAME) if [ "$OS_ID" = "debian" ]; then echo "🎯 Debian detected - installing native chromium" apt-get install -y chromium else echo "🎯 Ubuntu detected - adding xtradeb PPA" mkdir -p /etc/apt/keyrings KEY_ID="82BB6851C64F6880" # Multi-method Key Fetch SUCCESS=false echo "Fetching key $KEY_ID..." # Method 1: gpg --recv-keys (standard) for server in "hkp://keyserver.ubuntu.com:80" "hkp://keyserver.ubuntu.com:11371"; do if gpg --no-default-keyring --keyring /tmp/xtradeb.gpg --keyserver "$server" --recv-keys "$KEY_ID"; then gpg --no-default-keyring --keyring /tmp/xtradeb.gpg --export > /etc/apt/keyrings/xtradeb.gpg SUCCESS=true && break fi done # Method 2: Direct wget (fallback) if [ "$SUCCESS" = false ]; then wget -qO- "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x$KEY_ID" | gpg --dearmor > /etc/apt/keyrings/xtradeb.gpg && SUCCESS=true fi if [ "$SUCCESS" = true ]; then echo "deb [signed-by=/etc/apt/keyrings/xtradeb.gpg] http://ppa.launchpad.net/xtradeb/apps/ubuntu $CODENAME main" > /etc/apt/sources.list.d/xtradeb-ppa.list else echo "⚠️ GPG fetch failed, using legacy apt-key as last resort..." apt-key adv --keyserver keyserver.ubuntu.com --recv-keys "$KEY_ID" || true echo "deb http://ppa.launchpad.net/xtradeb/apps/ubuntu $CODENAME main" > /etc/apt/sources.list.d/xtradeb-ppa.list fi # PRIORITY PINNING: Force PPA over Snap-dummy printf "Package: *\nPin: release o=LP-PPA-xtradeb-apps\nPin-Priority: 1001\n" > /etc/apt/preferences.d/xtradeb apt-get update apt-get install -y --allow-downgrades chromium || apt-get install -y chromium-browser fi # Force clean paths (remove existing dead links/files if they are snap wrappers) rm -f /usr/bin/google-chrome /usr/bin/chromium-browser [ -f /usr/bin/chromium ] && ln -sf /usr/bin/chromium /usr/bin/google-chrome [ -f /usr/bin/chromium ] && ln -sf /usr/bin/chromium /usr/bin/chromium-browser echo "✅ Binary check:" ls -l /usr/bin/chromium* /usr/bin/google-chrome || true continue-on-error: true - name: 🧪 Run PageSpeed (Lighthouse) env: NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_base_url }} GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }} PAGESPEED_LIMIT: 8 PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium CHROME_PATH: /usr/bin/chromium run: npm run pagespeed:test # ────────────────────────────────────────────────────────────────────────────── # JOB 6: Notifications # ────────────────────────────────────────────────────────────────────────────── notifications: name: 🔔 Notifications needs: [prepare, qa, build-app, deploy, pagespeed] if: always() runs-on: docker container: image: catthehacker/ubuntu:act-latest 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=4" || true - name: 🔔 Gotify - Failure if: | needs.prepare.result == 'failure' || needs.qa.result == 'failure' || needs.build-app.result == 'failure' || needs.deploy.result == 'failure' || needs.pagespeed.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