Sanitized mirror from private repository - 2026-04-19 08:46:29 UTC
This commit is contained in:
342
ansible/automation/playbooks/backup_configs.yml
Normal file
342
ansible/automation/playbooks/backup_configs.yml
Normal file
@@ -0,0 +1,342 @@
|
||||
---
|
||||
# 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
|
||||
|
||||
================================
|
||||
Reference in New Issue
Block a user