nix/nixos/common/software/cli/scripts/btrfs-backup.sh
2024-12-16 01:41:22 +01:00

159 lines
4.6 KiB
Bash

#!/usr/bin/env bash
#!/bin/bash
# Check for required argument
if [ $# -ne 1 ]; then
echo "Usage: $(basename "$0") SNAPPER_CONFIG"
echo "Example: $(basename "$0") root"
exit 1
fi
# Configuration
SNAPPER_CONFIG="$1"
# Get the actual snapshot location from snapper config
SOURCE_PATH=$(snapper -c "$SNAPPER_CONFIG" get-config | grep '^SUBVOLUME' | cut -d'=' -f2 | tr -d '"'| awk {'print $3'})
if [ -z "$SOURCE_PATH" ]; then
echo "ERROR: Could not determine snapshot path for config '$SNAPPER_CONFIG'"
exit 1
fi
# Convert subvolume path to snapshot path
SNAPSHOT_PATH="$SOURCE_PATH/.snapshots"
if [ ! -d "$SNAPSHOT_PATH" ]; then
echo "ERROR: Snapshot directory '$SNAPSHOT_PATH' does not exist"
exit 1
fi
REMOTE_HOST="root@synology"
DEST_PATH="/volume1/backups/btrfs_`hostname`_${SNAPPER_CONFIG}"
STATE_FILE="/var/lib/snapper-backup-${SNAPPER_CONFIG}.state"
LOG_FILE="/var/log/snapper-backup-${SNAPPER_CONFIG}.log"
KEEP_SNAPSHOTS=5
# Ensure we exit on any error
set -e
# Verify snapper config exists
if ! snapper -c "$SNAPPER_CONFIG" list &>/dev/null; then
echo "ERROR: Snapper config '$SNAPPER_CONFIG' does not exist"
exit 1
fi
# Logging function
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
# Function to verify snapshot exists
verify_snapshot() {
local snapshot_num="$1"
if [ ! -d "$SNAPSHOT_PATH/$snapshot_num/snapshot" ]; then
log "ERROR: Snapshot $snapshot_num does not exist"
return 1
fi
return 0
}
# Function to verify remote connectivity
verify_remote() {
if ! ssh -q "$REMOTE_HOST" "exit"; then
log "ERROR: Cannot connect to remote host"
exit 1
fi
}
# Function to get remote snapshots
get_remote_snapshots() {
ssh "$REMOTE_HOST" "find '$DEST_PATH' -maxdepth 1 -type d -name 'snapshot*' | sort -n"
}
# Function to cleanup old remote snapshots
cleanup_remote_snapshots() {
local snapshots=($(get_remote_snapshots))
local count=${#snapshots[@]}
if [ $count -gt $KEEP_SNAPSHOTS ]; then
local to_delete=$((count - KEEP_SNAPSHOTS))
log "Cleaning up $to_delete old snapshots on remote system"
for ((i=0; i<$to_delete; i++)); do
local snapshot="${snapshots[$i]}"
log "Deleting remote snapshot: $snapshot"
ssh "$REMOTE_HOST" "btrfs subvolume delete '$snapshot'" || \
log "WARNING: Failed to delete snapshot $snapshot"
done
fi
}
# Start backup process
log "Starting backup for snapper config: $SNAPPER_CONFIG"
log "Using snapshot path: $SNAPSHOT_PATH"
# Verify remote connectivity first
verify_remote
# Get latest successful transfer number
LAST_TRANSFERRED=$(cat "$STATE_FILE" 2>/dev/null || echo "")
# Get latest snapshot number from snapper
LATEST_SNAPSHOT=$(snapper -c "$SNAPPER_CONFIG" list | tail -n 1 | awk '{print $1}')
# Verify snapshots exist
verify_snapshot "$LATEST_SNAPSHOT" || exit 1
if [ -n "$LAST_TRANSFERRED" ]; then
verify_snapshot "$LAST_TRANSFERRED" || exit 1
fi
# Exit if no new snapshots to transfer
if [ "$LAST_TRANSFERRED" = "$LATEST_SNAPSHOT" ]; then
log "No new snapshots to transfer"
exit 0
fi
# Create destination directory if it doesn't exist
ssh "$REMOTE_HOST" "mkdir -p '$DEST_PATH'"
# Perform the transfer
if [ -z "$LAST_TRANSFERRED" ]; then
# First time backup - full send
log "Performing full send of snapshot $LATEST_SNAPSHOT"
sudo btrfs send "$SNAPSHOT_PATH/$LATEST_SNAPSHOT/snapshot" | \
pv -bytes | \
ssh "$REMOTE_HOST" "btrfs receive '$DEST_PATH'" && {
echo "$LATEST_SNAPSHOT" > "$STATE_FILE"
log "Full send completed successfully"
} || {
log "ERROR: Full send failed"
exit 1
}
else
# Incremental send
log "Performing incremental send from $LAST_TRANSFERRED to $LATEST_SNAPSHOT"
sudo btrfs send -p "$SNAPSHOT_PATH/$LAST_TRANSFERRED/snapshot" \
"$SNAPSHOT_PATH/$LATEST_SNAPSHOT/snapshot" | \
pv -bytes | \
ssh "$REMOTE_HOST" "btrfs receive '$DEST_PATH'" && {
echo "$LATEST_SNAPSHOT" > "$STATE_FILE"
log "Incremental send completed successfully"
} || {
log "ERROR: Incremental send failed"
exit 1
}
fi
# Cleanup old snapshots if transfer was successful
cleanup_remote_snapshots
# Verify remote snapshots
log "Current remote snapshots:"
get_remote_snapshots | tee -a "$LOG_FILE"
# Final verification
if ! ssh "$REMOTE_HOST" "btrfs subvolume show '$DEST_PATH/snapshot'" &>/dev/null; then
log "WARNING: Final verification failed"
exit 1
fi
log "Backup completed successfully"