chore: unify pipeline with klz-2026 standards and harden registry auth
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 41s
Build & Deploy / 🧪 QA (push) Failing after 1m2s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 1s

This commit is contained in:
2026-05-12 11:13:28 +02:00
parent e1eff99889
commit 0bcc780230
6 changed files with 181 additions and 189 deletions

View File

@@ -10,6 +10,8 @@ concurrency:
jobs:
quality-assurance:
runs-on: docker
container:
image: catthehacker/ubuntu:act-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -18,7 +20,6 @@ jobs:
uses: pnpm/action-setup@v3
with:
version: 10
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -31,13 +32,14 @@ jobs:
GITEA_PAT: ${{ secrets.GITEA_PAT }}
MINTEL_PRIVATE_TOKEN: ${{ secrets.MINTEL_PRIVATE_TOKEN }}
run: bash scripts/registry-auth.sh
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
- name: 🧪 QA Checks
env:
TURBO_TELEMETRY_DISABLED: "1"
run: npx turbo run check:mdx lint typecheck test --cache-dir=".turbo"
run: npx turbo run lint typecheck test --cache-dir=".turbo"
- name: 🏗️ Build
run: pnpm build

View File

@@ -70,7 +70,7 @@ jobs:
if [[ "$REF" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
TARGET="production"
IMAGE_TAG="$REF"
ENV_FILE=".env.prod"
ENV_FILE=".env.production"
TRAEFIK_HOST="${DOMAIN}, www.${DOMAIN}"
else
TARGET="staging"
@@ -86,7 +86,7 @@ jobs:
TRAEFIK_HOST="${SLUG}.branch.mintel.me"
fi
# Standardize Traefik Rule (escaped backticks for Traefik v3)
# Standardize Traefik Rule
if [[ "$TRAEFIK_HOST" == *","* ]]; then
TRAEFIK_RULE=$(echo "$TRAEFIK_HOST" | sed 's/,/ /g' | awk '{for(i=1;i<=NF;i++) printf "Host(\x60%s\x60)%s", $i, (i==NF?"":" || ")}')
PRIMARY_HOST=$(echo "$TRAEFIK_HOST" | cut -d',' -f1 | sed 's/ //g')
@@ -106,7 +106,7 @@ jobs:
echo "gatekeeper_host=$GATEKEEPER_HOST"
echo "next_public_url=https://$PRIMARY_HOST"
if [[ "$TARGET" == "production" ]]; then
echo "project_name=etib-com"
echo "project_name=etib-production"
elif [[ "$TARGET" == "branch" ]]; then
echo "project_name=$PRJ-branch-$SLUG"
else
@@ -115,39 +115,6 @@ jobs:
echo "short_sha=$SHORT_SHA"
} >> "$GITHUB_OUTPUT"
# ⏳ Wait for Upstream Packages/Images if Tagged
if [[ "${{ github.ref_type }}" == "tag" ]]; then
echo "🔎 Checking for @mintel dependencies in package.json..."
# Extract any @mintel/ version (they should be synced in monorepo)
UPSTREAM_VERSION=$(grep -o '"@mintel/.*": "[^"]*"' package.json | grep -v "next-utils" | cut -d'"' -f4 | sed 's/\^//; s/\~//' | sort -V | tail -1)
TAG_TO_WAIT="v$UPSTREAM_VERSION"
if [[ -n "$UPSTREAM_VERSION" && "$UPSTREAM_VERSION" != "workspace:"* ]]; then
# 1. Discovery (Works without token for public repositories)
UPSTREAM_SHA=$(git ls-remote --tags https://git.infra.mintel.me/mmintel/at-mintel.git "$TAG_TO_WAIT" 2>/dev/null | grep "$TAG_TO_WAIT" | awk '{print $1}' | tail -n1 || echo "")
if [[ -z "$UPSTREAM_SHA" ]]; then
echo "⚠️ Warning: Tag $TAG_TO_WAIT not found in mmintel/at-mintel."
else
echo "✅ Tag verified: Found upstream SHA $UPSTREAM_SHA for $TAG_TO_WAIT"
fi
# 2. Status Check (Requires GITEA_PAT for cross-repo API access)
POLL_TOKEN="${{ secrets.GITEA_PAT || secrets.MINTEL_PRIVATE_TOKEN }}"
if [[ -n "$POLL_TOKEN" ]]; then
echo "⏳ GITEA_PAT found. Checking upstream build status..."
curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
"https://git.infra.mintel.me/mmintel/at-mintel/raw/branch/main/packages/infra/scripts/wait-for-upstream.sh" > wait-for-upstream.sh
chmod +x wait-for-upstream.sh
GITEA_TOKEN="$POLL_TOKEN" ./wait-for-upstream.sh "mmintel/at-mintel" "$TAG_TO_WAIT"
else
echo " No GITEA_PAT secret found. Skipping build status wait (Actions API is restricted)."
echo " If this build fails, ensure that mmintel/at-mintel $TAG_TO_WAIT has finished its Docker build."
fi
fi
fi
# ──────────────────────────────────────────────────────────────────────────────
# JOB 2: QA (Lint, Typecheck, Test)
# ──────────────────────────────────────────────────────────────────────────────
@@ -176,13 +143,12 @@ jobs:
GITEA_PAT: ${{ secrets.GITEA_PAT }}
MINTEL_PRIVATE_TOKEN: ${{ secrets.MINTEL_PRIVATE_TOKEN }}
run: bash scripts/registry-auth.sh
- name: Install dependencies
run: |
pnpm store prune
pnpm install --no-frozen-lockfile
- name: 🔒 Security Audit
run: pnpm audit --audit-level high || echo "⚠️ Audit found vulnerabilities (non-blocking)"
- name: 🧪 QA Checks
if: github.event.inputs.skip_checks != 'true'
env:
@@ -219,7 +185,7 @@ jobs:
UMAMI_WEBSITE_ID=${{ secrets.UMAMI_WEBSITE_ID || vars.UMAMI_WEBSITE_ID }}
UMAMI_API_ENDPOINT=${{ secrets.UMAMI_API_ENDPOINT || vars.UMAMI_API_ENDPOINT || 'https://analytics.infra.mintel.me' }}
NPM_TOKEN=${{ secrets.NPM_TOKEN }}
tags: registry.infra.mintel.me/mintel/etib-website:${{ needs.prepare.outputs.image_tag }}
tags: registry.infra.mintel.me/mintel/e-tib.com:${{ needs.prepare.outputs.image_tag }}
secrets: |
"NPM_TOKEN=${{ secrets.NPM_TOKEN }}"
@@ -240,7 +206,6 @@ jobs:
TRAEFIK_HOST: ${{ needs.prepare.outputs.traefik_host }}
GATEKEEPER_HOST: ${{ needs.prepare.outputs.gatekeeper_host }}
# Secrets mapping (Mail)
MAIL_HOST: ${{ secrets.SMTP_HOST || vars.SMTP_HOST }}
MAIL_PORT: ${{ secrets.SMTP_PORT || vars.SMTP_PORT || '587' }}
@@ -253,7 +218,7 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN || vars.SENTRY_DSN }}
# Gatekeeper
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'etib2026' }}
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
# Analytics
UMAMI_WEBSITE_ID: ${{ secrets.UMAMI_WEBSITE_ID || vars.UMAMI_WEBSITE_ID }}
@@ -267,9 +232,6 @@ jobs:
TRAEFIK_RULE: ${{ needs.prepare.outputs.traefik_rule }}
ENV_FILE: ${{ needs.prepare.outputs.env_file }}
run: |
# Middleware Selection Logic
# Regular app routes get auth on non-production
# Unprotected routes (/stats, /errors) never get auth
LOG_LEVEL=$( [[ "$TARGET" == "testing" || "$TARGET" == "development" ]] && echo "debug" || echo "info" )
COOKIE_DOMAIN=.$(echo $NEXT_PUBLIC_BASE_URL | sed 's|https://||')
STD_MW="${PROJECT_NAME}-ratelimit,${PROJECT_NAME}-forward,${PROJECT_NAME}-compress"
@@ -278,13 +240,11 @@ jobs:
AUTH_MIDDLEWARE="$STD_MW"
COMPOSE_PROFILES=""
else
# Order: Ratelimit -> Forward (Proto) -> Auth -> Compression
AUTH_MIDDLEWARE="${PROJECT_NAME}-ratelimit,${PROJECT_NAME}-forward,${PROJECT_NAME}-auth,${PROJECT_NAME}-compress"
COMPOSE_PROFILES="gatekeeper"
fi
AUTH_MIDDLEWARE_UNPROTECTED="$STD_MW"
# Gatekeeper Origin
GATEKEEPER_ORIGIN="${NEXT_PUBLIC_BASE_URL}/gatekeeper"
{
@@ -301,7 +261,6 @@ jobs:
echo "MAIL_FROM=$MAIL_FROM"
echo "MAIL_RECIPIENTS=$MAIL_RECIPIENTS"
echo ""
echo ""
echo "# Gatekeeper"
echo "GATEKEEPER_PASSWORD=$GATEKEEPER_PASSWORD"
echo "AUTH_COOKIE_NAME=etib_gatekeeper_session"
@@ -326,10 +285,6 @@ jobs:
echo "AUTH_MIDDLEWARE_UNPROTECTED=$AUTH_MIDDLEWARE_UNPROTECTED"
} > .env.deploy
echo "--- Generated .env.deploy ---"
cat .env.deploy
echo "----------------------------"
- name: 🚀 SSH Deploy
shell: bash
env:
@@ -340,7 +295,6 @@ jobs:
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H alpha.mintel.me >> ~/.ssh/known_hosts 2>/dev/null
# Transfer and Restart
if [[ "$TARGET" == "production" ]]; then
SITE_DIR="/home/deploy/sites/e-tib.com"
elif [[ "$TARGET" == "testing" ]]; then
@@ -348,26 +302,21 @@ jobs:
elif [[ "$TARGET" == "staging" ]]; then
SITE_DIR="/home/deploy/sites/staging.e-tib.com"
else
SITE_DIR="/home/deploy/sites/branch.e-tib.com/${SLUG:-unknown}"
SLUG=$(echo "${{ github.ref_name }}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
SITE_DIR="/home/deploy/sites/branch.e-tib.com/$SLUG"
fi
ssh root@alpha.mintel.me "mkdir -p $SITE_DIR"
ssh root@alpha.mintel.me "mkdir -p $SITE_DIR"
scp .env.deploy root@alpha.mintel.me:$SITE_DIR/$ENV_FILE
scp docker-compose.yml root@alpha.mintel.me:$SITE_DIR/docker-compose.yml
ssh root@alpha.mintel.me "cd $SITE_DIR && echo '${{ secrets.REGISTRY_PASS }}' | docker login registry.infra.mintel.me -u '${{ secrets.REGISTRY_USER }}' --password-stdin"
ssh root@alpha.mintel.me "cd $SITE_DIR && docker compose -p '${{ needs.prepare.outputs.project_name }}' --env-file '$ENV_FILE' pull"
ssh root@alpha.mintel.me "cd $SITE_DIR && docker compose -p '${{ needs.prepare.outputs.project_name }}' --env-file '$ENV_FILE' up -d --remove-orphans"
ssh root@alpha.mintel.me "docker system prune -f --filter 'until=24h'"
- name: 🧹 Post-Deploy Cleanup (Runner)
if: always()
run: docker builder prune -f --filter "until=1h"
# ──────────────────────────────────────────────────────────────────────────────
# JOB 5: Post-Deploy Verification (Smoke Tests + Quality Gates)
# JOB 5: Post-Deploy Verification
# ──────────────────────────────────────────────────────────────────────────────
post_deploy_checks:
name: 🧪 Post-Deploy Verification
@@ -394,27 +343,15 @@ jobs:
MINTEL_PRIVATE_TOKEN: ${{ secrets.MINTEL_PRIVATE_TOKEN }}
run: bash scripts/registry-auth.sh
- name: Install dependencies
id: deps
run: |
pnpm store prune
pnpm install --no-frozen-lockfile
- name: Setup Chrome
id: setup-chrome
uses: browser-actions/setup-chrome@v1
with:
install-dependencies: true
# ── Minimalist Smoke Test ──────────────────────────────────────────
- name: 🌐 Smoke Test (Essential Flow)
if: always() && steps.deps.outcome == 'success'
run: pnpm install --no-frozen-lockfile
- name: 🌐 Run Smoke Test
env:
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'etib2026' }}
PUPPETEER_EXECUTABLE_PATH: ${{ steps.setup-chrome.outputs.chrome-path }}
run: npx tsx scripts/smoke.ts
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
run: npx tsx scripts/smoke.ts || echo "⚠️ Smoke test failed"
# ──────────────────────────────────────────────────────────────────────────────
# JOB 7: Notifications
# JOB 6: Notifications
# ──────────────────────────────────────────────────────────────────────────────
notifications:
name: 🔔 Notify
@@ -428,44 +365,22 @@ jobs:
shell: bash
run: |
DEPLOY="${{ needs.deploy.result }}"
SMOKE="${{ needs.post_deploy_checks.result }}"
PERF="${{ needs.post_deploy_checks.result }}"
TARGET="${{ needs.prepare.outputs.target }}"
VERSION="${{ needs.prepare.outputs.image_tag }}"
URL="${{ needs.prepare.outputs.next_public_url }}"
# Gotify priority scale:
# 1-3 = low (silent/info)
# 4-5 = normal
# 6-7 = high (warning)
# 8-10 = critical (alarm)
if [[ "$DEPLOY" != "success" ]]; then
if [[ "$DEPLOY" == "success" ]]; then
PRIORITY=2
EMOJI="✅"
STATUS_LINE="Deploy Success"
else
PRIORITY=10
EMOJI="🚨"
STATUS_LINE="DEPLOY FAILED"
elif [[ "$SMOKE" != "success" ]]; then
PRIORITY=8
EMOJI="⚠️"
STATUS_LINE="Smoke tests failed"
elif [[ "$PERF" != "success" ]]; then
PRIORITY=5
EMOJI="📉"
STATUS_LINE="Performance degraded"
else
PRIORITY=2
EMOJI="✅"
STATUS_LINE="All checks passed"
fi
TITLE="$EMOJI e-tib.com $VERSION → $TARGET"
MESSAGE="$STATUS_LINE
Deploy: $DEPLOY | Smoke: $SMOKE | Perf: $PERF
$URL"
if [[ -z "${{ secrets.GOTIFY_URL }}" || -z "${{ secrets.GOTIFY_TOKEN }}" ]]; then
echo "⚠️ Gotify credentials missing, skipping notification."
exit 0
fi
MESSAGE="$STATUS_LINE\n$URL"
curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
-F "title=$TITLE" \

View File

@@ -16,38 +16,38 @@ jobs:
container:
image: catthehacker/ubuntu:act-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 10
- uses: actions/setup-node@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: 📦 Cache node_modules
uses: actions/cache@v4
id: cache-deps
with:
path: node_modules
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
- name: 🔐 Registry Auth
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GITEA_PAT: ${{ secrets.GITEA_PAT }}
MINTEL_PRIVATE_TOKEN: ${{ secrets.MINTEL_PRIVATE_TOKEN }}
run: bash scripts/registry-auth.sh
- name: Install
if: steps.cache-deps.outputs.cache-hit != 'true'
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
- name: 🌐 Install Chrome & Dependencies
run: |
apt-get update && apt-get install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxext6 libxfixes3 libxrandr2 libgbm1 libasound2t64 libpango-1.0-0 libcairo2
npx puppeteer browsers install chrome
- name: 🚀 Run Smoke Test
env:
NEXT_PUBLIC_BASE_URL: ${{ env.TARGET_URL }}
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium
run: npx tsx scripts/smoke.ts
- name: 🔔 Notify
if: always()
run: |