Files
homelab-optimized/ansible/automation/playbooks/backup_configs.yml
Gitea Mirror Bot d72af152e3
Some checks failed
Documentation / Deploy to GitHub Pages (push) Has been cancelled
Documentation / Build Docusaurus (push) Has been cancelled
Sanitized mirror from private repository - 2026-04-16 07:19:56 UTC
2026-04-16 07:19:56 +00:00

343 lines
14 KiB
YAML

---
# 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
================================