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 }} 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 }} gatekeeper_changed: ${{ steps.changes.outputs.gatekeeper_changed }} 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: 🔍 Check for Gatekeeper changes id: changes shell: bash run: | if git rev-parse HEAD~1 >/dev/null 2>&1; then if git diff --quiet HEAD~1 HEAD -- gatekeeper; then echo "gatekeeper_changed=false" >> $GITHUB_OUTPUT echo "ℹ️ No changes in gatekeeper/" else echo "gatekeeper_changed=true" >> $GITHUB_OUTPUT echo "⚠️ Changes detected in gatekeeper/" fi else echo "gatekeeper_changed=true" >> $GITHUB_OUTPUT echo "🆕 First commit or no history, building gatekeeper." fi - 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 { echo "target=$TARGET" echo "image_tag=$IMAGE_TAG" echo "env_file=$ENV_FILE" echo "traefik_host=$TRAEFIK_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 cache: 'npm' - name: 📦 Restore npm cache uses: actions/cache@v4 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: Install dependencies run: npm ci - 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 }} 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.directus_url }} run: | 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 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 . build-gatekeeper: name: 🏗️ Build Gatekeeper 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: 🏗️ Gatekeeper bauen & pushen env: IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }} CHG: ${{ needs.prepare.outputs.gatekeeper_changed }} run: | if [ "$CHG" == "true" ]; then echo "🏗️ Building Gatekeeper (Changes detected)..." docker buildx build \ --pull \ --platform linux/arm64 \ -t registry.infra.mintel.me/mintel/klz-cables-gatekeeper:$IMAGE_TAG \ --cache-from type=registry,ref=registry.infra.mintel.me/mintel/klz-cables-gatekeeper:buildcache \ --cache-to type=registry,ref=registry.infra.mintel.me/mintel/klz-cables-gatekeeper:buildcache,mode=max \ --push ./gatekeeper else echo "⏩ Skipping build, just re-tagging existing image..." # Fast-track: tag the latest (or buildcache) as the new version # We use buildx with cache but without rebuild triggers - it's near instant docker buildx build \ --platform linux/arm64 \ -t registry.infra.mintel.me/mintel/klz-cables-gatekeeper:$IMAGE_TAG \ --cache-from type=registry,ref=registry.infra.mintel.me/mintel/klz-cables-gatekeeper:buildcache \ --push ./gatekeeper fi # ────────────────────────────────────────────────────────────────────────────── # JOB 4: Deploy via SSH # ────────────────────────────────────────────────────────────────────────────── deploy: name: 🚀 Deploy needs: [prepare, build-app, build-gatekeeper, 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.traefik_host }} NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.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.directus_url }} DIRECTUS_HOST: ${{ needs.prepare.outputs.directus_host }} PROJECT_NAME: ${{ needs.prepare.outputs.project_name }} 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 }} GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }} steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 1 - 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_TARGET=$TARGET NEXT_PUBLIC_UMAMI_WEBSITE_ID=$NEXT_PUBLIC_UMAMI_WEBSITE_ID NEXT_PUBLIC_UMAMI_SCRIPT_URL=$NEXT_PUBLIC_UMAMI_SCRIPT_URL SENTRY_DSN=$SENTRY_DSN LOG_LEVEL=$( [[ "$TARGET" == "testing" || "$TARGET" == "development" ]] && echo "debug" || echo "info" ) 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 IMAGE_TAG=$IMAGE_TAG TRAEFIK_HOST=$TRAEFIK_HOST ENV_FILE=$ENV_FILE AUTH_MIDDLEWARE=$( [[ "$TARGET" == "production" ]] && echo "compress" || echo "${PROJECT_NAME}-auth,compress" ) 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" 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 # ────────────────────────────────────────────────────────────────────────────── # 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 cache: 'npm' - name: Install dependencies run: npm ci - 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, build-gatekeeper, 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.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