--- # Rollback Redeploy # Restores Traefik and Gitea from backup created before redeploy # # Usage: # ansible-playbook -i inventory/production.yml playbooks/maintenance/rollback-redeploy.yml \ # --vault-password-file secrets/.vault_pass \ # -e "backup_name=redeploy-backup-1234567890" - name: Rollback Redeploy hosts: production gather_facts: yes become: no vars: traefik_stack_path: "{{ stacks_base_path }}/traefik" gitea_stack_path: "{{ stacks_base_path }}/gitea" backup_base_path: "{{ backups_path | default('/home/deploy/backups') }}" backup_name: "{{ backup_name | default('') }}" tasks: - name: Validate backup name ansible.builtin.fail: msg: "backup_name is required. Use: -e 'backup_name=redeploy-backup-1234567890'" when: backup_name == "" - name: Check if backup directory exists ansible.builtin.stat: path: "{{ backup_base_path }}/{{ backup_name }}" register: backup_dir_stat - name: Fail if backup not found ansible.builtin.fail: msg: "Backup directory not found: {{ backup_base_path }}/{{ backup_name }}" when: not backup_dir_stat.stat.exists - name: Display rollback plan ansible.builtin.debug: msg: | ================================================================================ ROLLBACK REDEPLOY ================================================================================ This playbook will restore from backup: {{ backup_base_path }}/{{ backup_name }} Steps: 1. Stop Traefik and Gitea stacks 2. Restore Gitea volumes 3. Restore SSL certificates (acme.json) 4. Restore Gitea configuration (app.ini) 5. Restore Traefik configuration 6. Restore PostgreSQL data (if applicable) 7. Restart stacks 8. Verify ⚠️ WARNING: This will overwrite current state! ================================================================================ # ======================================== # 1. STOP STACKS # ======================================== - 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: Stop Gitea stack ansible.builtin.shell: | cd {{ gitea_stack_path }} docker compose down register: gitea_stop changed_when: gitea_stop.rc == 0 failed_when: false # ======================================== # 2. RESTORE GITEA VOLUMES # ======================================== - name: List Gitea volume backups ansible.builtin.shell: | ls -1 "{{ backup_base_path }}/{{ backup_name }}/gitea-volume-"*.tar.gz 2>/dev/null || echo "" register: gitea_volume_backups changed_when: false - name: Restore Gitea volumes ansible.builtin.shell: | for backup_file in {{ backup_base_path }}/{{ backup_name }}/gitea-volume-*.tar.gz; do if [ -f "$backup_file" ]; then volume_name=$(basename "$backup_file" .tar.gz | sed 's/gitea-volume-//') echo "Restoring volume: $volume_name" docker volume create "$volume_name" 2>/dev/null || true docker run --rm \ -v "$volume_name:/target" \ -v "{{ backup_base_path }}/{{ backup_name }}:/backup:ro" \ alpine sh -c "cd /target && tar xzf /backup/$(basename $backup_file)" fi done when: gitea_volume_backups.stdout != "" register: gitea_volumes_restore changed_when: gitea_volumes_restore.rc == 0 # ======================================== # 3. RESTORE SSL CERTIFICATES # ======================================== - name: Restore acme.json ansible.builtin.copy: src: "{{ backup_base_path }}/{{ backup_name }}/acme.json" dest: "{{ traefik_stack_path }}/acme.json" remote_src: yes mode: '0600' register: acme_restore changed_when: acme_restore.rc == 0 # ======================================== # 4. RESTORE CONFIGURATIONS # ======================================== - name: Restore Gitea docker-compose.yml ansible.builtin.copy: src: "{{ backup_base_path }}/{{ backup_name }}/gitea-docker-compose.yml" dest: "{{ gitea_stack_path }}/docker-compose.yml" remote_src: yes mode: '0644' register: gitea_compose_restore changed_when: gitea_compose_restore.rc == 0 failed_when: false - name: Restore Traefik configuration ansible.builtin.shell: | cd {{ traefik_stack_path }} tar xzf "{{ backup_base_path }}/{{ backup_name }}/traefik-config.tar.gz" 2>/dev/null || echo "Some files may be missing" register: traefik_config_restore changed_when: traefik_config_restore.rc == 0 failed_when: false # ======================================== # 5. RESTORE POSTGRESQL DATA # ======================================== - name: Find PostgreSQL backup ansible.builtin.shell: | ls -1 "{{ backup_base_path }}/{{ backup_name }}/postgresql-all-"*.sql.gz 2>/dev/null | head -1 || echo "" register: postgres_backup_file changed_when: false - name: Restore PostgreSQL database ansible.builtin.shell: | cd {{ stacks_base_path }}/postgresql if docker compose ps postgres | grep -q "Up"; then gunzip -c "{{ postgres_backup_file.stdout }}" | docker compose exec -T postgres psql -U postgres echo "PostgreSQL restored" else echo "PostgreSQL not running, skipping restore" fi when: postgres_backup_file.stdout != "" register: postgres_restore changed_when: false failed_when: false # ======================================== # 6. RESTART STACKS # ======================================== - 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 | 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: Deploy Gitea stack community.docker.docker_compose_v2: project_src: "{{ gitea_stack_path }}" state: present pull: always register: gitea_deploy - name: Restore Gitea app.ini ansible.builtin.shell: | if [ -f "{{ backup_base_path }}/{{ backup_name }}/gitea-app.ini" ]; then cd {{ gitea_stack_path }} docker compose exec -T gitea sh -c "cat > /data/gitea/conf/app.ini" < "{{ backup_base_path }}/{{ backup_name }}/gitea-app.ini" docker compose restart gitea echo "app.ini restored and Gitea restarted" else echo "No app.ini backup found" fi register: gitea_app_ini_restore changed_when: false failed_when: false - name: Wait for Gitea to be ready ansible.builtin.shell: | cd {{ gitea_stack_path }} docker compose ps gitea | 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 # ======================================== # 7. VERIFY # ======================================== - name: Wait for Gitea to be healthy ansible.builtin.shell: | cd {{ gitea_stack_path }} docker compose exec -T gitea 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: Summary ansible.builtin.debug: msg: | ================================================================================ ROLLBACK SUMMARY ================================================================================ Restored from backup: {{ backup_base_path }}/{{ backup_name }} Restored: - Gitea volumes: {% if gitea_volumes_restore.changed %}✅{% else %}ℹ️ No volumes to restore{% endif %} - SSL certificates: {% if acme_restore.changed %}✅{% else %}ℹ️ Not found{% endif %} - Gitea docker-compose.yml: {% if gitea_compose_restore.changed %}✅{% else %}ℹ️ Not found{% endif %} - Traefik configuration: {% if traefik_config_restore.rc == 0 %}✅{% else %}⚠️ Some files may be missing{% endif %} - PostgreSQL data: {% if postgres_restore.rc == 0 and 'restored' in postgres_restore.stdout %}✅{% else %}ℹ️ Not restored{% endif %} - Gitea app.ini: {% if gitea_app_ini_restore.rc == 0 and 'restored' in gitea_app_ini_restore.stdout %}✅{% else %}ℹ️ Not found{% endif %} Status: - Traefik: {% if traefik_ready.rc == 0 %}✅ Running{% else %}❌ Not running{% endif %} - Gitea: {% if gitea_ready.rc == 0 %}✅ Running{% else %}❌ Not running{% endif %} - Gitea Health: {% if gitea_health.stdout == 'HEALTHY' %}✅ Healthy{% else %}❌ Not healthy{% endif %} Next steps: 1. Test Gitea: curl -k https://{{ gitea_domain }}/api/healthz 2. Check logs if issues: cd {{ gitea_stack_path }} && docker compose logs gitea --tail=50 ================================================================================