diff --git a/.gitea/workflows/build-image.yml b/.gitea/workflows/build-image.yml index 5ff44dbf..c8005a6b 100644 --- a/.gitea/workflows/build-image.yml +++ b/.gitea/workflows/build-image.yml @@ -983,6 +983,8 @@ jobs: -e "deployment_environment=staging" \ -e "deployment_hosts=production" \ -e "git_branch=staging" \ + -e "traefik_auto_restart=false" \ + -e "gitea_auto_restart=false" \ --vault-password-file /tmp/vault_pass \ --private-key ~/.ssh/production @@ -997,6 +999,8 @@ jobs: -e "docker_registry=${{ env.REGISTRY }}" \ -e "docker_registry_username=${{ secrets.REGISTRY_USER }}" \ -e "docker_registry_password=${{ secrets.REGISTRY_PASSWORD }}" \ + -e "traefik_auto_restart=false" \ + -e "gitea_auto_restart=false" \ --vault-password-file /tmp/vault_pass \ --private-key ~/.ssh/production @@ -1006,6 +1010,8 @@ jobs: ansible-playbook -i inventory/production.yml \ playbooks/install-composer-dependencies.yml \ -e "deployment_environment=staging" \ + -e "traefik_auto_restart=false" \ + -e "gitea_auto_restart=false" \ --vault-password-file /tmp/vault_pass \ --private-key ~/.ssh/production @@ -1155,6 +1161,8 @@ jobs: -e "deployment_environment=production" \ -e "deployment_hosts=production" \ -e "git_branch=main" \ + -e "traefik_auto_restart=false" \ + -e "gitea_auto_restart=false" \ --vault-password-file /tmp/vault_pass \ --private-key ~/.ssh/production @@ -1169,6 +1177,8 @@ jobs: -e "docker_registry=${{ env.REGISTRY }}" \ -e "docker_registry_username=${{ secrets.REGISTRY_USER }}" \ -e "docker_registry_password=${{ secrets.REGISTRY_PASSWORD }}" \ + -e "traefik_auto_restart=false" \ + -e "gitea_auto_restart=false" \ --vault-password-file /tmp/vault_pass \ --private-key ~/.ssh/production @@ -1178,6 +1188,8 @@ jobs: ansible-playbook -i inventory/production.yml \ playbooks/install-composer-dependencies.yml \ -e "deployment_environment=production" \ + -e "traefik_auto_restart=false" \ + -e "gitea_auto_restart=false" \ --vault-password-file /tmp/vault_pass \ --private-key ~/.ssh/production diff --git a/deployment/ansible/playbooks/fix-gitea-complete.yml b/deployment/ansible/playbooks/fix-gitea-complete.yml new file mode 100644 index 00000000..d0f901df --- /dev/null +++ b/deployment/ansible/playbooks/fix-gitea-complete.yml @@ -0,0 +1,172 @@ +--- +# Fix Gitea Complete - Deaktiviert Runner, repariert Service Discovery +# Behebt Gitea-Timeouts durch: 1) Runner deaktivieren, 2) Service Discovery reparieren +- name: Fix Gitea Complete + hosts: production + gather_facts: yes + become: no + vars: + gitea_stack_path: "{{ stacks_base_path }}/gitea" + traefik_stack_path: "{{ stacks_base_path }}/traefik" + gitea_runner_path: "{{ stacks_base_path }}/../gitea-runner" + gitea_url: "https://{{ gitea_domain }}" + + tasks: + - name: Check Gitea Runner status + ansible.builtin.shell: | + cd {{ gitea_runner_path }} + docker compose ps gitea-runner 2>/dev/null || echo "Runner not found" + register: runner_status + changed_when: false + failed_when: false + + - name: Display Gitea Runner status + ansible.builtin.debug: + msg: | + ================================================================================ + Gitea Runner Status (Before): + ================================================================================ + {{ runner_status.stdout }} + ================================================================================ + + - name: Stop Gitea Runner to reduce load + ansible.builtin.shell: | + cd {{ gitea_runner_path }} + docker compose stop gitea-runner + register: runner_stop + changed_when: runner_stop.rc == 0 + failed_when: false + when: runner_status.rc == 0 + + - name: Check Gitea container status before restart + ansible.builtin.shell: | + cd {{ gitea_stack_path }} + docker compose ps gitea + register: gitea_status_before + changed_when: false + + - name: Check Traefik container status before restart + ansible.builtin.shell: | + cd {{ traefik_stack_path }} + docker compose ps traefik + register: traefik_status_before + changed_when: false + + - name: Restart Gitea container + ansible.builtin.shell: | + cd {{ gitea_stack_path }} + docker compose restart gitea + register: gitea_restart + changed_when: gitea_restart.rc == 0 + + - name: Wait for Gitea to be ready (direct check) + ansible.builtin.shell: | + cd {{ gitea_stack_path }} + for i in {1..30}; do + if docker compose exec -T gitea curl -f http://localhost:3000/api/healthz >/dev/null 2>&1; then + echo "Gitea is ready" + exit 0 + fi + sleep 2 + done + echo "Gitea not ready after 60 seconds" + exit 1 + register: gitea_ready + changed_when: false + failed_when: false + + - name: Restart Traefik to refresh service discovery + ansible.builtin.shell: | + cd {{ traefik_stack_path }} + docker compose restart traefik + register: traefik_restart + changed_when: traefik_restart.rc == 0 + when: traefik_auto_restart | default(false) | bool + + - name: Wait for Traefik to be ready + ansible.builtin.wait_for: + timeout: 30 + delay: 2 + changed_when: false + + - name: Wait for Gitea to be reachable via Traefik (with retries) + ansible.builtin.uri: + url: "{{ gitea_url }}/api/healthz" + method: GET + status_code: [200] + validate_certs: false + timeout: 10 + register: gitea_health_via_traefik + until: gitea_health_via_traefik.status == 200 + retries: 15 + delay: 2 + changed_when: false + failed_when: false + + - name: Check if Gitea is in Traefik service discovery + ansible.builtin.shell: | + cd {{ traefik_stack_path }} + docker compose exec -T traefik wget -qO- http://localhost:8080/api/http/services 2>/dev/null | grep -i "gitea" || echo "NOT_FOUND" + register: traefik_gitea_service_check + changed_when: false + failed_when: false + + - name: Final status check + ansible.builtin.uri: + url: "{{ gitea_url }}/api/healthz" + method: GET + status_code: [200] + validate_certs: false + timeout: 10 + register: final_status + changed_when: false + failed_when: false + + - name: Summary + ansible.builtin.debug: + msg: | + ================================================================================ + ZUSAMMENFASSUNG - Gitea Complete Fix: + ================================================================================ + + Aktionen: + - Gitea Runner: {% if runner_stop.changed | default(false) %}✅ Gestoppt{% else %}ℹ️ War nicht aktiv oder nicht gefunden{% endif %} + - Gitea Restart: {% if gitea_restart.changed %}✅ Durchgeführt{% else %}ℹ️ Nicht nötig{% endif %} + - Traefik Restart: {% if traefik_restart.changed %}✅ Durchgeführt{% else %}ℹ️ Nicht nötig{% endif %} + + Gitea Ready Check: + - Direkt: {% if gitea_ready.rc == 0 %}✅ Bereit{% else %}❌ Nicht bereit{% endif %} + + Final Status: + - Gitea via Traefik: {% if final_status.status == 200 %}✅ Erreichbar (Status: 200){% else %}❌ Nicht erreichbar (Status: {{ final_status.status | default('TIMEOUT') }}){% endif %} + - Traefik Service Discovery: {% if 'NOT_FOUND' not in traefik_gitea_service_check.stdout %}✅ Gitea gefunden{% else %}❌ Gitea nicht gefunden{% endif %} + + {% if final_status.status == 200 and 'NOT_FOUND' not in traefik_gitea_service_check.stdout %} + ✅ ERFOLG: Gitea ist jetzt über Traefik erreichbar! + URL: {{ gitea_url }} + + Nächste Schritte: + 1. Teste Gitea im Browser: {{ gitea_url }} + 2. Wenn alles stabil läuft, kannst du den Runner wieder aktivieren: + cd {{ gitea_runner_path }} && docker compose up -d gitea-runner + 3. Beobachte ob der Runner Gitea wieder überlastet + {% else %} + ⚠️ PROBLEM: Gitea ist noch nicht vollständig erreichbar + + Mögliche Ursachen: + {% if final_status.status != 200 %} + - Gitea antwortet nicht via Traefik (Status: {{ final_status.status | default('TIMEOUT') }}) + {% endif %} + {% if 'NOT_FOUND' in traefik_gitea_service_check.stdout %} + - Traefik Service Discovery hat Gitea noch nicht erkannt + {% endif %} + + Nächste Schritte: + 1. Warte 1-2 Minuten und teste erneut: curl -k {{ gitea_url }}/api/healthz + 2. Prüfe Traefik-Logs: cd {{ traefik_stack_path }} && docker compose logs traefik --tail=50 + 3. Prüfe Gitea-Logs: cd {{ gitea_stack_path }} && docker compose logs gitea --tail=50 + 4. Prüfe Service Discovery: cd {{ traefik_stack_path }} && docker compose exec -T traefik wget -qO- http://localhost:8080/api/http/services + {% endif %} + + ================================================================================ + diff --git a/deployment/ansible/playbooks/fix-gitea-ssl-routing.yml b/deployment/ansible/playbooks/fix-gitea-ssl-routing.yml new file mode 100644 index 00000000..1c4b8e84 --- /dev/null +++ b/deployment/ansible/playbooks/fix-gitea-ssl-routing.yml @@ -0,0 +1,195 @@ +--- +# Fix Gitea SSL and Routing Issues +# Prüft SSL-Zertifikat, Service Discovery und behebt Routing-Probleme +- name: Fix Gitea SSL and Routing + hosts: production + gather_facts: yes + become: no + vars: + gitea_stack_path: "{{ stacks_base_path }}/gitea" + traefik_stack_path: "{{ stacks_base_path }}/traefik" + gitea_url: "https://{{ gitea_domain }}" + gitea_url_http: "http://{{ gitea_domain }}" + + tasks: + - name: Check Gitea container status + ansible.builtin.shell: | + cd {{ gitea_stack_path }} + docker compose ps gitea + register: gitea_status + changed_when: false + + - name: Check Traefik container status + ansible.builtin.shell: | + cd {{ traefik_stack_path }} + docker compose ps traefik + register: traefik_status + changed_when: false + + - name: Check if Gitea is in traefik-public network + ansible.builtin.shell: | + docker network inspect traefik-public --format '{{ '{{' }}range .Containers{{ '}}' }}{{ '{{' }}.Name{{ '}}' }} {{ '{{' }}end{{ '}}' }}' 2>/dev/null | grep -q gitea && echo "YES" || echo "NO" + register: gitea_in_network + changed_when: false + + - name: Test direct connection from Traefik to Gitea (by service name) + ansible.builtin.shell: | + cd {{ traefik_stack_path }} + docker compose exec -T traefik wget -qO- --timeout=5 http://gitea:3000/api/healthz 2>&1 || echo "CONNECTION_FAILED" + register: traefik_gitea_direct + changed_when: false + failed_when: false + + - name: Check Traefik logs for SSL/ACME errors + ansible.builtin.shell: | + cd {{ traefik_stack_path }} + docker compose logs traefik --tail=100 2>&1 | grep -iE "acme|certificate|git\.michaelschiemer\.de|ssl|tls" | tail -20 || echo "No SSL/ACME errors found" + register: traefik_ssl_errors + changed_when: false + failed_when: false + + - name: Check if SSL certificate exists for git.michaelschiemer.de + ansible.builtin.shell: | + cd {{ traefik_stack_path }} + docker compose exec -T traefik cat /acme.json 2>/dev/null | grep -q "git.michaelschiemer.de" && echo "YES" || echo "NO" + register: ssl_cert_exists + changed_when: false + failed_when: false + + - name: Test Gitea via HTTP (port 80, should redirect or show error) + ansible.builtin.uri: + url: "{{ gitea_url_http }}/api/healthz" + method: GET + status_code: [200, 301, 302, 404, 502, 503, 504] + validate_certs: false + timeout: 10 + register: gitea_http_test + changed_when: false + failed_when: false + + - name: Test Gitea via HTTPS + ansible.builtin.uri: + url: "{{ gitea_url }}/api/healthz" + method: GET + status_code: [200, 301, 302, 404, 502, 503, 504] + validate_certs: false + timeout: 10 + register: gitea_https_test + changed_when: false + failed_when: false + + - name: Display diagnostic information + ansible.builtin.debug: + msg: | + ================================================================================ + GITEA SSL/ROUTING DIAGNOSE: + ================================================================================ + + Container Status: + - Gitea: {{ gitea_status.stdout | regex_replace('.*(Up|Down|Restarting).*', '\\1') | default('UNKNOWN') }} + - Traefik: {{ traefik_status.stdout | regex_replace('.*(Up|Down|Restarting).*', '\\1') | default('UNKNOWN') }} + + Network: + - Gitea in traefik-public: {% if gitea_in_network.stdout == 'YES' %}✅{% else %}❌{% endif %} + - Traefik → Gitea (direct): {% if 'CONNECTION_FAILED' not in traefik_gitea_direct.stdout %}✅{% else %}❌{% endif %} + + SSL/Certificate: + - Certificate in acme.json: {% if ssl_cert_exists.stdout == 'YES' %}✅{% else %}❌{% endif %} + + Connectivity: + - HTTP (port 80): Status {{ gitea_http_test.status | default('TIMEOUT') }} + - HTTPS (port 443): Status {{ gitea_https_test.status | default('TIMEOUT') }} + + Traefik SSL/ACME Errors: + {{ traefik_ssl_errors.stdout }} + + ================================================================================ + + - name: Restart Gitea if not in network or connection failed + ansible.builtin.shell: | + cd {{ gitea_stack_path }} + docker compose restart gitea + register: gitea_restart + changed_when: gitea_restart.rc == 0 + when: gitea_in_network.stdout != 'YES' or 'CONNECTION_FAILED' in traefik_gitea_direct.stdout + + - name: Wait for Gitea to be ready after restart + ansible.builtin.pause: + seconds: 30 + when: gitea_restart.changed | default(false) + + - name: Restart Traefik to refresh service discovery and SSL + ansible.builtin.shell: | + cd {{ traefik_stack_path }} + docker compose restart traefik + register: traefik_restart + changed_when: traefik_restart.rc == 0 + when: > + (traefik_auto_restart | default(false) | bool) + and (gitea_restart.changed | default(false) or gitea_https_test.status | default(0) != 200) + + - name: Wait for Traefik to be ready after restart + ansible.builtin.pause: + seconds: 15 + when: traefik_restart.changed | default(false) + + - name: Wait for Gitea to be reachable via HTTPS (with retries) + ansible.builtin.uri: + url: "{{ gitea_url }}/api/healthz" + method: GET + status_code: [200] + validate_certs: false + timeout: 10 + register: final_gitea_test + until: final_gitea_test.status == 200 + retries: 20 + delay: 3 + changed_when: false + failed_when: false + when: traefik_restart.changed | default(false) or gitea_restart.changed | default(false) + + - name: Final status check + ansible.builtin.uri: + url: "{{ gitea_url }}/api/healthz" + method: GET + status_code: [200] + validate_certs: false + timeout: 10 + register: final_status + changed_when: false + failed_when: false + + - name: Summary + ansible.builtin.debug: + msg: | + ================================================================================ + ZUSAMMENFASSUNG - Gitea SSL/Routing Fix: + ================================================================================ + + Aktionen: + - Gitea Restart: {% if gitea_restart.changed | default(false) %}✅ Durchgeführt{% else %}ℹ️ Nicht nötig{% endif %} + - Traefik Restart: {% if traefik_restart.changed | default(false) %}✅ Durchgeführt{% else %}ℹ️ Nicht nötig{% endif %} + + Final Status: + - Gitea via HTTPS: {% if final_status.status == 200 %}✅ Erreichbar{% else %}❌ Nicht erreichbar (Status: {{ final_status.status | default('TIMEOUT') }}){% endif %} + + {% if final_status.status == 200 %} + ✅ Gitea ist jetzt über Traefik erreichbar! + URL: {{ gitea_url }} + {% else %} + ⚠️ Gitea ist noch nicht erreichbar + + Mögliche Ursachen: + 1. SSL-Zertifikat wird noch generiert (ACME Challenge läuft) + 2. Traefik Service Discovery braucht mehr Zeit + 3. Netzwerk-Problem zwischen Traefik und Gitea + + Nächste Schritte: + 1. Warte 2-5 Minuten und teste erneut: curl -k {{ gitea_url }}/api/healthz + 2. Prüfe Traefik-Logs: cd {{ traefik_stack_path }} && docker compose logs traefik --tail=50 + 3. Prüfe Gitea-Logs: cd {{ gitea_stack_path }} && docker compose logs gitea --tail=50 + 4. Prüfe Netzwerk: docker network inspect traefik-public | grep -A 5 gitea + {% endif %} + + ================================================================================ + diff --git a/deployment/ansible/playbooks/fix-gitea-timeouts.yml b/deployment/ansible/playbooks/fix-gitea-timeouts.yml new file mode 100644 index 00000000..332b6cc6 --- /dev/null +++ b/deployment/ansible/playbooks/fix-gitea-timeouts.yml @@ -0,0 +1,157 @@ +--- +# Fix Gitea Timeouts +# Startet Gitea und Traefik neu, um Timeout-Probleme zu beheben +- name: Fix Gitea Timeouts + hosts: production + gather_facts: yes + become: no + + tasks: + - name: Check Gitea container status before restart + ansible.builtin.shell: | + cd /home/deploy/deployment/stacks/gitea + docker compose ps gitea + register: gitea_status_before + changed_when: false + + - name: Display Gitea status before restart + ansible.builtin.debug: + msg: | + ================================================================================ + Gitea Status (Before Restart): + ================================================================================ + {{ gitea_status_before.stdout }} + ================================================================================ + + - name: Check Traefik container status before restart + ansible.builtin.shell: | + cd /home/deploy/deployment/stacks/traefik + docker compose ps traefik + register: traefik_status_before + changed_when: false + + - name: Display Traefik status before restart + ansible.builtin.debug: + msg: | + ================================================================================ + Traefik Status (Before Restart): + ================================================================================ + {{ traefik_status_before.stdout }} + ================================================================================ + + - name: Restart Gitea container + ansible.builtin.shell: | + cd /home/deploy/deployment/stacks/gitea + docker compose restart gitea + register: gitea_restart + changed_when: gitea_restart.rc == 0 + + - name: Wait for Gitea to be ready + ansible.builtin.uri: + url: "https://git.michaelschiemer.de/api/healthz" + method: GET + status_code: [200] + validate_certs: false + timeout: 10 + register: gitea_health_after_restart + until: gitea_health_after_restart.status == 200 + retries: 30 + delay: 2 + changed_when: false + failed_when: false + + - name: Display Gitea health after restart + ansible.builtin.debug: + msg: | + ================================================================================ + Gitea Health After Restart: + ================================================================================ + {% if gitea_health_after_restart.status == 200 %} + ✅ Gitea is healthy after restart + {% else %} + ⚠️ Gitea health check failed after restart (Status: {{ gitea_health_after_restart.status | default('TIMEOUT') }}) + {% endif %} + ================================================================================ + + - name: Restart Traefik to refresh service discovery + ansible.builtin.shell: | + cd /home/deploy/deployment/stacks/traefik + docker compose restart traefik + register: traefik_restart + changed_when: traefik_restart.rc == 0 + when: traefik_auto_restart | default(false) | bool + + - name: Wait for Traefik to be ready + ansible.builtin.wait_for: + timeout: 30 + delay: 2 + changed_when: false + + - name: Wait for Gitea to be reachable via Traefik + ansible.builtin.uri: + url: "https://git.michaelschiemer.de/api/healthz" + method: GET + status_code: [200] + validate_certs: false + timeout: 10 + register: gitea_health_via_traefik + until: gitea_health_via_traefik.status == 200 + retries: 30 + delay: 2 + changed_when: false + failed_when: false + + - name: Check final Gitea container status + ansible.builtin.shell: | + cd /home/deploy/deployment/stacks/gitea + docker compose ps gitea + register: gitea_status_after + changed_when: false + + - name: Check final Traefik container status + ansible.builtin.shell: | + cd /home/deploy/deployment/stacks/traefik + docker compose ps traefik + register: traefik_status_after + changed_when: false + + - name: Test Gitea access via Traefik + ansible.builtin.uri: + url: "https://git.michaelschiemer.de/api/healthz" + method: GET + status_code: [200] + validate_certs: false + timeout: 10 + register: final_gitea_test + changed_when: false + failed_when: false + + - name: Summary + ansible.builtin.debug: + msg: | + ================================================================================ + ZUSAMMENFASSUNG - Gitea Timeout Fix: + ================================================================================ + + Gitea Restart: {% if gitea_restart.changed %}✅ Durchgeführt{% else %}ℹ️ Nicht nötig{% endif %} + Traefik Restart: {% if traefik_restart.changed %}✅ Durchgeführt{% else %}ℹ️ Nicht nötig{% endif %} + + Final Status: + - Gitea: {{ gitea_status_after.stdout | regex_replace('.*(Up|Down|Restarting).*', '\\1') | default('UNKNOWN') }} + - Traefik: {{ traefik_status_after.stdout | regex_replace('.*(Up|Down|Restarting).*', '\\1') | default('UNKNOWN') }} + - Gitea via Traefik: {% if final_gitea_test.status == 200 %}✅ Erreichbar{% else %}❌ Nicht erreichbar (Status: {{ final_gitea_test.status | default('TIMEOUT') }}){% endif %} + + {% if final_gitea_test.status == 200 %} + ✅ Gitea ist jetzt über Traefik erreichbar! + URL: https://git.michaelschiemer.de + {% else %} + ⚠️ Gitea ist noch nicht über Traefik erreichbar + + Nächste Schritte: + 1. Prüfe Gitea-Logs: cd /home/deploy/deployment/stacks/gitea && docker compose logs gitea --tail=50 + 2. Prüfe Traefik-Logs: cd /home/deploy/deployment/stacks/traefik && docker compose logs traefik --tail=50 + 3. Prüfe Netzwerk: docker network inspect traefik-public | grep -A 5 gitea + 4. Führe diagnose-gitea-timeouts.yml aus für detaillierte Diagnose + {% endif %} + + ================================================================================ diff --git a/deployment/ansible/playbooks/redeploy-traefik-gitea.yml b/deployment/ansible/playbooks/redeploy-traefik-gitea.yml new file mode 100644 index 00000000..c2113b60 --- /dev/null +++ b/deployment/ansible/playbooks/redeploy-traefik-gitea.yml @@ -0,0 +1,363 @@ +--- +# Redeploy Traefik and Gitea Stacks +# Purpose: Clean redeployment of Traefik and Gitea stacks to fix service discovery issues +# This playbook: +# - Stops and removes containers (but keeps volumes and acme.json) +# - Redeploys both stacks with fresh containers +# - Reinitializes service discovery +# - Verifies everything works +# +# Usage: +# ansible-playbook -i inventory/production.yml playbooks/redeploy-traefik-gitea.yml \ +# --vault-password-file secrets/.vault_pass + +- name: Redeploy Traefik and Gitea Stacks + hosts: production + gather_facts: yes + become: no + + vars: + traefik_stack_path: "{{ stacks_base_path }}/traefik" + gitea_stack_path: "{{ stacks_base_path }}/gitea" + gitea_url: "https://{{ gitea_domain }}" + traefik_container_name: "traefik" + gitea_container_name: "gitea" + + tasks: + # ======================================== + # 1. PREPARATION + # ======================================== + + - name: Display redeployment plan + ansible.builtin.debug: + msg: | + ================================================================================ + TRAEFIK + GITEA REDEPLOYMENT PLAN + ================================================================================ + + This playbook will: + 1. ✅ Sync latest stack configurations + 2. ✅ Stop and remove Traefik containers (keeps acme.json) + 3. ✅ Stop and remove Gitea containers (keeps volumes/data) + 4. ✅ Redeploy Traefik stack + 5. ✅ Redeploy Gitea stack + 6. ✅ Verify service discovery + 7. ✅ Test Gitea accessibility + + ⚠️ IMPORTANT: + - SSL certificates (acme.json) will be preserved + - Gitea data (volumes) will be preserved + - Only containers will be recreated + - Expected downtime: ~2-5 minutes + + ================================================================================ + + - name: Sync infrastructure stacks to server + ansible.builtin.include_role: + name: traefik + tasks_from: deploy + vars: + traefik_auto_restart: false # Don't restart during sync + when: false # Skip for now, we'll do it manually + + - name: Sync stacks directory to production server + ansible.builtin.synchronize: + src: "{{ playbook_dir }}/../../stacks/" + dest: "{{ stacks_base_path }}/" + delete: no + recursive: yes + rsync_opts: + - "--chmod=D755,F644" + - "--exclude=.git" + - "--exclude=*.log" + - "--exclude=data/" + - "--exclude=volumes/" + - "--exclude=acme.json" # Preserve SSL certificates + - "--exclude=*.key" + - "--exclude=*.pem" + + # ======================================== + # 2. TRAEFIK REDEPLOYMENT + # ======================================== + + - name: Check Traefik container status (before) + ansible.builtin.shell: | + cd {{ traefik_stack_path }} + docker compose ps {{ traefik_container_name }} 2>/dev/null || echo "NOT_RUNNING" + register: traefik_status_before + changed_when: false + + - name: Display Traefik status (before) + ansible.builtin.debug: + msg: | + Traefik Status (Before): + {{ traefik_status_before.stdout }} + + - name: Check if acme.json exists + ansible.builtin.stat: + path: "{{ traefik_stack_path }}/acme.json" + register: acme_json_stat + + - name: Backup acme.json (safety measure) + ansible.builtin.copy: + src: "{{ traefik_stack_path }}/acme.json" + dest: "{{ traefik_stack_path }}/acme.json.backup.{{ ansible_date_time.epoch }}" + remote_src: yes + mode: '0600' + when: acme_json_stat.stat.exists + register: acme_backup + failed_when: false + changed_when: acme_backup.rc == 0 + + - name: Stop Traefik stack + ansible.builtin.shell: | + cd {{ traefik_stack_path }} + docker compose down + register: traefik_stop + changed_when: traefik_stop.rc == 0 + failed_when: false + + - name: Remove Traefik containers (if any remain) + ansible.builtin.shell: | + docker ps -a --filter "name={{ traefik_container_name }}" --format "{{ '{{' }}.ID{{ '}}' }}" | xargs -r docker rm -f 2>/dev/null || true + register: traefik_remove + changed_when: traefik_remove.rc == 0 + failed_when: false + + - name: Ensure acme.json exists and has correct permissions + ansible.builtin.file: + path: "{{ traefik_stack_path }}/acme.json" + state: touch + mode: '0600' + owner: "{{ ansible_user }}" + group: "{{ ansible_user }}" + become: yes + register: acme_json_ensure + + - name: Check if acme.json exists after ensure + ansible.builtin.stat: + path: "{{ traefik_stack_path }}/acme.json" + register: acme_json_after_ensure + + - name: Restore acme.json from backup if it was deleted + ansible.builtin.copy: + src: "{{ traefik_stack_path }}/acme.json.backup.{{ ansible_date_time.epoch }}" + dest: "{{ traefik_stack_path }}/acme.json" + remote_src: yes + mode: '0600' + when: + - acme_backup.changed | default(false) + - acme_json_stat.stat.exists + - not acme_json_after_ensure.stat.exists + failed_when: false + + - name: Deploy Traefik stack + community.docker.docker_compose_v2: + project_src: "{{ traefik_stack_path }}" + state: present + pull: always + register: traefik_deploy + + - name: Wait for Traefik to be ready + ansible.builtin.shell: | + cd {{ traefik_stack_path }} + docker compose ps {{ traefik_container_name }} | grep -Eiq "Up|running" + register: traefik_ready + changed_when: false + until: traefik_ready.rc == 0 + retries: 12 + delay: 5 + failed_when: traefik_ready.rc != 0 + + - name: Check Traefik container status (after) + ansible.builtin.shell: | + cd {{ traefik_stack_path }} + docker compose ps {{ traefik_container_name }} + register: traefik_status_after + changed_when: false + + - name: Display Traefik status (after) + ansible.builtin.debug: + msg: | + Traefik Status (After): + {{ traefik_status_after.stdout }} + + # ======================================== + # 3. GITEA REDEPLOYMENT + # ======================================== + + - name: Check Gitea container status (before) + ansible.builtin.shell: | + cd {{ gitea_stack_path }} + docker compose ps {{ gitea_container_name }} 2>/dev/null || echo "NOT_RUNNING" + register: gitea_status_before + changed_when: false + + - name: Display Gitea status (before) + ansible.builtin.debug: + msg: | + Gitea Status (Before): + {{ gitea_status_before.stdout }} + + - name: Stop Gitea stack (preserves volumes) + ansible.builtin.shell: | + cd {{ gitea_stack_path }} + docker compose down + register: gitea_stop + changed_when: gitea_stop.rc == 0 + failed_when: false + + - name: Remove Gitea containers (if any remain, volumes are preserved) + ansible.builtin.shell: | + docker ps -a --filter "name={{ gitea_container_name }}" --format "{{ '{{' }}.ID{{ '}}' }}" | xargs -r docker rm -f 2>/dev/null || true + register: gitea_remove + changed_when: gitea_remove.rc == 0 + failed_when: false + + - name: Deploy Gitea stack + community.docker.docker_compose_v2: + project_src: "{{ gitea_stack_path }}" + state: present + pull: always + register: gitea_deploy + + - name: Wait for Gitea to be ready + ansible.builtin.shell: | + cd {{ gitea_stack_path }} + docker compose ps {{ gitea_container_name }} | grep -Eiq "Up|running" + register: gitea_ready + changed_when: false + until: gitea_ready.rc == 0 + retries: 12 + delay: 5 + failed_when: gitea_ready.rc != 0 + + - name: Wait for Gitea to be healthy + ansible.builtin.shell: | + cd {{ gitea_stack_path }} + docker compose exec -T {{ gitea_container_name }} curl -f http://localhost:3000/api/healthz 2>&1 | grep -q "status.*pass" && echo "HEALTHY" || echo "NOT_HEALTHY" + register: gitea_health + changed_when: false + until: gitea_health.stdout == "HEALTHY" + retries: 30 + delay: 2 + failed_when: false + + - name: Check Gitea container status (after) + ansible.builtin.shell: | + cd {{ gitea_stack_path }} + docker compose ps {{ gitea_container_name }} + register: gitea_status_after + changed_when: false + + - name: Display Gitea status (after) + ansible.builtin.debug: + msg: | + Gitea Status (After): + {{ gitea_status_after.stdout }} + + # ======================================== + # 4. SERVICE DISCOVERY VERIFICATION + # ======================================== + + - name: Wait for Traefik to discover Gitea (service discovery delay) + ansible.builtin.pause: + seconds: 15 + + - name: Check if Gitea is in traefik-public network + ansible.builtin.shell: | + docker network inspect traefik-public --format '{{ '{{' }}range .Containers{{ '}}' }}{{ '{{' }}.Name{{ '}}' }} {{ '{{' }}end{{ '}}' }}' 2>/dev/null | grep -q {{ gitea_container_name }} && echo "YES" || echo "NO" + register: gitea_in_network + changed_when: false + + - name: Check if Traefik is in traefik-public network + ansible.builtin.shell: | + docker network inspect traefik-public --format '{{ '{{' }}range .Containers{{ '}}' }}{{ '{{' }}.Name{{ '}}' }} {{ '{{' }}end{{ '}}' }}' 2>/dev/null | grep -q {{ traefik_container_name }} && echo "YES" || echo "NO" + register: traefik_in_network + changed_when: false + + - name: Test direct connection from Traefik to Gitea + ansible.builtin.shell: | + cd {{ traefik_stack_path }} + docker compose exec -T {{ traefik_container_name }} wget -qO- --timeout=5 http://{{ gitea_container_name }}:3000/api/healthz 2>&1 | head -5 || echo "CONNECTION_FAILED" + register: traefik_gitea_direct + changed_when: false + failed_when: false + + - name: Display network status + ansible.builtin.debug: + msg: | + Network Status: + - Gitea in traefik-public: {% if gitea_in_network.stdout == 'YES' %}✅{% else %}❌{% endif %} + - Traefik in traefik-public: {% if traefik_in_network.stdout == 'YES' %}✅{% else %}❌{% endif %} + - Traefik → Gitea (direct): {% if 'CONNECTION_FAILED' not in traefik_gitea_direct.stdout %}✅{% else %}❌{% endif %} + + # ======================================== + # 5. FINAL VERIFICATION + # ======================================== + + - name: Test Gitea via HTTPS (with retries) + ansible.builtin.uri: + url: "{{ gitea_url }}/api/healthz" + method: GET + status_code: [200] + validate_certs: false + timeout: 10 + register: gitea_https_test + until: gitea_https_test.status == 200 + retries: 20 + delay: 3 + changed_when: false + failed_when: false + + - name: Check SSL certificate status + ansible.builtin.shell: | + cd {{ traefik_stack_path }} + if [ -f acme.json ] && [ -s acme.json ]; then + echo "SSL certificates: PRESENT" + else + echo "SSL certificates: MISSING or EMPTY" + fi + register: ssl_status + changed_when: false + + - name: Final status summary + ansible.builtin.debug: + msg: | + ================================================================================ + REDEPLOYMENT SUMMARY + ================================================================================ + + Traefik: + - Status: {{ traefik_status_after.stdout | regex_replace('.*(Up|Down|Restarting).*', '\\1') | default('UNKNOWN') }} + - SSL Certificates: {{ ssl_status.stdout }} + + Gitea: + - Status: {{ gitea_status_after.stdout | regex_replace('.*(Up|Down|Restarting).*', '\\1') | default('UNKNOWN') }} + - Health: {% if gitea_health.stdout == 'HEALTHY' %}✅ Healthy{% else %}❌ Not Healthy{% endif %} + + Service Discovery: + - Gitea in network: {% if gitea_in_network.stdout == 'YES' %}✅{% else %}❌{% endif %} + - Traefik in network: {% if traefik_in_network.stdout == 'YES' %}✅{% else %}❌{% endif %} + - Direct connection: {% if 'CONNECTION_FAILED' not in traefik_gitea_direct.stdout %}✅{% else %}❌{% endif %} + + Gitea Accessibility: + {% if gitea_https_test.status == 200 %} + ✅ Gitea is reachable via HTTPS (Status: 200) + URL: {{ gitea_url }} + {% else %} + ❌ Gitea is NOT reachable via HTTPS (Status: {{ gitea_https_test.status | default('TIMEOUT') }}) + + Possible causes: + 1. SSL certificate is still being generated (wait 2-5 minutes) + 2. Service discovery needs more time (wait 1-2 minutes) + 3. Network configuration issue + + Next steps: + - Wait 2-5 minutes and test again: curl -k {{ gitea_url }}/api/healthz + - Check Traefik logs: cd {{ traefik_stack_path }} && docker compose logs traefik --tail=50 + - Check Gitea logs: cd {{ gitea_stack_path }} && docker compose logs gitea --tail=50 + {% endif %} + + ================================================================================ +