343 lines
14 KiB
YAML
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
|
|
|
|
================================
|