nix/nixos/common/software/cli/scripts/btrfs-backup.sh

184 lines
5.8 KiB
Bash
Raw Normal View History

2024-12-16 00:57:26 +01:00
#!/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"
2024-12-18 21:19:13 +01:00
BACKUP_DESCRIPTION="btrfs-backup"
2024-12-16 01:41:22 +01:00
# Get the actual snapshot location from snapper config
2024-12-16 01:57:56 +01:00
SOURCE_PATH=$(sudo snapper -c "$SNAPPER_CONFIG" get-config | grep '^SUBVOLUME' | cut -d'=' -f2 | tr -d '"'| awk {'print $3'})
2024-12-16 02:43:40 +01:00
echo "SOURCE_PATH: $SOURCE_PATH"
2024-12-16 01:41:22 +01:00
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
2024-12-16 03:11:23 +01:00
SNAPSHOT_PATH="$SOURCE_PATH/.snapshots"
2024-12-16 01:41:22 +01:00
if [ ! -d "$SNAPSHOT_PATH" ]; then
echo "ERROR: Snapshot directory '$SNAPSHOT_PATH' does not exist"
exit 1
fi
2024-12-18 21:19:13 +01:00
# Create new snapshot with backup description
NEW_SNAPSHOT=$(sudo snapper -c "$SNAPPER_CONFIG" create --description "$BACKUP_DESCRIPTION" --print-number)
if [ -z "$NEW_SNAPSHOT" ]; then
echo "ERROR: Failed to create new snapshot"
exit 1
fi
2024-12-16 02:43:40 +01:00
2024-12-16 16:14:42 +01:00
HOSTNME=$(hostname)
2024-12-16 00:57:26 +01:00
REMOTE_HOST="root@synology"
2024-12-16 15:47:58 +01:00
BASE_DEST_PATH="/volume1/BTRFS_Receives/`hostname`/${SNAPPER_CONFIG}"
2024-12-18 21:19:13 +01:00
DEST_PATH="/volume1/BTRFS_Receives/`hostname`/${SNAPPER_CONFIG}/${NEW_SNAPSHOT}"
2024-12-16 01:41:22 +01:00
STATE_FILE="/var/lib/snapper-backup-${SNAPPER_CONFIG}.state"
LOG_FILE="/var/log/snapper-backup-${SNAPPER_CONFIG}.log"
2024-12-16 00:57:26 +01:00
KEEP_SNAPSHOTS=5
2024-12-18 21:19:13 +01:00
# Get latest successful transfer number from snapshots with backup description
2024-12-16 02:43:40 +01:00
LAST_TRANSFERRED=$(cat "$STATE_FILE" 2>/dev/null || echo "")
2024-12-16 00:57:26 +01:00
# Ensure we exit on any error
set -e
# Verify snapper config exists
2024-12-16 01:51:21 +01:00
if ! sudo snapper -c "$SNAPPER_CONFIG" list &>/dev/null; then
2024-12-16 00:57:26 +01:00
echo "ERROR: Snapper config '$SNAPPER_CONFIG' does not exist"
exit 1
fi
# Logging function
log() {
2024-12-16 02:43:40 +01:00
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | sudo tee -a "$LOG_FILE"
2024-12-16 00:57:26 +01:00
}
# Function to verify snapshot exists
verify_snapshot() {
local snapshot_num="$1"
2024-12-16 01:41:22 +01:00
if [ ! -d "$SNAPSHOT_PATH/$snapshot_num/snapshot" ]; then
2024-12-16 00:57:26 +01:00
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() {
2024-12-18 21:19:13 +01:00
ssh "$REMOTE_HOST" "find '$BASE_DEST_PATH' -maxdepth 1 -type d -name '[0-9]*' | sort -n"
}
# Function to get local backup snapshots
get_local_snapshots() {
sudo snapper -c "$SNAPPER_CONFIG" list | grep "$BACKUP_DESCRIPTION" | awk '{print $1}' | sort -n
2024-12-16 00:57:26 +01:00
}
2024-12-18 21:19:13 +01:00
# Function to cleanup old snapshots both locally and remotely
cleanup_snapshots() {
local local_snapshots=($(get_local_snapshots))
local remote_snapshots=($(get_remote_snapshots))
local count=${#local_snapshots[@]}
2024-12-16 00:57:26 +01:00
if [ $count -gt $KEEP_SNAPSHOTS ]; then
local to_delete=$((count - KEEP_SNAPSHOTS))
2024-12-18 21:19:13 +01:00
log "Cleaning up $to_delete old snapshots both locally and remotely"
2024-12-16 00:57:26 +01:00
for ((i=0; i<$to_delete; i++)); do
2024-12-18 21:19:13 +01:00
local snapshot="${local_snapshots[$i]}"
# Delete remote snapshot first
if ssh "$REMOTE_HOST" "[ -d '$BASE_DEST_PATH/$snapshot' ]"; then
log "Deleting remote snapshot: $snapshot"
ssh "$REMOTE_HOST" "btrfs subvolume delete '$BASE_DEST_PATH/$snapshot/snapshot'" || \
log "WARNING: Failed to delete remote snapshot $snapshot"
ssh "$REMOTE_HOST" "rm -rf '$BASE_DEST_PATH/$snapshot'" || \
log "WARNING: Failed to cleanup remote snapshot directory $snapshot"
fi
# Then delete local snapshot
log "Deleting local snapshot: $snapshot"
sudo snapper -c "$SNAPPER_CONFIG" delete "$snapshot" || \
log "WARNING: Failed to delete local snapshot $snapshot"
2024-12-16 00:57:26 +01:00
done
fi
}
# Start backup process
log "Starting backup for snapper config: $SNAPPER_CONFIG"
2024-12-16 01:41:22 +01:00
log "Using snapshot path: $SNAPSHOT_PATH"
2024-12-18 21:19:13 +01:00
log "Created new snapshot: $NEW_SNAPSHOT"
2024-12-16 00:57:26 +01:00
# Verify remote connectivity first
verify_remote
# Verify snapshots exist
2024-12-18 21:19:13 +01:00
verify_snapshot "$NEW_SNAPSHOT" || exit 1
2024-12-16 00:57:26 +01:00
if [ -n "$LAST_TRANSFERRED" ]; then
verify_snapshot "$LAST_TRANSFERRED" || exit 1
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
2024-12-18 21:19:13 +01:00
log "Performing full send of snapshot $NEW_SNAPSHOT"
sudo btrfs send "$SNAPSHOT_PATH/$NEW_SNAPSHOT/snapshot" | \
2024-12-16 02:43:40 +01:00
pv --bytes | \
2024-12-16 00:57:26 +01:00
ssh "$REMOTE_HOST" "btrfs receive '$DEST_PATH'" && {
2024-12-18 21:19:13 +01:00
echo "$NEW_SNAPSHOT" | sudo tee "$STATE_FILE"
2024-12-16 00:57:26 +01:00
log "Full send completed successfully"
} || {
log "ERROR: Full send failed"
2024-12-18 21:19:13 +01:00
sudo snapper -c "$SNAPPER_CONFIG" delete "$NEW_SNAPSHOT"
2024-12-16 00:57:26 +01:00
exit 1
}
else
# Incremental send
2024-12-18 21:19:13 +01:00
log "Performing incremental send from $LAST_TRANSFERRED to $NEW_SNAPSHOT"
2024-12-16 01:41:22 +01:00
sudo btrfs send -p "$SNAPSHOT_PATH/$LAST_TRANSFERRED/snapshot" \
2024-12-18 21:19:13 +01:00
"$SNAPSHOT_PATH/$NEW_SNAPSHOT/snapshot" | \
2024-12-16 02:43:40 +01:00
pv --bytes | \
2024-12-16 00:57:26 +01:00
ssh "$REMOTE_HOST" "btrfs receive '$DEST_PATH'" && {
2024-12-18 21:19:13 +01:00
echo "$NEW_SNAPSHOT" | sudo tee "$STATE_FILE"
2024-12-16 00:57:26 +01:00
log "Incremental send completed successfully"
} || {
log "ERROR: Incremental send failed"
2024-12-18 21:19:13 +01:00
sudo snapper -c "$SNAPPER_CONFIG" delete "$NEW_SNAPSHOT"
2024-12-16 00:57:26 +01:00
exit 1
}
fi
# Cleanup old snapshots if transfer was successful
2024-12-18 21:19:13 +01:00
cleanup_snapshots
2024-12-16 00:57:26 +01:00
# Verify remote snapshots
log "Current remote snapshots:"
2024-12-16 02:43:40 +01:00
get_remote_snapshots | sudo tee -a "$LOG_FILE"
2024-12-16 00:57:26 +01:00
2024-12-18 21:19:13 +01:00
log "Current local snapshots:"
get_local_snapshots | sudo tee -a "$LOG_FILE"
2024-12-16 00:57:26 +01:00
# 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"