Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
269 lines
11 KiB
YAML
269 lines
11 KiB
YAML
---
|
|
- name: Create Comprehensive Backups
|
|
hosts: production
|
|
gather_facts: yes
|
|
become: no
|
|
|
|
vars:
|
|
backup_retention_days: "{{ backup_retention_days | default(7) }}"
|
|
|
|
pre_tasks:
|
|
- name: Ensure backup directory exists
|
|
file:
|
|
path: "{{ backups_path }}"
|
|
state: directory
|
|
mode: '0755'
|
|
become: yes
|
|
|
|
- name: Create timestamp for backup
|
|
set_fact:
|
|
backup_timestamp: "{{ ansible_date_time.epoch }}"
|
|
backup_date: "{{ ansible_date_time.date }}"
|
|
backup_time: "{{ ansible_date_time.time }}"
|
|
|
|
- name: Create backup directory for this run
|
|
file:
|
|
path: "{{ backups_path }}/backup_{{ backup_date }}_{{ backup_time }}"
|
|
state: directory
|
|
mode: '0755'
|
|
register: backup_dir
|
|
become: yes
|
|
|
|
- name: Set backup directory path
|
|
set_fact:
|
|
current_backup_dir: "{{ backup_dir.path }}"
|
|
|
|
tasks:
|
|
- name: Backup PostgreSQL Database
|
|
when: backup_postgresql | default(true) | bool
|
|
block:
|
|
- name: Check if PostgreSQL stack is running
|
|
shell: docker compose -f {{ stacks_base_path }}/postgresql/docker-compose.yml ps --format json | jq -r '.[] | select(.Service=="postgres") | .State' | grep -q "running"
|
|
register: postgres_running
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Get PostgreSQL container name
|
|
shell: docker compose -f {{ stacks_base_path }}/postgresql/docker-compose.yml ps --format json | jq -r '.[] | select(.Service=="postgres") | .Name'
|
|
register: postgres_container
|
|
changed_when: false
|
|
when: postgres_running.rc == 0
|
|
|
|
- name: Read PostgreSQL environment variables
|
|
shell: |
|
|
cd {{ stacks_base_path }}/postgresql
|
|
grep -E "^POSTGRES_(DB|USER|PASSWORD)=" .env 2>/dev/null || echo ""
|
|
register: postgres_env
|
|
changed_when: false
|
|
failed_when: false
|
|
no_log: true
|
|
|
|
- name: Extract PostgreSQL credentials
|
|
set_fact:
|
|
postgres_db: "{{ postgres_env.stdout | regex_search('POSTGRES_DB=([^\\n]+)', '\\1') | first | default('michaelschiemer') }}"
|
|
postgres_user: "{{ postgres_env.stdout | regex_search('POSTGRES_USER=([^\\n]+)', '\\1') | first | default('postgres') }}"
|
|
postgres_password: "{{ postgres_env.stdout | regex_search('POSTGRES_PASSWORD=([^\\n]+)', '\\1') | first | default('') }}"
|
|
when: postgres_running.rc == 0
|
|
no_log: true
|
|
|
|
- name: Create PostgreSQL backup
|
|
shell: |
|
|
cd {{ stacks_base_path }}/postgresql
|
|
PGPASSWORD="{{ postgres_password }}" docker compose exec -T postgres pg_dump \
|
|
-U {{ postgres_user }} \
|
|
-d {{ postgres_db }} \
|
|
--clean \
|
|
--if-exists \
|
|
--create \
|
|
--no-owner \
|
|
--no-privileges \
|
|
| gzip > {{ current_backup_dir }}/postgresql_${postgres_db}_{{ backup_date }}_{{ backup_time }}.sql.gz
|
|
when: postgres_running.rc == 0
|
|
no_log: true
|
|
|
|
- name: Verify PostgreSQL backup
|
|
stat:
|
|
path: "{{ current_backup_dir }}/postgresql_{{ postgres_db }}_{{ backup_date }}_{{ backup_time }}.sql.gz"
|
|
register: postgres_backup_file
|
|
when: postgres_running.rc == 0
|
|
|
|
- name: Display PostgreSQL backup status
|
|
debug:
|
|
msg: "PostgreSQL backup: {{ 'SUCCESS' if (postgres_running.rc == 0 and postgres_backup_file.stat.exists) else 'SKIPPED (PostgreSQL not running)' }}"
|
|
|
|
- name: Backup Application Data
|
|
when: backup_application_data | default(true) | bool
|
|
block:
|
|
- name: Check if production stack is running
|
|
shell: docker compose -f {{ stacks_base_path }}/production/docker-compose.base.yml -f {{ stacks_base_path }}/production/docker-compose.production.yml ps --format json | jq -r '.[] | select(.Service=="php") | .State' | grep -q "running"
|
|
register: app_running
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Backup application storage directory
|
|
archive:
|
|
path: "{{ stacks_base_path }}/production/storage"
|
|
dest: "{{ current_backup_dir }}/application_storage_{{ backup_date }}_{{ backup_time }}.tar.gz"
|
|
format: gz
|
|
when: app_running.rc == 0
|
|
ignore_errors: yes
|
|
|
|
- name: Backup application logs
|
|
archive:
|
|
path: "{{ stacks_base_path }}/production/storage/logs"
|
|
dest: "{{ current_backup_dir }}/application_logs_{{ backup_date }}_{{ backup_time }}.tar.gz"
|
|
format: gz
|
|
when: app_running.rc == 0
|
|
ignore_errors: yes
|
|
|
|
- name: Backup application .env file
|
|
copy:
|
|
src: "{{ stacks_base_path }}/production/.env"
|
|
dest: "{{ current_backup_dir }}/application_env_{{ backup_date }}_{{ backup_time }}.env"
|
|
remote_src: yes
|
|
when: app_running.rc == 0
|
|
ignore_errors: yes
|
|
|
|
- name: Display application backup status
|
|
debug:
|
|
msg: "Application data backup: {{ 'SUCCESS' if app_running.rc == 0 else 'SKIPPED (Application not running)' }}"
|
|
|
|
- name: Backup Gitea Data
|
|
when: backup_gitea | default(true) | bool
|
|
block:
|
|
- name: Check if Gitea stack is running
|
|
shell: docker compose -f {{ stacks_base_path }}/gitea/docker-compose.yml ps --format json | jq -r '.[] | select(.Service=="gitea") | .State' | grep -q "running"
|
|
register: gitea_running
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Get Gitea volume name
|
|
shell: docker compose -f {{ stacks_base_path }}/gitea/docker-compose.yml config --volumes | head -1
|
|
register: gitea_volume
|
|
changed_when: false
|
|
when: gitea_running.rc == 0
|
|
|
|
- name: Backup Gitea volume
|
|
shell: |
|
|
docker run --rm \
|
|
-v {{ gitea_volume.stdout }}:/source:ro \
|
|
-v {{ current_backup_dir }}:/backup \
|
|
alpine tar czf /backup/gitea_data_{{ backup_date }}_{{ backup_time }}.tar.gz -C /source .
|
|
when: gitea_running.rc == 0 and gitea_volume.stdout != ""
|
|
ignore_errors: yes
|
|
|
|
- name: Display Gitea backup status
|
|
debug:
|
|
msg: "Gitea backup: {{ 'SUCCESS' if (gitea_running.rc == 0 and gitea_volume.stdout != '') else 'SKIPPED (Gitea not running)' }}"
|
|
|
|
- name: Backup Docker Registry Images (Optional)
|
|
when: backup_registry | default(false) | bool
|
|
block:
|
|
- name: Check if registry stack is running
|
|
shell: docker compose -f {{ stacks_base_path }}/registry/docker-compose.yml ps --format json | jq -r '.[] | select(.Service=="registry") | .State' | grep -q "running"
|
|
register: registry_running
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: List registry images
|
|
shell: |
|
|
cd {{ stacks_base_path }}/registry
|
|
docker compose exec -T registry registry garbage-collect --dry-run /etc/docker/registry/config.yml 2>&1 | grep -E "repository|tag" || echo "No images found"
|
|
register: registry_images
|
|
changed_when: false
|
|
when: registry_running.rc == 0
|
|
ignore_errors: yes
|
|
|
|
- name: Save registry image list
|
|
copy:
|
|
content: "{{ registry_images.stdout }}"
|
|
dest: "{{ current_backup_dir }}/registry_images_{{ backup_date }}_{{ backup_time }}.txt"
|
|
when: registry_running.rc == 0 and registry_images.stdout != ""
|
|
ignore_errors: yes
|
|
|
|
- name: Display registry backup status
|
|
debug:
|
|
msg: "Registry backup: {{ 'SUCCESS' if registry_running.rc == 0 else 'SKIPPED (Registry not running)' }}"
|
|
|
|
- name: Create backup metadata
|
|
copy:
|
|
content: |
|
|
Backup Date: {{ backup_date }} {{ backup_time }}
|
|
Backup Timestamp: {{ backup_timestamp }}
|
|
Host: {{ inventory_hostname }}
|
|
|
|
Components Backed Up:
|
|
- PostgreSQL: {{ 'YES' if ((backup_postgresql | default(true) | bool) and (postgres_running.rc | default(1) == 0)) else 'NO' }}
|
|
- Application Data: {{ 'YES' if ((backup_application_data | default(true) | bool) and (app_running.rc | default(1) == 0)) else 'NO' }}
|
|
- Gitea: {{ 'YES' if ((backup_gitea | default(true) | bool) and (gitea_running.rc | default(1) == 0)) else 'NO' }}
|
|
- Registry: {{ 'YES' if ((backup_registry | default(false) | bool) and (registry_running.rc | default(1) == 0)) else 'NO' }}
|
|
|
|
Backup Location: {{ current_backup_dir }}
|
|
dest: "{{ current_backup_dir }}/backup_metadata.txt"
|
|
mode: '0644'
|
|
|
|
- name: Verify backup files
|
|
when: verify_backups | default(true) | bool
|
|
block:
|
|
- name: List all backup files
|
|
find:
|
|
paths: "{{ current_backup_dir }}"
|
|
file_type: file
|
|
register: backup_files
|
|
|
|
- name: Check backup file sizes
|
|
stat:
|
|
path: "{{ item.path }}"
|
|
register: backup_file_stats
|
|
loop: "{{ backup_files.files }}"
|
|
|
|
- name: Display backup summary
|
|
debug:
|
|
msg: |
|
|
Backup Summary:
|
|
- Total files: {{ backup_files.files | length }}
|
|
- Total size: {{ backup_file_stats.results | map(attribute='stat.size') | sum | int / 1024 / 1024 }} MB
|
|
- Location: {{ current_backup_dir }}
|
|
|
|
- name: Fail if no backup files created
|
|
fail:
|
|
msg: "No backup files were created in {{ current_backup_dir }}"
|
|
when: backup_files.files | length == 0
|
|
|
|
- name: Cleanup old backups
|
|
block:
|
|
- name: Find old backup directories
|
|
find:
|
|
paths: "{{ backups_path }}"
|
|
patterns: "backup_*"
|
|
file_type: directory
|
|
register: backup_dirs
|
|
|
|
- name: Calculate cutoff date
|
|
set_fact:
|
|
cutoff_timestamp: "{{ (ansible_date_time.epoch | int) - (backup_retention_days | int * 86400) }}"
|
|
|
|
- name: Remove old backup directories
|
|
file:
|
|
path: "{{ item.path }}"
|
|
state: absent
|
|
loop: "{{ backup_dirs.files }}"
|
|
when: item.mtime | int < cutoff_timestamp | int
|
|
become: yes
|
|
|
|
- name: Display cleanup summary
|
|
debug:
|
|
msg: "Cleaned up backups older than {{ backup_retention_days }} days"
|
|
|
|
post_tasks:
|
|
- name: Display final backup status
|
|
debug:
|
|
msg: |
|
|
==========================================
|
|
Backup completed successfully!
|
|
==========================================
|
|
Backup location: {{ current_backup_dir }}
|
|
Retention: {{ backup_retention_days }} days
|
|
==========================================
|
|
|