diff --git a/.gitea/workflows/build-image.yml b/.gitea/workflows/build-image.yml index a1b037da..3703185a 100644 --- a/.gitea/workflows/build-image.yml +++ b/.gitea/workflows/build-image.yml @@ -883,13 +883,16 @@ jobs: echo "⏳ Waiting for services to start..." sleep 15 - # Force containers to pull latest code by restarting staging-app - echo "🔄 Restarting staging-app to pull latest code from Git..." + # Force containers to pull latest code from Git repository + echo "🔄 Pulling latest code from Git repository in staging-app container..." + docker compose exec -T staging-app bash -c "cd /var/www/html && git -c safe.directory=/var/www/html fetch origin staging && git -c safe.directory=/var/www/html reset --hard origin/staging && git -c safe.directory=/var/www/html clean -fd" || echo "⚠️ Git pull failed, container will sync on next restart" + + # Also trigger a restart to ensure entrypoint script runs + echo "🔄 Restarting staging-app to ensure all services are up-to-date..." docker compose restart staging-app || echo "⚠️ Failed to restart staging-app" - echo "⏳ Waiting for Git sync to complete..." + echo "⏳ Waiting for services to stabilize..." sleep 10 - echo "📊 Container status:" docker compose ps @@ -919,3 +922,155 @@ jobs: echo "🚀 Staging deployment successful!" echo "URL: https://staging.michaelschiemer.de" echo "Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" + + # Job 4: Auto-deploy to Production (only for main branch) + deploy-production: + name: Auto-deploy to Production + needs: [changes, build, runtime-base] + if: (github.ref_name == 'main' || github.head_ref == 'main' || (github.ref_name == '' && contains(github.ref, 'main'))) && needs.changes.outputs.needs_build == 'true' + runs-on: ubuntu-latest + environment: + name: production + url: https://michaelschiemer.de + env: + DEPLOYMENT_HOST: 94.16.110.151 + steps: + - name: Determine branch name + id: branch + shell: bash + run: | + REF_NAME="${{ github.ref_name }}" + if [ -z "$REF_NAME" ]; then + REF_NAME=$(echo "${{ github.ref }}" | sed 's/refs\/heads\///') + fi + if [ -z "$REF_NAME" ]; then + REF_NAME="main" + fi + echo "BRANCH=$REF_NAME" >> $GITHUB_OUTPUT + echo "📋 Branch: $REF_NAME" + + - name: Checkout deployment scripts + run: | + REF_NAME="${{ steps.branch.outputs.BRANCH }}" + REPO="${{ github.repository }}" + + if [ -n "${{ secrets.CI_TOKEN }}" ]; then + git clone --depth 1 --branch "$REF_NAME" \ + "https://${{ secrets.CI_TOKEN }}@git.michaelschiemer.de/${REPO}.git" \ + /workspace/repo + else + git clone --depth 1 --branch "$REF_NAME" \ + "https://git.michaelschiemer.de/${REPO}.git" \ + /workspace/repo || \ + git clone --depth 1 \ + "https://git.michaelschiemer.de/${REPO}.git" \ + /workspace/repo + fi + + cd /workspace/repo + + - name: Setup SSH key + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/production + chmod 600 ~/.ssh/production + ssh-keyscan -H ${{ env.DEPLOYMENT_HOST }} >> ~/.ssh/known_hosts + + - name: Deploy to Production Server + run: | + set -e + + DEPLOYMENT_HOST="${{ env.DEPLOYMENT_HOST }}" + REGISTRY="${{ env.REGISTRY }}" + IMAGE_NAME="${{ env.IMAGE_NAME }}" + + # Get image tag from build job output with fallback + IMAGE_TAG="${{ needs.build.outputs.image_tag }}" + + # If IMAGE_TAG is empty, use latest + if [ -z "$IMAGE_TAG" ] || [ "$IMAGE_TAG" = "..." ] || [ "$IMAGE_TAG" = "null" ]; then + COMMIT_SHA="${{ github.sha }}" + if [ -z "$COMMIT_SHA" ]; then + COMMIT_SHA=$(cd /workspace/repo && git rev-parse HEAD 2>/dev/null || echo "") + fi + if [ -z "$COMMIT_SHA" ]; then + IMAGE_TAG="latest" + else + SHORT_SHA=$(echo "$COMMIT_SHA" | cut -c1-7) + IMAGE_TAG="git-${SHORT_SHA}" + fi + fi + + FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}" + STACK_PATH="~/deployment/stacks/application" + + echo "🚀 Starting production deployment..." + echo " Image: ${FULL_IMAGE}" + echo " Tag: ${IMAGE_TAG}" + echo " Host: ${DEPLOYMENT_HOST}" + echo " Stack: ${STACK_PATH}" + + ssh -i ~/.ssh/production \ + -o StrictHostKeyChecking=no \ + -o UserKnownHostsFile=/dev/null \ + deploy@${DEPLOYMENT_HOST} <> $GITHUB_OUTPUT - echo "📦 Deploying image tag: $IMAGE_TAG" - - - name: Setup SSH key - run: | - mkdir -p ~/.ssh - echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/production - chmod 600 ~/.ssh/production - ssh-keyscan -H ${{ env.DEPLOYMENT_HOST }} >> ~/.ssh/known_hosts - - - name: Deploy via SSH - run: | - set -e - - DEPLOYMENT_HOST="${{ env.DEPLOYMENT_HOST }}" - REGISTRY="${{ env.REGISTRY }}" - IMAGE_NAME="${{ env.IMAGE_NAME }}" - IMAGE_TAG="${{ steps.image_tag.outputs.IMAGE_TAG }}" - - FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}" - STACK_PATH="~/deployment/stacks/application" - - echo "🚀 Starting deployment..." - echo " Image: ${FULL_IMAGE}" - echo " Tag: ${IMAGE_TAG}" - echo " Host: ${DEPLOYMENT_HOST}" - echo " Stack: ${STACK_PATH}" - - ssh -i ~/.ssh/production \ - -o StrictHostKeyChecking=no \ - -o UserKnownHostsFile=/dev/null \ - deploy@${DEPLOYMENT_HOST} <> $GITHUB_OUTPUT - echo "📦 Deploying image tag: $IMAGE_TAG" - - - name: Setup SSH key - run: | - mkdir -p ~/.ssh - echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/production - chmod 600 ~/.ssh/production - ssh-keyscan -H ${{ env.DEPLOYMENT_HOST }} >> ~/.ssh/known_hosts - - - name: Deploy via SSH - run: | - set -e - - DEPLOYMENT_HOST="${{ env.DEPLOYMENT_HOST }}" - REGISTRY="${{ env.REGISTRY }}" - IMAGE_NAME="${{ env.IMAGE_NAME }}" - IMAGE_TAG="${{ steps.image_tag.outputs.IMAGE_TAG }}" - - FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}" - STACK_PATH="~/deployment/stacks/staging" - - echo "🚀 Starting staging deployment..." - echo " Image: ${FULL_IMAGE}" - echo " Tag: ${IMAGE_TAG}" - echo " Host: ${DEPLOYMENT_HOST}" - echo " Stack: ${STACK_PATH}" - - ssh -i ~/.ssh/production \ - -o StrictHostKeyChecking=no \ - -o UserKnownHostsFile=/dev/null \ - deploy@${DEPLOYMENT_HOST} </dev/null || true - docker network create staging-internal 2>/dev/null || true - - echo "🔄 Starting/updating services..." - docker compose up -d --pull always --force-recreate || { - echo "❌ Failed to start services" - exit 1 - } - - echo "⏳ Waiting for services to start..." - sleep 15 - - # Force containers to pull latest code by restarting staging-app - echo "🔄 Restarting staging-app to pull latest code from Git..." - docker compose restart staging-app || echo "⚠️ Failed to restart staging-app" - - echo "⏳ Waiting for Git sync to complete..." - sleep 10 - - echo "📊 Container status:" - docker compose ps - - echo "✅ Staging deployment completed!" - EOF - - - name: Wait for deployment to stabilize - run: sleep 30 - - - name: Health check - id: health - run: | - for i in {1..10}; do - if curl -f -k https://staging.michaelschiemer.de/health; then - echo "✅ Health check passed" - exit 0 - fi - echo "⏳ Waiting for staging service... (attempt $i/10)" - sleep 10 - done - echo "❌ Health check failed" - exit 1 - - - name: Rollback on failure - if: failure() && steps.health.outcome == 'failure' - run: | - echo "⚠️ Staging deployment failed - manual rollback may be required" - echo "💡 To rollback manually, SSH to the server and run:" - echo " cd ~/deployment/stacks/staging" - echo " docker compose down" - echo " git checkout docker-compose.yml" - echo " docker compose up -d" - - - name: Notify deployment success - if: success() - run: | - echo "🚀 Staging deployment successful!" - echo "URL: https://staging.michaelschiemer.de" - echo "Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.image_tag.outputs.IMAGE_TAG }}" - - - name: Notify deployment failure - if: failure() - run: | - echo "❌ Staging deployment failed" - # TODO: Add Slack/Email notification diff --git a/.gitea/workflows/production-deploy.yml b/.gitea/workflows/production-deploy.yml deleted file mode 100644 index 59604226..00000000 --- a/.gitea/workflows/production-deploy.yml +++ /dev/null @@ -1,777 +0,0 @@ -name: Production Deployment Pipeline - -run-name: Production Deployment - ${{ github.ref_name }} - ${{ github.sha }} - -on: - push: - branches: [ main, develop ] - workflow_dispatch: - -env: - REGISTRY: registry.michaelschiemer.de - IMAGE_NAME: framework - DEPLOYMENT_HOST: 94.16.110.151 - -jobs: - # Job 1: Run Tests - test: - name: Run Tests & Quality Checks - runs-on: php-ci # Uses pre-built PHP 8.5 CI image with Composer pre-installed - steps: - - name: Checkout code - run: | - REF_NAME="${{ github.ref_name }}" - REPO="${{ github.repository }}" - if [ -z "$REF_NAME" ]; then - REF_NAME="main" - fi - - # Use CI token if available, otherwise try public access - if [ -n "${{ secrets.CI_TOKEN }}" ]; then - git clone --depth 1 --branch "$REF_NAME" \ - "https://${{ secrets.CI_TOKEN }}@git.michaelschiemer.de/${REPO}.git" \ - /workspace/repo - else - # Try public HTTPS (works if repository is public) - git clone --depth 1 --branch "$REF_NAME" \ - "https://git.michaelschiemer.de/${REPO}.git" \ - /workspace/repo || \ - # Fallback: Try to use Gitea's internal runner access - git clone --depth 1 \ - "https://git.michaelschiemer.de/${REPO}.git" \ - /workspace/repo - fi - - cd /workspace/repo - - # PHP is already installed in php-ci image - no setup needed - - - name: Cache Composer dependencies (simple) - run: | - if [ -d "/tmp/composer-cache/vendor" ]; then - echo "📦 Restoring cached dependencies..." - cp -r /tmp/composer-cache/vendor /workspace/repo/vendor || true - fi - - - name: Install dependencies - run: | - cd /workspace/repo - # TEMPORARY WORKAROUND: Ignore PHP 8.5 platform requirement until dependencies officially support PHP 8.5 - # TODO: Remove --ignore-platform-req=php when dependencies are updated (estimated: 1 month) - composer install --no-interaction --prefer-dist --optimize-autoloader --ignore-platform-req=php - - - name: Save Composer cache - run: | - mkdir -p /tmp/composer-cache - cp -r /workspace/repo/vendor /tmp/composer-cache/vendor || true - - # TEMPORARY WORKAROUND: Skip tests until dependencies support PHP 8.5 - # TODO: Re-enable these steps when dependencies are updated (estimated: 1 month) - # - name: Run Pest tests - # if: false # Temporarily disabled - # run: | - # cd /workspace/repo - # ./vendor/bin/pest --colors=always - - # - name: Run PHPStan - # if: false # Temporarily disabled - # run: | - # cd /workspace/repo - # make phpstan - - # - name: Code style check - # if: false # Temporarily disabled - # run: | - # cd /workspace/repo - # composer cs - - - name: Tests temporarily skipped - run: | - echo "⚠️ Tests temporarily skipped due to PHP 8.5 compatibility issues" - echo "This will be re-enabled when dependencies (pestphp/pest, brianium/paratest) support PHP 8.5" - echo "Estimated timeline: 1 month" - echo "Test: Runner docker-build label verification" - - # Job 2: Build & Push Docker Image - build: - name: Build Docker Image - needs: test - # Note: if condition might not work correctly in Gitea - always() might need different syntax - # if: always() && (needs.test.result == 'success' || needs.test.result == 'skipped') - runs-on: docker-build # Uses docker:dind image with Docker pre-installed - outputs: - image_tag: ${{ steps.image_info.outputs.IMAGE_TAG || steps.build_image.outputs.IMAGE_TAG || 'git-' + github.sha }} - commit_sha: ${{ steps.meta.outputs.commit_sha }} - image_built: ${{ steps.check_image.outputs.SKIP_BUILD != 'true' }} - steps: - - name: Install git and setup environment - shell: sh - run: | - # docker:latest is minimal (Alpine-based), install git and bash - # Note: docker-build image should already have these, but ensure they're available - if ! command -v bash >/dev/null 2>&1 || ! command -v git >/dev/null 2>&1; then - apk add --no-cache git bash curl - fi - # Verify installation - bash --version - git --version - - - name: Checkout code - shell: bash - run: | - REF_NAME="${{ github.ref_name }}" - REPO="${{ github.repository }}" - if [ -z "$REF_NAME" ]; then - REF_NAME="main" - fi - - # Use CI token if available, otherwise try public access - if [ -n "${{ secrets.CI_TOKEN }}" ]; then - git clone --depth 1 --branch "$REF_NAME" \ - "https://${{ secrets.CI_TOKEN }}@git.michaelschiemer.de/${REPO}.git" \ - /workspace/repo - else - # Try public HTTPS (works if repository is public) - git clone --depth 1 --branch "$REF_NAME" \ - "https://git.michaelschiemer.de/${REPO}.git" \ - /workspace/repo || \ - # Fallback: Try to use Gitea's internal runner access - git clone --depth 1 \ - "https://git.michaelschiemer.de/${REPO}.git" \ - /workspace/repo - fi - - cd /workspace/repo - - - name: Setup Docker Buildx - shell: bash - run: | - # Verifiziere dass Buildx verfügbar ist - docker buildx version || echo "Buildx nicht gefunden, versuche Installation..." - - # Zeige aktuellen Docker-Host (vom Runner gesetzt) - echo "🔧 Aktueller DOCKER_HOST: ${DOCKER_HOST:-nicht gesetzt}" - echo "🔧 Docker-Info:" - docker info | grep -E "Server Version|Registry|Insecure" || true - - # Prüfe ob Docker verfügbar ist - if ! docker ps >/dev/null 2>&1; then - echo "❌ Fehler: Docker ist nicht verfügbar!" - echo "Versuche DOCKER_HOST zu setzen..." - # Der Runner setzt normalerweise DOCKER_HOST automatisch - # Falls nicht, verwende den Standard-Host - if [ -z "$DOCKER_HOST" ]; then - echo "⚠️ DOCKER_HOST nicht gesetzt - verwende Standard" - fi - fi - - # Erstelle Builder mit Standard-Docker-Connection (vom Runner bereitgestellt) - # Buildx verwendet automatisch DOCKER_HOST aus der Umgebung - if ! docker buildx ls 2>/dev/null | grep -q builder; then - echo "📦 Erstelle neuen Buildx Builder..." - docker buildx create --name builder --use --driver docker-container - else - echo "✅ Builder existiert bereits, verwende ihn..." - docker buildx use builder - fi - - # Bootstrap Builder - echo "🔄 Bootstrap Builder..." - docker buildx inspect --bootstrap - - # Zeige Builder-Info - echo "📋 Builder-Status:" - docker buildx ls - - - name: Generate image metadata - id: meta - run: | - cd /workspace/repo - # Gitea Actions supports github.sha for compatibility - COMMIT_SHA="${{ github.sha }}" - if [ -z "$COMMIT_SHA" ]; then - COMMIT_SHA=$(git rev-parse HEAD) - fi - SHORT_SHA=$(echo "$COMMIT_SHA" | cut -c1-7) - TAG="${SHORT_SHA}-$(date +%s)" - echo "tag=${TAG}" >> $GITHUB_OUTPUT - echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT - echo "commit_sha=${COMMIT_SHA}" >> $GITHUB_OUTPUT - echo "Generated tag: ${TAG}" - - - name: Login to Registry - id: login - shell: bash - run: | - REGISTRY_USER="${{ secrets.REGISTRY_USER }}" - REGISTRY_PASSWORD="${{ secrets.REGISTRY_PASSWORD }}" - REGISTRY_URL="${{ env.REGISTRY }}" - DEPLOYMENT_HOST="${{ env.DEPLOYMENT_HOST }}" - - # Prüfe ob Secrets gesetzt sind - if [ -z "$REGISTRY_USER" ]; then - echo "❌ Error: REGISTRY_USER Secret ist nicht gesetzt in Gitea" - echo "Bitte gehe zu: Repository → Settings → Secrets" - echo "Und füge REGISTRY_USER hinzu (z.B. 'admin')" - exit 1 - fi - - if [ -z "$REGISTRY_PASSWORD" ]; then - echo "❌ Error: REGISTRY_PASSWORD Secret ist nicht gesetzt in Gitea" - echo "Bitte gehe zu: Repository → Settings → Secrets" - echo "Und füge REGISTRY_PASSWORD hinzu" - exit 1 - fi - - # Versuche verschiedene Registry-URLs - # 1. Externe Domain (HTTPS via Traefik) - # 2. Host-IP (HTTPS, falls DNS nicht funktioniert) - # 3. Host-IP Port 5000 (HTTP, direkter Zugriff) - - echo "🔐 Versuche Registry-Login..." - echo "📝 Benutzer: $REGISTRY_USER" - echo "🌐 Deployment Host: $DEPLOYMENT_HOST" - - # Finde das Gateway des Docker-Netzwerks (Host-IP vom Container aus) - # Job-Container laufen in docker-dind, das wiederum in einem Container läuft - # Daher müssen wir den Host vom docker-dind Container aus erreichen - HOST_IP=$(ip route | grep default | awk '{print $3}' 2>/dev/null | head -1) - if [ -z "$HOST_IP" ]; then - # Fallback: Versuche Host über bekannte Docker-Netzwerk-Gateways - HOST_IP=$(getent hosts host.docker.internal | awk '{print $1}' 2>/dev/null || echo "") - if [ -z "$HOST_IP" ]; then - HOST_IP="$DEPLOYMENT_HOST" - fi - fi - - echo "🔍 Gefundene Host-IP: ${HOST_IP:-nicht gefunden}" - - # Teste verschiedene Registry-URLs - # PRIORITÄT: HTTPS-Registry zuerst (keine insecure-registry nötig, funktioniert mit Buildx) - REGISTRY_URLS=( - "registry.michaelschiemer.de" # HTTPS via Traefik (empfohlen für Buildx) - "$REGISTRY_URL" # Externe Domain (falls gesetzt) - "$DEPLOYMENT_HOST" # Host IP (HTTPS via Traefik, falls erreichbar) - "$DEPLOYMENT_HOST:5000" # Direkter HTTP-Zugriff auf Host Port 5000 (Fallback) - "host.docker.internal:5000" # Docker Host (Mac/Windows) - "${HOST_IP}:5000" # Gateway-IP (Linux) - "registry:5000" # Container-Name (funktioniert wenn docker-dind Zugriff auf traefik-public hat) - ) - - LOGIN_SUCCESS=false - - for TEST_URL in "${REGISTRY_URLS[@]}"; do - echo "" - echo "🔍 Teste Registry: $TEST_URL" - - # Wenn URL bereits Port 5000 enthält, teste HTTP (Fallback) - if [[ "$TEST_URL" == *":5000" ]]; then - # Direkter HTTP-Zugriff (Port bereits in URL) - echo " Versuche HTTP-Zugriff auf http://$TEST_URL/v2/" - HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://$TEST_URL/v2/" 2>&1 || echo "000") - echo " HTTP-Status: $HTTP_CODE" - - # 401 bedeutet Registry ist erreichbar, aber Auth erforderlich (das ist gut!) - if [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "200" ]; then - echo "✅ Registry erreichbar über HTTP: http://$TEST_URL (Status: $HTTP_CODE)" - - # Versuche Login mit HTTP (ohne :5000 nochmal hinzuzufügen) - # Docker benötigt möglicherweise insecure-registry Konfiguration für HTTP - echo " Versuche Docker Login..." - - # Prüfe ob Registry in insecure-registry Liste ist (vom docker-dind Container aus) - echo " Prüfe insecure-registry Konfiguration..." - - # Führe Login aus und sammle Output UND Exit-Code - set +e # Erlaube Fehler während Login-Versuch - - # Versuche zuerst mit expliziter HTTP-URL (ohne Protokoll-Prefix) - # Docker sollte automatisch HTTP verwenden wenn insecure-registry konfiguriert ist - LOGIN_OUTPUT=$(echo "$REGISTRY_PASSWORD" | docker login "$TEST_URL" -u "$REGISTRY_USER" --password-stdin 2>&1) - LOGIN_EXIT_CODE=$? - - # Falls fehlgeschlagen und HTTPS-Fehler, versuche explizit HTTP - if [ $LOGIN_EXIT_CODE -ne 0 ] && echo "$LOGIN_OUTPUT" | grep -qi "https\|tls\|certificate"; then - echo " ⚠️ Docker versucht HTTPS, versuche explizit HTTP..." - # Docker kann nicht direkt HTTP erzwingen, aber insecure-registry sollte das lösen - # Versuche nochmal - sollte jetzt funktionieren wenn insecure-registry greift - LOGIN_OUTPUT=$(echo "$REGISTRY_PASSWORD" | docker login "$TEST_URL" -u "$REGISTRY_USER" --password-stdin 2>&1) - LOGIN_EXIT_CODE=$? - fi - - set -e # Zurück zu strict mode - - echo " Login-Exit-Code: $LOGIN_EXIT_CODE" - echo " Login-Output:" - if [ -n "$LOGIN_OUTPUT" ]; then - echo "$LOGIN_OUTPUT" | while IFS= read -r line; do - echo " $line" - done || echo " (keine Ausgabe)" - else - echo " (keine Ausgabe)" - fi - - # Prüfe ob es ein insecure-registry Problem ist - if echo "$LOGIN_OUTPUT" | grep -qi "insecure\|certificate\|tls\|unauthorized\|401"; then - echo " ⚠️ Mögliche Probleme:" - if echo "$LOGIN_OUTPUT" | grep -qi "unauthorized\|401"; then - echo " - Unauthorized (401): Möglicherweise falsche Credentials" - fi - if echo "$LOGIN_OUTPUT" | grep -qi "insecure\|certificate\|tls"; then - echo " - SSL/Insecure Registry Problem erkannt" - fi - fi - - if [ $LOGIN_EXIT_CODE -eq 0 ]; then - echo "✅ Erfolgreich bei Registry angemeldet: $TEST_URL" - REGISTRY_URL="$TEST_URL" - ACTUAL_REGISTRY="$TEST_URL" - LOGIN_SUCCESS=true - echo "REGISTRY_URL=$TEST_URL" >> $GITHUB_ENV - echo "ACTUAL_REGISTRY=$TEST_URL" >> $GITHUB_ENV - break - else - echo "⚠️ Login fehlgeschlagen für $TEST_URL (Exit Code: $LOGIN_EXIT_CODE)" - echo " Prüfe Login-Output oben für Details" - fi - else - echo "⚠️ Registry nicht erreichbar: http://$TEST_URL (Status: $HTTP_CODE)" - fi - else - # Domain ohne Port - teste HTTPS zuerst (empfohlen, keine insecure-registry nötig!) - echo " Versuche HTTPS-Zugriff auf https://$TEST_URL/v2/" - # Stelle sicher, dass curl verfügbar ist - if ! command -v curl >/dev/null 2>&1; then - apk add --no-cache curl ca-certificates >/dev/null 2>&1 || true - fi - HTTPS_CODE=$(curl -k -s -o /dev/null -w "%{http_code}" "https://$TEST_URL/v2/" 2>&1 || echo "000") - # Debug: Wenn curl fehlschlägt, zeige mehr Details - if [ "$HTTPS_CODE" = "000" ]; then - echo " ⚠️ curl Fehler beim HTTPS-Test" - CURL_VERBOSE=$(curl -k -v "https://$TEST_URL/v2/" 2>&1 | head -20) - echo " curl Verbose Output:" - echo "$CURL_VERBOSE" | while IFS= read -r line; do - echo " $line" - done || true - fi - echo " HTTPS-Status: $HTTPS_CODE" - - # 404 könnte bedeuten, dass die Route nicht richtig konfiguriert ist - # 401 ist gut (Registry erreichbar, Auth erforderlich) - # 200 ist auch gut (Auth erfolgreich oder nicht erforderlich) - if [ "$HTTPS_CODE" = "401" ] || [ "$HTTPS_CODE" = "200" ]; then - echo "✅ Registry erreichbar über HTTPS: https://$TEST_URL (Status: $HTTPS_CODE)" - - # Versuche Login via HTTPS (keine insecure-registry nötig!) - echo " Versuche Docker Login über HTTPS..." - set +e - LOGIN_OUTPUT=$(echo "$REGISTRY_PASSWORD" | docker login "$TEST_URL" -u "$REGISTRY_USER" --password-stdin 2>&1) - LOGIN_EXIT_CODE=$? - set -e - - echo " Login-Exit-Code: $LOGIN_EXIT_CODE" - if [ -n "$LOGIN_OUTPUT" ]; then - echo "$LOGIN_OUTPUT" | while IFS= read -r line; do - echo " $line" - done || true - fi - - if [ $LOGIN_EXIT_CODE -eq 0 ]; then - echo "✅ Erfolgreich bei Registry angemeldet über HTTPS: $TEST_URL" - REGISTRY_URL="$TEST_URL" - ACTUAL_REGISTRY="$TEST_URL" - LOGIN_SUCCESS=true - echo "REGISTRY_URL=$TEST_URL" >> $GITHUB_ENV - echo "ACTUAL_REGISTRY=$TEST_URL" >> $GITHUB_ENV - break - else - echo "⚠️ HTTPS-Login fehlgeschlagen für $TEST_URL, versuche HTTP als Fallback..." - fi - else - echo "⚠️ Registry nicht erreichbar über HTTPS: https://$TEST_URL (Status: $HTTPS_CODE)" - fi - - # Fallback: Teste HTTP (falls HTTPS nicht funktioniert) - echo " Versuche HTTP-Zugriff auf http://$TEST_URL:5000/v2/ (Fallback)" - HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://$TEST_URL:5000/v2/" 2>&1 || echo "000") - echo " HTTP-Status: $HTTP_CODE" - - if [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "200" ]; then - echo "✅ Registry erreichbar über HTTP: http://$TEST_URL:5000 (Status: $HTTP_CODE)" - - # Versuche Login mit HTTP (benötigt insecure-registry Konfiguration) - echo " Versuche Docker Login über HTTP (benötigt insecure-registry)..." - set +e - LOGIN_OUTPUT=$(echo "$REGISTRY_PASSWORD" | docker login "$TEST_URL:5000" -u "$REGISTRY_USER" --password-stdin 2>&1) - LOGIN_EXIT_CODE=$? - set -e - - echo " Login-Exit-Code: $LOGIN_EXIT_CODE" - if [ -n "$LOGIN_OUTPUT" ]; then - echo "$LOGIN_OUTPUT" | while IFS= read -r line; do - echo " $line" - done || true - fi - - if [ $LOGIN_EXIT_CODE -eq 0 ]; then - echo "✅ Erfolgreich bei Registry angemeldet über HTTP: $TEST_URL:5000" - REGISTRY_URL="$TEST_URL:5000" - ACTUAL_REGISTRY=$(echo "$TEST_URL:5000" | sed 's|^/||' | sed 's|/$||') - LOGIN_SUCCESS=true - echo "REGISTRY_URL=$TEST_URL:5000" >> $GITHUB_ENV - echo "ACTUAL_REGISTRY=$ACTUAL_REGISTRY" >> $GITHUB_ENV - break - else - echo "⚠️ HTTP-Login fehlgeschlagen für $TEST_URL:5000, versuche nächste URL..." - fi - fi - fi - done - - if [ "$LOGIN_SUCCESS" = false ]; then - echo "" - echo "❌ Registry-Login fehlgeschlagen für alle getesteten URLs" - echo "" - echo "🔍 Debugging-Informationen:" - echo "Getestete URLs:" - for URL in "${REGISTRY_URLS[@]}"; do - echo " - $URL (HTTPS)" - echo " - $URL:5000 (HTTP)" - done - echo "" - echo "User: $REGISTRY_USER" - echo "Password vorhanden: $([ -n "$REGISTRY_PASSWORD" ] && echo 'Ja' || echo 'Nein')" - echo "" - echo "Mögliche Ursachen:" - echo "1. ⚠️ WICHTIG: Docker-daemon (docker-dind) muss neu gestartet werden nach Änderungen an insecure-registry" - echo " - Stoppe: docker compose -f deployment/gitea-runner/docker-compose.yml stop docker-dind" - echo " - Starte: docker compose -f deployment/gitea-runner/docker-compose.yml up -d docker-dind" - echo "2. Registry nicht vom Runner-Container aus erreichbar (Netzwerk-Isolation)" - echo "3. Falsche Credentials in Gitea Secrets (REGISTRY_USER, REGISTRY_PASSWORD)" - echo "4. Registry-DNS nicht auflösbar vom Container aus" - echo "5. Registry läuft nicht oder ist nicht erreichbar" - echo "" - echo "Lösungsschritte:" - echo "1. Stelle sicher, dass docker-compose.yml alle Registry-URLs in --insecure-registry Flags enthält" - echo "2. Starte docker-dind Container NEU (siehe oben)" - echo "3. Prüfe die Secrets in Gitea (REGISTRY_USER, REGISTRY_PASSWORD)" - echo "4. Teste Registry-Erreichbarkeit: curl http://94.16.110.151:5000/v2/" - exit 1 - fi - - echo "" - echo "✅ Registry-Login erfolgreich!" - echo "📦 Verwendete Registry URL: $REGISTRY_URL" - echo "📦 ACTUAL_REGISTRY: $ACTUAL_REGISTRY" - - # Stelle sicher, dass ACTUAL_REGISTRY gesetzt ist (für Build-Step) - if [ -z "$ACTUAL_REGISTRY" ] || [ "$ACTUAL_REGISTRY" = "/" ]; then - ACTUAL_REGISTRY="$REGISTRY_URL" - fi - - # Entferne führende/trailing Slashes - ACTUAL_REGISTRY=$(echo "$ACTUAL_REGISTRY" | sed 's|^/||' | sed 's|/$||') - - # Schreibe bereinigte Registry in GITHUB_ENV - echo "ACTUAL_REGISTRY=$ACTUAL_REGISTRY" >> $GITHUB_ENV - echo "📝 Finale ACTUAL_REGISTRY (für Build): $ACTUAL_REGISTRY" - - - name: Build and push Docker image - shell: bash - env: - ACTUAL_REGISTRY: ${{ env.ACTUAL_REGISTRY }} - run: | - cd /workspace/repo - - # Bestimme die zu verwendende Registry (setze REGISTRY_TO_USE) - if [ -n "$ACTUAL_REGISTRY" ] && [ "$ACTUAL_REGISTRY" != "/" ]; then - REGISTRY_TO_USE="$ACTUAL_REGISTRY" - echo "✅ Verwende ACTUAL_REGISTRY: $REGISTRY_TO_USE" - elif [ -n "$FALLBACK_REGISTRY" ] && [ "$FALLBACK_REGISTRY" != "/" ]; then - REGISTRY_TO_USE="$FALLBACK_REGISTRY" - echo "⚠️ ACTUAL_REGISTRY leer, verwende FALLBACK_REGISTRY: $REGISTRY_TO_USE" - else - echo "❌ Fehler: Weder ACTUAL_REGISTRY noch REGISTRY sind gesetzt!" - echo "ACTUAL_REGISTRY: '${ACTUAL_REGISTRY}'" - echo "FALLBACK_REGISTRY: '${FALLBACK_REGISTRY}'" - exit 1 - fi - - # Entferne führende/trailing Slashes und Leerzeichen - REGISTRY_TO_USE=$(echo "$REGISTRY_TO_USE" | sed 's|^[/ ]*||' | sed 's|[/ ]*$||' | tr -d '[:space:]') - - # Validierung: Stelle sicher, dass REGISTRY_TO_USE nicht leer ist - if [ -z "$REGISTRY_TO_USE" ] || [ "$REGISTRY_TO_USE" = "/" ]; then - echo "❌ Fehler: REGISTRY_TO_USE ist leer oder ungültig: '$REGISTRY_TO_USE'" - exit 1 - fi - - # Validiere dass der Image-Name nicht leer ist - IMAGE_NAME="${{ env.IMAGE_NAME }}" - if [ -z "$IMAGE_NAME" ]; then - echo "❌ Fehler: IMAGE_NAME ist leer!" - exit 1 - fi - - echo "📦 Finale Registry für Build: $REGISTRY_TO_USE" - echo "📝 Image Name: $IMAGE_NAME" - echo "🏷️ Vollständiger Image-Pfad: ${REGISTRY_TO_USE}/${IMAGE_NAME}:latest" - - # Test: Zeige die exakten Docker-Tags die verwendet werden - echo "🔖 Docker Tags:" - echo " - ${REGISTRY_TO_USE}/${IMAGE_NAME}:latest" - - COMMIT_SHA="${{ github.sha }}" - if [ -z "$COMMIT_SHA" ]; then - COMMIT_SHA=$(git rev-parse HEAD) - fi - REF_NAME="${{ github.ref_name }}" - if [ -z "$REF_NAME" ]; then - REF_NAME=$(git rev-parse --abbrev-ref HEAD) - fi - SHORT_SHA=$(echo "$COMMIT_SHA" | cut -c1-7) - TAG="${SHORT_SHA}-$(date +%s)" - - # Build with cache - verwende REGISTRY_TO_USE Variable - echo "🏗️ Starte Docker Build für Commit ${SHORT_SHA}..." - docker buildx build \ - --platform linux/amd64 \ - --file ./Dockerfile.production \ - --tag "${REGISTRY_TO_USE}/${IMAGE_NAME}:latest" \ - --tag "${REGISTRY_TO_USE}/${IMAGE_NAME}:${TAG}" \ - --tag "${REGISTRY_TO_USE}/${IMAGE_NAME}:git-${SHORT_SHA}" \ - --cache-from type=registry,ref="${REGISTRY_TO_USE}/${IMAGE_NAME}:buildcache" \ - --cache-from type=registry,ref="${REGISTRY_TO_USE}/${IMAGE_NAME}:latest" \ - --cache-to type=registry,ref="${REGISTRY_TO_USE}/${IMAGE_NAME}:buildcache",mode=max \ - --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \ - --build-arg GIT_COMMIT=${COMMIT_SHA} \ - --build-arg GIT_BRANCH=${REF_NAME} \ - --push \ - . - - echo "✅ Image erfolgreich gebaut und gepusht!" - - - name: Use existing image (skip build) - if: steps.check_image.outputs.SKIP_BUILD == 'true' - shell: bash - run: | - echo "✅ Verwende vorhandenes Image: ${{ steps.check_image.outputs.EXISTING_IMAGE }}" - echo "📝 Commit: ${{ github.sha }}" - echo "🏷️ Image-Tag: ${{ steps.check_image.outputs.IMAGE_TAG }}" - echo "" - echo "⏭️ Build-Schritt wurde übersprungen, da Image bereits in Registry existiert." - echo "💡 Dies spart Zeit und Ressourcen - das Image wird direkt für Deployment verwendet." - - - name: Set image tag for deployment - id: image_info - shell: bash - run: | - COMMIT_SHA="${{ github.sha }}" - if [ -z "$COMMIT_SHA" ]; then - COMMIT_SHA=$(cd /workspace/repo && git rev-parse HEAD) - fi - SHORT_SHA=$(echo "$COMMIT_SHA" | cut -c1-7) - - # Verwende vorhandenes Image oder neu gebautes - if [ "${{ steps.check_image.outputs.SKIP_BUILD }}" = "true" ]; then - IMAGE_TAG="git-${SHORT_SHA}" - echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_OUTPUT - echo "IMAGE_EXISTS=true" >> $GITHUB_OUTPUT - else - # Tag aus neuem Build - IMAGE_TAG="${SHORT_SHA}-$(date +%s)" - echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_OUTPUT - echo "IMAGE_EXISTS=false" >> $GITHUB_OUTPUT - fi - - echo "📦 Verwendetes Image-Tag: $IMAGE_TAG" - - - name: Image scan for vulnerabilities - shell: bash - run: | - if [ "${{ steps.check_image.outputs.SKIP_BUILD }}" = "true" ]; then - echo "⏭️ Vulnerability scan übersprungen (verwendet vorhandenes Image)" - else - echo "✅ Image built successfully: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.image_info.outputs.IMAGE_TAG }}" - fi - - # Job 3: Deploy to Production - deploy: - name: Deploy to Production Server - needs: build - runs-on: ubuntu-latest - environment: - name: production - url: https://michaelschiemer.de - steps: - - name: Checkout deployment scripts - run: | - REF_NAME="${{ github.ref_name }}" - REPO="${{ github.repository }}" - if [ -z "$REF_NAME" ]; then - REF_NAME="main" - fi - - # Use CI token if available, otherwise try public access - if [ -n "${{ secrets.CI_TOKEN }}" ]; then - git clone --depth 1 --branch "$REF_NAME" \ - "https://${{ secrets.CI_TOKEN }}@git.michaelschiemer.de/${REPO}.git" \ - /workspace/repo - else - # Try public HTTPS (works if repository is public) - git clone --depth 1 --branch "$REF_NAME" \ - "https://git.michaelschiemer.de/${REPO}.git" \ - /workspace/repo || \ - # Fallback: Try to use Gitea's internal runner access - git clone --depth 1 \ - "https://git.michaelschiemer.de/${REPO}.git" \ - /workspace/repo - fi - - cd /workspace/repo - - - name: Setup SSH key - run: | - mkdir -p ~/.ssh - echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/production - chmod 600 ~/.ssh/production - ssh-keyscan -H ${{ env.DEPLOYMENT_HOST }} >> ~/.ssh/known_hosts - - - name: Deploy via SSH - run: | - set -e - - DEPLOYMENT_HOST="${{ env.DEPLOYMENT_HOST }}" - REGISTRY="${{ env.REGISTRY }}" - IMAGE_NAME="${{ env.IMAGE_NAME }}" - - # Get image tag from build job output with fallback - IMAGE_TAG="${{ needs.build.outputs.image_tag }}" - - # Debug: Show what we got from the build job - echo "🔍 Debug: Build job output image_tag: '${IMAGE_TAG}'" - - # If IMAGE_TAG is empty or invalid, use fallback - if [ -z "$IMAGE_TAG" ] || [ "$IMAGE_TAG" = "..." ] || [ "$IMAGE_TAG" = "null" ]; then - echo "⚠️ Image tag from build job is empty or invalid, trying fallback..." - COMMIT_SHA="${{ github.sha }}" - if [ -z "$COMMIT_SHA" ]; then - # Try to get from git repo if checked out - COMMIT_SHA=$(cd /workspace/repo && git rev-parse HEAD 2>/dev/null || echo "") - fi - if [ -z "$COMMIT_SHA" ]; then - # Last resort: use latest tag (always exists after build) - echo " Using 'latest' tag as fallback" - IMAGE_TAG="latest" - else - SHORT_SHA=$(echo "$COMMIT_SHA" | cut -c1-7) - IMAGE_TAG="git-${SHORT_SHA}" - echo " Generated tag from commit SHA: ${IMAGE_TAG}" - fi - fi - - FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}" - STACK_PATH="~/deployment/stacks/application" - - echo "🚀 Starting deployment..." - echo " Image: ${FULL_IMAGE}" - echo " Tag: ${IMAGE_TAG}" - echo " Host: ${DEPLOYMENT_HOST}" - echo " Stack: ${STACK_PATH}" - - # Validate that IMAGE_TAG is not empty - if [ -z "$IMAGE_TAG" ] || [ "$IMAGE_TAG" = "..." ]; then - echo "❌ Error: IMAGE_TAG is empty or invalid: '${IMAGE_TAG}'" - exit 1 - fi - - # SSH with proper key and execute deployment commands - ssh -i ~/.ssh/production \ - -o StrictHostKeyChecking=no \ - -o UserKnownHostsFile=/dev/null \ - deploy@${DEPLOYMENT_HOST} <