--- # Configuration Backup Playbook # Backup docker-compose files, configs, and important data # Usage: ansible-playbook playbooks/backup_configs.yml # Usage: ansible-playbook playbooks/backup_configs.yml --limit atlantis # Usage: ansible-playbook playbooks/backup_configs.yml -e "include_secrets=true" - name: Backup Configurations and Important Data hosts: "{{ host_target | default('all') }}" gather_facts: yes vars: backup_base_dir: "/volume1/backups/configs" # Synology path backup_local_dir: "/tmp/config_backups" # Configuration paths to backup per host config_paths: atlantis: - path: "/volume1/docker" name: "docker_configs" exclude: ["*/cache/*", "*/logs/*", "*/tmp/*"] - path: "/volume1/homes" name: "user_configs" exclude: ["*/Downloads/*", "*/Trash/*"] - path: "/etc/ssh" name: "ssh_config" exclude: ["ssh_host_*_key"] calypso: - path: "/volume1/docker" name: "docker_configs" exclude: ["*/cache/*", "*/logs/*", "*/tmp/*"] - path: "/etc/ssh" name: "ssh_config" exclude: ["ssh_host_*_key"] homelab_vm: - path: "/opt/docker" name: "docker_configs" exclude: ["*/cache/*", "*/logs/*", "*/tmp/*"] - path: "/etc/nginx" name: "nginx_config" exclude: [] - path: "/etc/ssh" name: "ssh_config" exclude: ["ssh_host_*_key"] concord_nuc: - path: "/opt/docker" name: "docker_configs" exclude: ["*/cache/*", "*/logs/*", "*/tmp/*"] - path: "/etc/ssh" name: "ssh_config" exclude: ["ssh_host_*_key"] # Important service data directories service_data: atlantis: - service: "immich" paths: ["/volume1/docker/immich/config"] - service: "vaultwarden" paths: ["/volume1/docker/vaultwarden/data"] - service: "plex" paths: ["/volume1/docker/plex/config"] calypso: - service: "authentik" paths: ["/volume1/docker/authentik/config"] - service: "paperless" paths: ["/volume1/docker/paperless/config"] tasks: - name: Create backup directories file: path: "{{ item }}" state: directory mode: '0755' loop: - "{{ backup_base_dir }}/{{ inventory_hostname }}" - "{{ backup_local_dir }}/{{ inventory_hostname }}" ignore_errors: yes - name: Get current config paths for this host set_fact: current_configs: "{{ config_paths.get(inventory_hostname, []) }}" current_service_data: "{{ service_data.get(inventory_hostname, []) }}" - name: Display backup plan debug: msg: | ๐Ÿ“Š CONFIGURATION BACKUP PLAN ============================= ๐Ÿ–ฅ๏ธ Host: {{ inventory_hostname }} ๐Ÿ“… Date: {{ ansible_date_time.date }} ๐Ÿ“ Config Paths: {{ current_configs | length }} {% for config in current_configs %} - {{ config.name }}: {{ config.path }} {% endfor %} ๐Ÿ”ง Service Data: {{ current_service_data | length }} {% for service in current_service_data %} - {{ service.service }} {% endfor %} ๐Ÿ” Include Secrets: {{ include_secrets | default(false) }} ๐Ÿ—œ๏ธ Compression: {{ compress_backups | default(true) }} - name: Create system info snapshot shell: | info_file="{{ backup_local_dir }}/{{ inventory_hostname }}/system_info_{{ ansible_date_time.epoch }}.txt" echo "๐Ÿ“Š SYSTEM INFORMATION SNAPSHOT" > "$info_file" echo "===============================" >> "$info_file" echo "Host: {{ inventory_hostname }}" >> "$info_file" echo "Date: {{ ansible_date_time.iso8601 }}" >> "$info_file" echo "OS: {{ ansible_distribution }} {{ ansible_distribution_version }}" >> "$info_file" echo "Kernel: {{ ansible_kernel }}" >> "$info_file" echo "Uptime: {{ ansible_uptime_seconds | int // 86400 }} days" >> "$info_file" echo "" >> "$info_file" echo "๐Ÿณ DOCKER INFO:" >> "$info_file" docker --version >> "$info_file" 2>/dev/null || echo "Docker not available" >> "$info_file" echo "" >> "$info_file" echo "๐Ÿ“ฆ RUNNING CONTAINERS:" >> "$info_file" docker ps --format "table {{ '{{' }}.Names{{ '}}' }}\t{{ '{{' }}.Image{{ '}}' }}\t{{ '{{' }}.Status{{ '}}' }}" >> "$info_file" 2>/dev/null || echo "Cannot access Docker" >> "$info_file" echo "" >> "$info_file" echo "๐Ÿ’พ DISK USAGE:" >> "$info_file" df -h >> "$info_file" echo "" >> "$info_file" echo "๐Ÿ”ง INSTALLED PACKAGES (last 20):" >> "$info_file" if command -v dpkg &> /dev/null; then dpkg -l | tail -20 >> "$info_file" elif command -v rpm &> /dev/null; then rpm -qa | tail -20 >> "$info_file" fi - name: Backup configuration directories shell: | config_name="{{ item.name }}" source_path="{{ item.path }}" backup_file="{{ backup_local_dir }}/{{ inventory_hostname }}/${config_name}_{{ ansible_date_time.date }}_{{ ansible_date_time.hour }}{{ ansible_date_time.minute }}.tar" if [ -d "$source_path" ]; then echo "๐Ÿ”„ Backing up $config_name from $source_path..." # Build exclude options exclude_opts="" {% for exclude in item.exclude %} exclude_opts="$exclude_opts --exclude='{{ exclude }}'" {% endfor %} {% if not (include_secrets | default(false)) %} # Add common secret file exclusions exclude_opts="$exclude_opts --exclude='*.key' --exclude='*.pem' --exclude='*.p12' --exclude='*password*' --exclude='*secret*' --exclude='*.env'" {% endif %} # Create tar backup eval "tar -cf '$backup_file' -C '$(dirname $source_path)' $exclude_opts '$(basename $source_path)'" if [ $? -eq 0 ]; then echo "โœ… $config_name backup successful" {% if compress_backups | default(true) %} gzip "$backup_file" backup_file="${backup_file}.gz" {% endif %} backup_size=$(du -h "$backup_file" | cut -f1) echo "๐Ÿ“ฆ Backup size: $backup_size" # Copy to permanent storage if [ -d "{{ backup_base_dir }}/{{ inventory_hostname }}" ]; then cp "$backup_file" "{{ backup_base_dir }}/{{ inventory_hostname }}/" echo "๐Ÿ“ Copied to permanent storage" fi else echo "โŒ $config_name backup failed" fi else echo "โš ๏ธ $source_path does not exist, skipping $config_name" fi register: config_backups loop: "{{ current_configs }}" - name: Backup service-specific data shell: | service_name="{{ item.service }}" backup_file="{{ backup_local_dir }}/{{ inventory_hostname }}/service_${service_name}_{{ ansible_date_time.date }}_{{ ansible_date_time.hour }}{{ ansible_date_time.minute }}.tar" echo "๐Ÿ”„ Backing up $service_name service data..." # Create temporary file list temp_list="/tmp/service_${service_name}_files.txt" > "$temp_list" {% for path in item.paths %} if [ -d "{{ path }}" ]; then echo "{{ path }}" >> "$temp_list" fi {% endfor %} if [ -s "$temp_list" ]; then tar -cf "$backup_file" -T "$temp_list" {% if not (include_secrets | default(false)) %}--exclude='*.key' --exclude='*.pem' --exclude='*password*' --exclude='*secret*'{% endif %} if [ $? -eq 0 ]; then echo "โœ… $service_name service data backup successful" {% if compress_backups | default(true) %} gzip "$backup_file" backup_file="${backup_file}.gz" {% endif %} backup_size=$(du -h "$backup_file" | cut -f1) echo "๐Ÿ“ฆ Backup size: $backup_size" if [ -d "{{ backup_base_dir }}/{{ inventory_hostname }}" ]; then cp "$backup_file" "{{ backup_base_dir }}/{{ inventory_hostname }}/" fi else echo "โŒ $service_name service data backup failed" fi else echo "โš ๏ธ No valid paths found for $service_name" fi rm -f "$temp_list" register: service_backups loop: "{{ current_service_data }}" - name: Backup docker-compose files shell: | compose_backup="{{ backup_local_dir }}/{{ inventory_hostname }}/docker_compose_files_{{ ansible_date_time.date }}_{{ ansible_date_time.hour }}{{ ansible_date_time.minute }}.tar" echo "๐Ÿ”„ Backing up docker-compose files..." # Find all docker-compose files find /volume1 /opt /home -name "docker-compose.yml" -o -name "docker-compose.yaml" -o -name "*.yml" -path "*/docker/*" 2>/dev/null > /tmp/compose_files.txt if [ -s /tmp/compose_files.txt ]; then tar -cf "$compose_backup" -T /tmp/compose_files.txt if [ $? -eq 0 ]; then echo "โœ… Docker-compose files backup successful" {% if compress_backups | default(true) %} gzip "$compose_backup" compose_backup="${compose_backup}.gz" {% endif %} backup_size=$(du -h "$compose_backup" | cut -f1) echo "๐Ÿ“ฆ Backup size: $backup_size" if [ -d "{{ backup_base_dir }}/{{ inventory_hostname }}" ]; then cp "$compose_backup" "{{ backup_base_dir }}/{{ inventory_hostname }}/" fi else echo "โŒ Docker-compose files backup failed" fi else echo "โš ๏ธ No docker-compose files found" fi rm -f /tmp/compose_files.txt register: compose_backup - name: Create backup inventory shell: | inventory_file="{{ backup_local_dir }}/{{ inventory_hostname }}/backup_inventory_{{ ansible_date_time.date }}.txt" echo "๐Ÿ“‹ BACKUP INVENTORY" > "$inventory_file" echo "===================" >> "$inventory_file" echo "Host: {{ inventory_hostname }}" >> "$inventory_file" echo "Date: {{ ansible_date_time.iso8601 }}" >> "$inventory_file" echo "Include Secrets: {{ include_secrets | default(false) }}" >> "$inventory_file" echo "Compression: {{ compress_backups | default(true) }}" >> "$inventory_file" echo "" >> "$inventory_file" echo "๐Ÿ“ BACKUP FILES:" >> "$inventory_file" ls -la {{ backup_local_dir }}/{{ inventory_hostname }}/ >> "$inventory_file" echo "" >> "$inventory_file" echo "๐Ÿ“Š BACKUP SIZES:" >> "$inventory_file" du -h {{ backup_local_dir }}/{{ inventory_hostname }}/* >> "$inventory_file" echo "" >> "$inventory_file" echo "๐Ÿ” BACKUP CONTENTS:" >> "$inventory_file" {% for config in current_configs %} backup_file="{{ backup_local_dir }}/{{ inventory_hostname }}/{{ config.name }}_{{ ansible_date_time.date }}_{{ ansible_date_time.hour }}{{ ansible_date_time.minute }}.tar{% if compress_backups | default(true) %}.gz{% endif %}" if [ -f "$backup_file" ]; then echo "=== {{ config.name }} ===" >> "$inventory_file" {% if compress_backups | default(true) %} tar -tzf "$backup_file" | head -20 >> "$inventory_file" 2>/dev/null || echo "Cannot list contents" >> "$inventory_file" {% else %} tar -tf "$backup_file" | head -20 >> "$inventory_file" 2>/dev/null || echo "Cannot list contents" >> "$inventory_file" {% endif %} echo "" >> "$inventory_file" fi {% endfor %} # Copy inventory to permanent storage if [ -d "{{ backup_base_dir }}/{{ inventory_hostname }}" ]; then cp "$inventory_file" "{{ backup_base_dir }}/{{ inventory_hostname }}/" fi cat "$inventory_file" register: backup_inventory - name: Clean up old backups shell: | echo "๐Ÿงน Cleaning up backups older than {{ backup_retention_days | default(30) }} days..." # Clean local backups find {{ backup_local_dir }}/{{ inventory_hostname }} -name "*.tar*" -mtime +{{ backup_retention_days | default(30) }} -delete find {{ backup_local_dir }}/{{ inventory_hostname }} -name "*.txt" -mtime +{{ backup_retention_days | default(30) }} -delete # Clean permanent storage backups if [ -d "{{ backup_base_dir }}/{{ inventory_hostname }}" ]; then find {{ backup_base_dir }}/{{ inventory_hostname }} -name "*.tar*" -mtime +{{ backup_retention_days | default(30) }} -delete find {{ backup_base_dir }}/{{ inventory_hostname }} -name "*.txt" -mtime +{{ backup_retention_days | default(30) }} -delete fi echo "โœ… Cleanup complete" when: (backup_retention_days | default(30) | int) > 0 - name: Display backup summary debug: msg: | โœ… CONFIGURATION BACKUP COMPLETE ================================ ๐Ÿ–ฅ๏ธ Host: {{ inventory_hostname }} ๐Ÿ“… Date: {{ ansible_date_time.date }} ๐Ÿ“ Config Paths: {{ current_configs | length }} ๐Ÿ”ง Service Data: {{ current_service_data | length }} ๐Ÿ” Secrets Included: {{ include_secrets | default(false) }} {{ backup_inventory.stdout }} ๐Ÿ” Next Steps: - Verify backups: ls -la {{ backup_local_dir }}/{{ inventory_hostname }} - Test restore: tar -tf backup_file.tar.gz - Schedule regular backups via cron ================================