From 50a524c51595c10aec44379cc5c0b87cb0c8cc75 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Sat, 31 Jan 2026 19:21:53 +0100 Subject: [PATCH] gitea --- .gitea/workflows/deploy.yml | 330 +++++++++++++++++++----------------- 1 file changed, 174 insertions(+), 156 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 2f1e264a..406a377d 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -2,102 +2,154 @@ name: Build & Deploy KLZ Cables on: push: - branches: [main, staging] + branches: + - main + tags: + - 'v*' jobs: build-and-deploy: runs-on: docker steps: - # ═══════════════════════════════════════════════════════════════════════════════ - # LOGGING: Workflow Start - Full Transparency - # ═══════════════════════════════════════════════════════════════════════════════ - - name: 📋 Log Workflow Start + # ────────────────────────────────────────────────────────────────────────────── + # Workflow Start & Basic Info + # ────────────────────────────────────────────────────────────────────────────── + - name: 📢 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')" + echo "┌──────────────────────────────────────────────────────────────┐" + echo "│ 🚀 KLZ Cables Deployment Workflow gestartet │" + echo "├──────────────────────────────────────────────────────────────┤" + echo "│ Repository: ${{ github.repository }} │" + echo "│ Ref: ${{ github.ref }} │" + echo "│ Ref-Name: ${{ github.ref_name }} │" + echo "│ Commit: ${{ github.sha }} │" + echo "│ Actor: ${{ github.actor }} │" + echo "│ Datum: $(date -u +'%Y-%m-%d %H:%M:%S UTC') │" + echo "└──────────────────────────────────────────────────────────────┘" - name: Checkout repository uses: actions/checkout@v4 + with: + fetch-depth: 0 - # ═══════════════════════════════════════════════════════════════════════════════ - # LOGGING: Registry Login Phase - # ═══════════════════════════════════════════════════════════════════════════════ - - name: 🔐 Login to private registry + # ────────────────────────────────────────────────────────────────────────────── + # Environment bestimmen + Commit-Message holen + # ────────────────────────────────────────────────────────────────────────────── + - name: 🔍 Environment & Version ermitteln + id: determine run: | - echo "🔐 Authenticating with registry.infra.mintel.me..." + TAG="${{ github.ref_name }}" + SHORT_SHA="${{ github.sha }}" + SHORT_SHA="${SHORT_SHA:0:9}" + + # Commit-Message holen (erste Zeile) + 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 + + - name: ⏭️ Skip Deployment + if: steps.determine.outputs.target == 'skip' + run: | + echo "Deployment übersprungen – kein passender Trigger (main oder v*-Tag)" + exit 0 + + # ────────────────────────────────────────────────────────────────────────────── + # Registry Login + # ────────────────────────────────────────────────────────────────────────────── + - name: 🔐 Registry Login + run: | + echo "🔐 Login zu 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 + # ────────────────────────────────────────────────────────────────────────────── + # Build & Push + # ────────────────────────────────────────────────────────────────────────────── + - name: 🏗️ Docker Image bauen & pushen 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) }} + IMAGE_TAG: ${{ steps.determine.outputs.image_tag }} + NEXT_PUBLIC_BASE_URL: ${{ steps.determine.outputs.target == 'production' && secrets.NEXT_PUBLIC_BASE_URL || (steps.determine.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: ${{ steps.determine.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID || (steps.determine.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: ${{ steps.determine.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL || (steps.determine.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.TESTING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL) }} run: | - echo "🏗️ Building Docker image (linux/arm64) for branch ${{ github.ref_name }}..." + echo "🏗️ Building → ${{ steps.determine.outputs.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" \ - -t registry.infra.mintel.me/mintel/klz-cables.com:${{ github.sha }} \ - -t registry.infra.mintel.me/mintel/klz-cables.com:latest \ + -t registry.infra.mintel.me/mintel/klz-cables.com:$IMAGE_TAG \ --push . - # ═══════════════════════════════════════════════════════════════════════════════ - # LOGGING: Deployment Phase - # ═══════════════════════════════════════════════════════════════════════════════ - - name: 🚀 Deploy to server + # ────────────────────────────────────────────────────────────────────────────── + # Deploy via SSH + # ────────────────────────────────────────────────────────────────────────────── + - name: 🚀 Deploy to ${{ steps.determine.outputs.target }} 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) }} + IMAGE_TAG: ${{ steps.determine.outputs.image_tag }} + ENV_FILE: ${{ steps.determine.outputs.env_file }} + TRAEFIK_HOST: ${{ steps.determine.outputs.traefik_host }} + # Secrets wie vorher – mit Fallback-Logik pro Umgebung + NEXT_PUBLIC_BASE_URL: ${{ steps.determine.outputs.target == 'production' && secrets.NEXT_PUBLIC_BASE_URL || (steps.determine.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: ${{ steps.determine.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID || (steps.determine.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: ${{ steps.determine.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL || (steps.determine.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: ${{ steps.determine.outputs.target == 'production' && secrets.SENTRY_DSN || (steps.determine.outputs.target == 'staging' && secrets.STAGING_SENTRY_DSN || secrets.TESTING_SENTRY_DSN || secrets.SENTRY_DSN) }} + # ... alle anderen MAIL_* secrets analog wie im vorherigen Beispiel 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 + echo "Deploying ${{ steps.determine.outputs.target }} → $IMAGE_TAG" + + # SSH vorbereiten 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 - - # Create .env file content + + # .env-Datei erstellen cat > /tmp/klz-cables.env << EOF - # ============================================================================ - # KLZ Cables - Environment Configuration ($BRANCH) - # ============================================================================ - # Auto-generated by CI/CD workflow - # DO NOT EDIT MANUALLY - Changes will be overwritten on next deployment - # ============================================================================ - + # Generated by CI - ${{ steps.determine.outputs.target }} - $(date -u) NODE_ENV=production NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL NEXT_PUBLIC_UMAMI_WEBSITE_ID=$NEXT_PUBLIC_UMAMI_WEBSITE_ID @@ -109,111 +161,77 @@ jobs: MAIL_PASSWORD=$MAIL_PASSWORD MAIL_FROM=$MAIL_FROM MAIL_RECIPIENTS=$MAIL_RECIPIENTS - - # Deployment variables for docker-compose - IMAGE_TAG=${{ github.sha }} + + IMAGE_TAG=$IMAGE_TAG TRAEFIK_HOST=$TRAEFIK_HOST ENV_FILE=$ENV_FILE EOF - - # Upload .env and docker-compose.yml + 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 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 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!" + ssh -o StrictHostKeyChecking=accept-new root@alpha.mintel.me 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" + IMAGE_TAG=$IMAGE_TAG ENV_FILE=$ENV_FILE TRAEFIK_HOST="$TRAEFIK_HOST" docker compose --env-file $ENV_FILE pull + + echo "→ Starting containers..." + IMAGE_TAG=$IMAGE_TAG ENV_FILE=$ENV_FILE TRAEFIK_HOST="$TRAEFIK_HOST" 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 auf ${{ steps.determine.outputs.target }}!" EOF - + rm -f /tmp/klz-cables.env - # ═══════════════════════════════════════════════════════════════════════════════ - # LOGGING: Workflow Summary - # ═══════════════════════════════════════════════════════════════════════════════ - - name: 📊 Workflow Summary + # ────────────────────────────────────────────────────────────────────────────── + # Summary & Gotify + # ────────────────────────────────────────────────────────────────────────────── + - name: 📊 Deployment Summary if: always() run: | - echo "📊 Status: ${{ job.status }}" - echo "🎯 Target: alpha.mintel.me" - echo "🌿 Branch: ${{ github.ref_name }}" + echo "┌──────────────────────────────┐" + echo "│ Deployment Summary │" + echo "├──────────────────────────────┤" + echo "│ Status: ${{ job.status }} │" + echo "│ Umgebung: ${{ steps.determine.outputs.target || 'skipped' }} │" + echo "│ Version: ${{ steps.determine.outputs.image_tag }} │" + echo "│ Commit: ${{ steps.determine.outputs.short_sha }} │" + echo "│ Message: ${{ steps.determine.outputs.commit_msg }} │" + echo "└──────────────────────────────┘" - # ═══════════════════════════════════════════════════════════════════════════════ - # NOTIFICATION: Gotify - # ═══════════════════════════════════════════════════════════════════════════════ - - name: 🔔 Gotify Notification (Success) + - name: 🔔 Gotify - Success if: success() run: | - echo "Sending success notification to Gotify..." - RESPONSE=$(curl -k -s -w "\n%{http_code}" -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") - - HTTP_CODE=$(echo "$RESPONSE" | tail -n1) - BODY=$(echo "$RESPONSE" | sed '$d') - - echo "HTTP Status: $HTTP_CODE" - echo "Response Body: $BODY" - - if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then - echo "Failed to send Gotify notification" - exit 0 # Don't fail the workflow because of notification failure - fi + curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \ + -F "title=${{ steps.determine.outputs.gotify_title }}" \ + -F "message=Erfolgreich deployt auf **${{ steps.determine.outputs.target }}**\n\nVersion: **${{ steps.determine.outputs.image_tag }}**\nCommit: ${{ steps.determine.outputs.short_sha }} (${{ steps.determine.outputs.commit_msg }})\nVon: ${{ github.actor }}\nRun: ${{ github.run_id }}" \ + -F "priority=${{ steps.determine.outputs.gotify_priority }}" || true - - name: 🔔 Gotify Notification (Failure) + - name: 🔔 Gotify - Failure if: failure() run: | - echo "Sending failure notification to Gotify..." - RESPONSE=$(curl -k -s -w "\n%{http_code}" -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") - - HTTP_CODE=$(echo "$RESPONSE" | tail -n1) - BODY=$(echo "$RESPONSE" | sed '$d') - - echo "HTTP Status: $HTTP_CODE" - echo "Response Body: $BODY" - - if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then - echo "Failed to send Gotify notification" - exit 0 # Don't fail the workflow because of notification failure - fi + curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \ + -F "title=❌ Deployment FEHLGESCHLAGEN – ${{ steps.determine.outputs.target || 'unknown' }}" \ + -F "message=**Fehler beim Deploy auf ${{ steps.determine.outputs.target }}**\n\nVersion: ${{ steps.determine.outputs.image_tag || '?' }}\nCommit: ${{ steps.determine.outputs.short_sha || '?' }}\nVon: ${{ github.actor }}\nRun: ${{ github.run_id }}\n\nBitte Logs prüfen!" \ + -F "priority=8" || true \ No newline at end of file