From 646d3e59bc041d1fa77a9fbaaafcfe01a3c41b61 Mon Sep 17 00:00:00 2001 From: Arpad Krejczinger Date: Sat, 11 Oct 2025 18:24:51 +0200 Subject: [PATCH] Add comprehensive system backup scripts Implement full system backup with Restic including: - Docker volume exports (Gitea, Jellyfin, Nextcloud, Portainer) - System configuration backup (/etc/) - Package list exports (pacman explicit, all, AUR) - Automated retention policy (7 daily, 4 weekly, 3 monthly, 1 yearly) - Separate temporary directories for Docker and system data --- scripts/backup-homelab.sh | 95 +++++++++++++++++++++++++ scripts/backup-system-full.sh | 128 ++++++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+) create mode 100755 scripts/backup-homelab.sh create mode 100755 scripts/backup-system-full.sh diff --git a/scripts/backup-homelab.sh b/scripts/backup-homelab.sh new file mode 100755 index 0000000..ee7beba --- /dev/null +++ b/scripts/backup-homelab.sh @@ -0,0 +1,95 @@ +#!/bin/bash +# Backup Docker volumes and system configs to NAS using Restic +# Can be run manually or via systemd timer + +set -e + +# Configuration +BACKUP_REPO="/mnt/nas/backups/homelab-restic" +PASSWORD_FILE="/home/hoborg/creds/restic-password.txt" +LOG_FILE="/var/log/homelab-backup.log" +DOCKER_BACKUP_DIR="/tmp/docker-backup-$$" + +# Export password +export RESTIC_PASSWORD_FILE="$PASSWORD_FILE" + +# Logging function +log() { + echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +log "========================================" +log "Starting Homelab Backup" +log "========================================" + +# Check if repository exists +if ! restic -r "$BACKUP_REPO" snapshots &>/dev/null; then + log "ERROR: Backup repository not initialized. Run backup-init.sh first." + exit 1 +fi + +# Create temporary directory for Docker volume exports +mkdir -p "$DOCKER_BACKUP_DIR" +log "Created temporary backup directory: $DOCKER_BACKUP_DIR" + +# Export Docker volumes +log "Exporting Docker volumes..." + +# Gitea +log " - Exporting Gitea data..." +docker run --rm -v gitea_gitea:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \ + tar czf /backup/gitea-data.tar.gz -C /data . + +# Jellyfin +log " - Exporting Jellyfin config..." +docker run --rm -v jellyfin_jellyfin_config:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \ + tar czf /backup/jellyfin-config.tar.gz -C /data . + +# Nextcloud data +log " - Exporting Nextcloud data..." +docker run --rm -v nextcloud_nextcloud_data:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \ + tar czf /backup/nextcloud-data.tar.gz -C /data . + +# Nextcloud database +log " - Exporting Nextcloud database..." +docker run --rm -v nextcloud_nextcloud_db:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \ + tar czf /backup/nextcloud-db.tar.gz -C /data . + +# Portainer +log " - Exporting Portainer data..." +docker run --rm -v portainer_data:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \ + tar czf /backup/portainer-data.tar.gz -C /data . + +log "✓ Docker volumes exported" + +# Run backup +log "Running Restic backup..." +restic -r "$BACKUP_REPO" backup \ + --verbose \ + --tag homelab-docker \ + --tag homelab-configs \ + /etc/nginx/sites-available/homelab \ + /etc/systemd/system/copyparty.service \ + /home/hoborg/.config/copyparty/ \ + "$DOCKER_BACKUP_DIR" + +# Cleanup temporary files +log "Cleaning up temporary files..." +rm -rf "$DOCKER_BACKUP_DIR" + +# Prune old backups (keep: 7 daily, 4 weekly, 6 monthly, 2 yearly) +log "Pruning old backups..." +restic -r "$BACKUP_REPO" forget \ + --keep-daily 7 \ + --keep-weekly 4 \ + --keep-monthly 6 \ + --keep-yearly 2 \ + --prune + +# Show backup stats +log "Backup statistics:" +restic -r "$BACKUP_REPO" stats --mode restore-size + +log "========================================" +log "Backup completed successfully!" +log "========================================" diff --git a/scripts/backup-system-full.sh b/scripts/backup-system-full.sh new file mode 100755 index 0000000..0acc103 --- /dev/null +++ b/scripts/backup-system-full.sh @@ -0,0 +1,128 @@ +#!/bin/bash +# Full system backup - packages, configs, and Docker volumes +# Can be run manually or via systemd timer + +set -e + +# Configuration +BACKUP_REPO="/mnt/nas/backups/homelab-restic" +PASSWORD_FILE="/home/hoborg/creds/restic-password.txt" +LOG_FILE="/var/log/homelab-backup.log" +DOCKER_BACKUP_DIR="/tmp/docker-backup-$$" +SYSTEM_BACKUP_DIR="/tmp/system-backup-$$" + +# Export password +export RESTIC_PASSWORD_FILE="$PASSWORD_FILE" + +# Logging function +log() { + echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +log "========================================" +log "Starting Full System Backup" +log "========================================" + +# Check if repository exists +if ! restic -r "$BACKUP_REPO" snapshots &>/dev/null; then + log "ERROR: Backup repository not initialized. Run backup-init.sh first." + exit 1 +fi + +# Create temporary directories +mkdir -p "$DOCKER_BACKUP_DIR" +mkdir -p "$SYSTEM_BACKUP_DIR" +log "Created temporary directories" + +# ============================================ +# 1. Export Package Lists +# ============================================ +log "Exporting package lists..." +pacman -Qqe > "$SYSTEM_BACKUP_DIR/pacman-explicit.txt" # Explicitly installed packages +pacman -Qq > "$SYSTEM_BACKUP_DIR/pacman-all.txt" # All installed packages +pacman -Qqm > "$SYSTEM_BACKUP_DIR/aur-packages.txt" # AUR/foreign packages only +log "✓ Package lists exported ($(wc -l < $SYSTEM_BACKUP_DIR/pacman-explicit.txt) explicit, $(wc -l < $SYSTEM_BACKUP_DIR/aur-packages.txt) AUR)" + +# ============================================ +# 2. Export Docker Volumes +# ============================================ +log "Exporting Docker volumes..." + +# Gitea +log " - Exporting Gitea data..." +docker run --rm -v gitea_gitea:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \ + tar czf /backup/gitea-data.tar.gz -C /data . + +# Jellyfin +log " - Exporting Jellyfin config..." +docker run --rm -v jellyfin_jellyfin_config:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \ + tar czf /backup/jellyfin-config.tar.gz -C /data . + +# Nextcloud data +log " - Exporting Nextcloud data..." +docker run --rm -v nextcloud_nextcloud_data:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \ + tar czf /backup/nextcloud-data.tar.gz -C /data . + +# Nextcloud database +log " - Exporting Nextcloud database..." +docker run --rm -v nextcloud_nextcloud_db:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \ + tar czf /backup/nextcloud-db.tar.gz -C /data . + +# Portainer +log " - Exporting Portainer data..." +docker run --rm -v portainer_data:/data -v "$DOCKER_BACKUP_DIR":/backup alpine \ + tar czf /backup/portainer-data.tar.gz -C /data . + +log "✓ Docker volumes exported" + +# ============================================ +# 3. Run Backup +# ============================================ +log "Running Restic backup..." +restic -r "$BACKUP_REPO" backup \ + --verbose \ + --tag full-system \ + --exclude '/etc/shadow*' \ + --exclude '/etc/gshadow*' \ + --exclude '/etc/passwd-' \ + --exclude '/etc/group-' \ + /etc/ \ + "$DOCKER_BACKUP_DIR" \ + "$SYSTEM_BACKUP_DIR" + +# ============================================ +# 4. Cleanup +# ============================================ +log "Cleaning up temporary files..." +rm -rf "$DOCKER_BACKUP_DIR" +rm -rf "$SYSTEM_BACKUP_DIR" + +# ============================================ +# 5. Prune Old Backups +# ============================================ +log "Pruning old backups..." +restic -r "$BACKUP_REPO" forget \ + --keep-daily 7 \ + --keep-weekly 4 \ + --keep-monthly 3 \ + --keep-yearly 1 \ + --prune + +# ============================================ +# 6. Show Stats +# ============================================ +log "Backup statistics:" +restic -r "$BACKUP_REPO" stats --mode restore-size + +log "========================================" +log "Full system backup completed!" +log "========================================" +log "" +log "Backed up:" +log " - /etc/ (all system configs)" +log " - Docker volumes (Gitea, Jellyfin, Nextcloud, Portainer)" +log " - Package lists (pacman + AUR)" +log "" +log "To restore packages after reinstall:" +log " pacman -S --needed \$(cat pacman-explicit.txt)" +log " yay -S --needed \$(cat aur-packages.txt)"