1 Backup approach
ayakael edited this page 2026-02-15 22:35:57 +00:00

Our backup approach leverages a custom wrapper for syncoid, packaged in sanoid. Sanoid handles the snapshots, and then bacoid sends those snapshots incrementally to a server that can receive via SSH. Bacoid uses custom ZFS config variables to know which snapshots to send and where. A dataset that doesn't have snapshots does not get backed-up. If the dataset is encrypted, the backup will also be encrypted. The backups on the server-side then gets purged via that server's sanoid configuration.

The bacoid script:

#!/bin/bash

for copy in _a _b; do
	subvolArray=($(zfs get syncoid:pool$copy -H -o name,value -r $1  | grep -v '@' | awk '{if($2 != "-" && $2 != "") print $1}'))

	COUNT=1
	for subvol in ${subvolArray[@]}; do
		targetArray[$COUNT]="$(zfs get syncoid:target$copy ${subvol} | grep -v 'NAME' | awk '{print $3}')"
		COUNT=$(( ${COUNT} + 1 ))
	done
	targetArray=($(printf '%s\n' ${targetArray[@]} | awk '!a[$0]++'))


	for target in ${targetArray[@]}; do
		[[ ${target} != *@* ]] && continue
		echo "Testing SSH connection to ${target}"
		USERHOST="$(sed 's|:.*||' <<< ${target})"
		HOST="$(sed 's|.*@||' <<< ${USERHOST})"
		USER="$(sed 's|@.*||' <<< ${USERHOST})"
		PORT="$(sed 's|.*:||' <<< ${target})"
		if [[ ! -f "${HOME}/.ssh/bkp_rsa" ]]; then
			echo "Generating bkp_rsa key"
			ssh-keygen -N "" -f "${HOME}/.ssh/bkp_rsa"
		fi

		ssh -i "${HOME}/.ssh/bkp_rsa" \
       			-p ${PORT} \
       			-o ControlPath=none \
       			-o LogLevel=INFO \
       			-o PreferredAuthentications=publickey \
       			-o IdentitiesOnly=yes ${USERHOST} exit
		[[ $? -ne 0 ]] && ssh-copy-id -p ${PORT} -i "${HOME}/.ssh/bkp_rsa" ${USERHOST}
	done


	for subvol in ${subvolArray[@]}; do
		TARGET="$(zfs get syncoid:target$copy ${subvol} | grep -v NAME | awk '{print $3}')"
		if [[ ${target} != "-" ]]; then
			USERHOST="$(sed 's|:.*||' <<< ${TARGET})"
			HOST="$(sed 's|.*@||' <<< ${USERHOST})"
			USER="$(sed 's|@.*||' <<< ${USERHOST})"
			PORT="$(sed 's|.*:||' <<< ${TARGET})"
			MACHINE="$(zfs get syncoid:machine ${subvol} | grep -v NAME | awk '{print $3}')"
			[[ ${MACHINE} == '-' ]] && { echo "syncoid:machine not set for ${subvol}"; continue; }
			POOL="$(zfs get syncoid:pool$copy ${subvol} | grep -v NAME | awk '{print $3}')"
			[[ ${POOL} == '-' ]] && { echo "syncoid:pool$copy not set for ${subvol}"; continue; }
			echo "Sending ${subvol} to ${USERHOST}:${POOL}/${USER/-*}/$MACHINE/$subvol"
			syncoid --sendoptions="w" --recvoptions="u" --force-delete  --sshport=${PORT} --sshkey="${HOME}/.ssh/bkp_rsa" --no-sync-snap --no-privilege-elevation  ${subvol} ${USERHOST}:${POOL}/${USER/-*}/${MACHINE}/${subvol}
		else
			MACHINE="$(zfs get syncoid:machine ${subvol} | grep -v NAME | awk '{print $3}')"
			[[ ${MACHINE} == '-' ]] && { echo "syncoid:machine not set for ${subvol}"; continue; }
			POOL="$(zfs get syncoid:pool$copy ${subvol} | grep -v NAME | awk '{print $3}')"
			[[ ${POOL} == '-' ]] && { echo "syncoid:pool$copy not set for ${subvol}"; continue; }
			echo "Sending ${subvol} to ${POOL}/$MACHINE/$subvol"
			syncoid --sendoptions="w" --recvoptions="u" --no-sync-snap ${subvol} ${POOL}/${MACHINE}/${subvol}
		fi
	done
done