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

143 lines
4.1 KiB
Bash

#!/usr/bin/env 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"
SOURCE_PATH="/.snapshots"
REMOTE_HOST="root@synology"
DEST_PATH="/volume1/backups/${SNAPPER_CONFIG}" # Added config name to path
STATE_FILE="/var/lib/snapper-backup-${SNAPPER_CONFIG}.state" # Made state file unique per config
LOG_FILE="/var/log/snapper-backup-${SNAPPER_CONFIG}.log" # Made log file unique per config
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 "$SOURCE_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"
# 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 "$SOURCE_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 "$SOURCE_PATH/$LAST_TRANSFERRED/snapshot" \
"$SOURCE_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"