snap-sync: enable snapper to admin snap-sync backups on target

- create and verify snapper compatible structure on target host
- create snapper config (snap-$selected_config) on target host
  if not already available. use a snap-sync template.
- change userdata for snapper listings on target config
  subvolid, uuid and hostname reflact the values from the source system
- introduce a snapper template (/etc/snapper/config-templates/snap-sync)
- adapt Makefile to support an initial snap-sync template
  per default, this templates excludes snap-sync backup configs
  from timeline and cleanup tasks

Signed-off-by: Ralf Zerres <ralf.zerres@networkx.de>
This commit is contained in:
2017-11-13 11:47:56 +01:00
parent ee1ed3b4ed
commit fe15397f33
3 changed files with 110 additions and 36 deletions

View File

@@ -18,6 +18,7 @@
PKGNAME = snap-sync PKGNAME = snap-sync
PREFIX ?= /usr PREFIX ?= /usr
SNAPPER_CONFIG ?= /etc/sysconfig/snapper SNAPPER_CONFIG ?= /etc/sysconfig/snapper
SNAPPER_TEMPLATES ?= /etc/snapper/config-templates
BIN_DIR = $(DESTDIR)$(PREFIX)/bin BIN_DIR = $(DESTDIR)$(PREFIX)/bin
SYSTEMD_DIR = $(DESTDIR)$(PREFIX)/lib/systemd/system SYSTEMD_DIR = $(DESTDIR)$(PREFIX)/lib/systemd/system
@@ -27,3 +28,4 @@ SYSTEMD_DIR = $(DESTDIR)$(PREFIX)/lib/systemd/system
install: install:
@./find_snapper_config || sed -i 's@^SNAPPER_CONFIG.*@SNAPPER_CONFIG='$(SNAPPER_CONFIG)'@g' bin/$(PKGNAME) @./find_snapper_config || sed -i 's@^SNAPPER_CONFIG.*@SNAPPER_CONFIG='$(SNAPPER_CONFIG)'@g' bin/$(PKGNAME)
@install -Dm755 bin/* -t $(BIN_DIR)/ @install -Dm755 bin/* -t $(BIN_DIR)/
@install -Dm644 ./$(SNAPPER_TEMPLATES)/* -t $(SNAPPER_TEMPLATES)/

View File

@@ -28,9 +28,10 @@
progname="`basename v$0`" progname="`basename v$0`"
version="0.4.4" version="0.4.4"
# The following line is modified by the Makefile or # The following lines are modified by the Makefile or
# find_snapper_config script # find_snapper_config script
SNAPPER_CONFIG=/etc/conf.d/snapper SNAPPER_CONFIG=/etc/conf.d/snapper
SNAPPER_TEMPLATES=/etc/snapper/config-templates
TMPDIR=$(mktemp -d) TMPDIR=$(mktemp -d)
PIPE=$TMPDIR/$progname.out PIPE=$TMPDIR/$progname.out
@@ -88,6 +89,7 @@ get_disk_infos () {
# get mounted BTRFS infos # get mounted BTRFS infos
if [ "$(findmnt --noheadings --nofsroot --target / --output FSTYPE)" = "btrfs" ]; then if [ "$(findmnt --noheadings --nofsroot --target / --output FSTYPE)" = "btrfs" ]; then
# root filesystem is never seen as valid target location
exclude_uuid=$(findmnt --noheadings --nofsroot --types btrfs --target / --output UUID) exclude_uuid=$(findmnt --noheadings --nofsroot --types btrfs --target / --output UUID)
disk_uuids=$($ssh findmnt --noheadings --nofsroot --types btrfs --output UUID,TARGET --list | grep -v $exclude_uuid | awk '{print $1}') disk_uuids=$($ssh findmnt --noheadings --nofsroot --types btrfs --output UUID,TARGET --list | grep -v $exclude_uuid | awk '{print $1}')
disk_targets=$($ssh findmnt --noheadings --nofsroot --types btrfs --output UUID,TARGET --list | grep -v $exclude_uuid | awk '{print $2}') disk_targets=$($ssh findmnt --noheadings --nofsroot --types btrfs --output UUID,TARGET --list | grep -v $exclude_uuid | awk '{print $2}')
@@ -394,9 +396,9 @@ run_config () {
if [ "$verbose" ]; then if [ "$verbose" ]; then
if [ -n "$ssh" ];then if [ -n "$ssh" ];then
printf "Backup-Path on remote %s: %s/%s\n" "$remote" "$backup_root" "$backupdir" printf "Backup-Path on remote %s: %s\n" "$remote" "$backup_root"
else else
printf "Backup-Path: %s/%s\n" "$backup_root" "$backupdir" printf "Backup-Path: %s\n" "$backup_root"
fi fi
fi fi
@@ -409,16 +411,17 @@ run_config () {
sync sync
else else
printf "dryrun: Creating new snapshot with snapper config '%s' ...\n" "$selected_config" | tee $PIPE printf "dryrun: Creating new snapshot with snapper config '%s' ...\n" "$selected_config" | tee $PIPE
snapper_new_id="<new_snapper_id>"
fi fi
# if we want to use snapper on the target to supervise the synced snapshots # if we want to use snapper on the target to supervise the synced snapshots
# the backup_location needs to be in a subvol ".snapshots" inside $selected_config (hardcoded in snapper) # the backup_location needs to be in a subvol ".snapshots" inside $selected_config (hardcoded in snapper)
snapper_target_subvol=.snapshots snapper_target_subvol="snap-$selected_config"
snapper_target_snapshot=$backup_root/$selected_config/$snapper_target_subvol/$snapper_new_id snapper_target_snapshots=$backup_root/$snapper_target_subvol/.snapshots/$snapper_new_id
if [ -z "$ssh" ]; then if [ -z "$ssh" ]; then
printf "Will backup %s to %s\n" "$snapper_new_snapshot" "$snapper_target_snapshot/snapshot" | tee $PIPE printf "Will backup %s to %s\n" "$snapper_new_snapshot" "$snapper_target_snapshots/snapshot" | tee $PIPE
else else
printf "Will backup %s to %s\n" "$snapper_new_snapshot" "$remote":"$snapper_target_snapshot/snapshot" | tee $PIPE printf "Will backup %s to %s\n" "$snapper_new_snapshot" "$remote":"$snapper_target_snapshots/snapshot" | tee $PIPE
fi fi
# save in config specific infos in pseudo Arrays # save in config specific infos in pseudo Arrays
@@ -427,7 +430,7 @@ run_config () {
eval "snapper_new_info_$i='$snapper_new_info'" eval "snapper_new_info_$i='$snapper_new_info'"
eval "snapper_config_$i='$selected_config'" eval "snapper_config_$i='$selected_config'"
eval "snapper_target_subvol_$i='$snapper_target_subvol'" eval "snapper_target_subvol_$i='$snapper_target_subvol'"
eval "snapper_target_snapshot_$i='$snapper_target_snapshot'" eval "snapper_target_snapshots_$i='$snapper_target_snapshots'"
cont_backup="K" cont_backup="K"
eval "snapper_activate_$i=yes" eval "snapper_activate_$i=yes"
@@ -510,7 +513,7 @@ run_backup () {
snapper_new_snapshot=$(eval echo \$snapper_new_snapshot_$i) snapper_new_snapshot=$(eval echo \$snapper_new_snapshot_$i)
snapper_new_info=$(eval echo \$snapper_new_info_$i) snapper_new_info=$(eval echo \$snapper_new_info_$i)
snapper_target_subvol=$(eval echo \$snapper_target_subvol_$i) snapper_target_subvol=$(eval echo \$snapper_target_subvol_$i)
snapper_target_snapshot=$(eval echo \$snapper_target_snapshot_$i) snapper_target_snapshots=$(eval echo \$snapper_target_snapshots_$i)
if [ ! "$dryrun" ]; then if [ ! "$dryrun" ]; then
verify_snapper_structure $backup_root $snapper_config $snapper_target_subvol $snapper_new_id verify_snapper_structure $backup_root $snapper_config $snapper_target_subvol $snapper_new_id
@@ -520,16 +523,16 @@ run_backup () {
fi fi
if [ -z "$snapper_sync_id" ]; then if [ -z "$snapper_sync_id" ]; then
cmd="btrfs send $snapper_new_snapshot | $ssh btrfs receive $snapper_target_snapshot" cmd="btrfs send $snapper_new_snapshot | $ssh btrfs receive $snapper_target_snapshots"
printf "Sending first snapshot for snapper config '%s' ...\n" "$selected_config" | tee $PIPE printf "Sending first snapshot for snapper config '%s' ...\n" "$selected_config" | tee $PIPE
if [ "$verbose" ]; then if [ "$verbose" ]; then
echo "btrfs send $snapper_new_snapshot | $ssh btrfs receive $snapper_target_snapshot" echo "btrfs send $snapper_new_snapshot | $ssh btrfs receive $snapper_target_snapshots"
cmd="btrfs send -v $snapper_new_snapshot | $ssh btrfs receive -v $snapper_target_snapshot" cmd="btrfs send -v $snapper_new_snapshot | $ssh btrfs receive -v $snapper_target_snapshots"
fi fi
if [ ! "$dryrun" ]; then if [ ! "$dryrun" ]; then
btrfs send "$snapper_new_snapshot" | $ssh btrfs receive "$snapper_target_snapshot" &>/dev/null btrfs send "$snapper_new_snapshot" | $ssh btrfs receive "$snapper_target_snapshots" &>/dev/null
else else
cmd="btrfs send -v $snapper_new_snapshot | $ssh btrfs receive -v $snapper_target_snapshot" cmd="btrfs send -v $snapper_new_snapshot | $ssh btrfs receive -v $snapper_target_snapshots"
printf "dryrun: %s\n" "$cmd" printf "dryrun: %s\n" "$cmd"
fi fi
else else
@@ -540,7 +543,7 @@ run_backup () {
# location where it can get its data. This helps speed up the transfer. # location where it can get its data. This helps speed up the transfer.
verbose_flag="-v" verbose_flag="-v"
if [ ! "$dryrun" ]; then if [ ! "$dryrun" ]; then
btrfs send "$verbose_flag" -c "$snapper_sync_snapshot" "$snapper_new_snapshot" | $ssh btrfs receive "$verbose_flag" "$snapper_target_snapshot" btrfs send "$verbose_flag" -c "$snapper_sync_snapshot" "$snapper_new_snapshot" | $ssh btrfs receive "$verbose_flag" "$snapper_target_snapshots"
if [ "$verbose" ]; then if [ "$verbose" ]; then
printf "Deleting sync snapshot for %s ...\n" "$selected_config" | tee $PIPE printf "Deleting sync snapshot for %s ...\n" "$selected_config" | tee $PIPE
fi fi
@@ -548,23 +551,23 @@ run_backup () {
else else
printf "dryrun: btrfs send %s -c %s %s | %s btrfs receive %s %s\n" \ printf "dryrun: btrfs send %s -c %s %s | %s btrfs receive %s %s\n" \
"$verbose_flag" "$snapper_sync_snapshot" "$snapper_new_snapshot" \ "$verbose_flag" "$snapper_sync_snapshot" "$snapper_new_snapshot" \
"$ssh" "$verbose_flag" "$snapper_target_snapshot" "$ssh" "$verbose_flag" "$snapper_target_snapshots"
printf "dryrun: snapper -c %s delete %a\n" "$selected_config" "$snapper_sync_id" printf "dryrun: snapper -c %s delete %a\n" "$selected_config" "$snapper_sync_id"
fi fi
fi fi
if [ -z "$ssh" ]; then if [ -z "$ssh" ]; then
if [ ! "$dryrun" ]; then if [ ! "$dryrun" ]; then
cp "$snapper_new_info" "$snapper_target_snapshot" cp "$snapper_new_info" "$snapper_target_snapshots"
else else
cmd="cp $snapper_new_info $snapper_target_snapshot" cmd="cp $snapper_new_info $snapper_target_snapshots"
printf "dryrun: %s\n" "$cmd" printf "dryrun: %s\n" "$cmd"
fi fi
else else
if [ ! "$dryrun" ]; then if [ ! "$dryrun" ]; then
rsync -avzq "$snapper_new_info" "$remote":"$snapper_target_snapshot" rsync -avzq "$snapper_new_info" "$remote":"$snapper_target_snapshots"
else else
cmd="rsync -avzq $snapper_new_info $remote:$snapper_target_snapshot" cmd="rsync -avzq $snapper_new_info $remote:$snapper_target_snapshots"
printf "dryrun: %s\n" "$cmd" printf "dryrun: %s\n" "$cmd"
fi fi
fi fi
@@ -574,14 +577,22 @@ run_backup () {
# This is how we find the parent. # This is how we find the parent.
userdata="backupdir=$backup_dir, subvolid=$selected_subvol, uuid=$selected_uuid" userdata="backupdir=$backup_dir, subvolid=$selected_subvol, uuid=$selected_uuid"
src_host=$(eval cat /etc/hostname)
src_uuid=$(eval findmnt --noheadings --output UUID $SUBVOLUME)
src_subvolid=$(eval findmnt --noheadings --output OPTIONS $SUBVOLUME | sed -e 's/.*subvolid=\([0-9]*\).*/\1/')
target_description="snap-sync backup"
target_userdata="subvolid=$src_subvolid, uuid=$src_uuid, host=$src_host"
# Tag new snapshot as the latest # Tag new snapshot as the latest
printf "Tagging new snapshot as latest backup for '%s' ...\n" "$selected_config" | tee $PIPE printf "Tagging new snapshot as latest backup for '%s' ...\n" "$selected_config" | tee $PIPE
if [ ! "$dryrun" ]; then if [ ! "$dryrun" ]; then
snapper -v -c "$selected_config" modify -d "$description" -u "$userdata" "$snapper_new_id" snapper -v -c "$selected_config" modify -d "$description" -u "$userdata" "$snapper_new_id"
$ssh snapper -v -c "$snapper_target_subvol" modify -d \"$target_description\" -u \"$target_userdata\" "$snapper_new_id"
else else
cmd="snapper -v -c $selected_config modify -d $description -u $userdata $snapper_new_id" cmd="snapper -v -c $selected_config modify -d $description -u $userdata $snapper_new_id"
printf "dryrun: %s\n" "$cmd" printf "dryrun: %s\n" "$cmd"
cmd="$ssh snapper -v -c $snapper_target_subvol modify -d $target_description -u $target_userdata $snapper_new_id"
printf "dryrun: %s\n" "$cmd"
fi fi
printf "Backup complete for snapper configuration '%s'.\n" "$selected_config" > $PIPE printf "Backup complete for snapper configuration '%s'.\n" "$selected_config" > $PIPE
done done
@@ -731,38 +742,41 @@ verify_snapper_structure () {
local snapper_subvol=$3 local snapper_subvol=$3
local snapper_id=$4 local snapper_id=$4
local snapper_snapshots=".snapshots"
if [ "$verbose" ]; then if [ "$verbose" ]; then
echo "Verify snapper filesystem structure on target ..." echo "Verify snapper filesystem structure on target ..."
fi fi
# if not accessible, create backup-path # if not accessible, create backup-path
if $ssh [ ! -d $backup_root/$snapper_config ]; then if $ssh [ ! -d $backup_root ]; then
if [ "$verbose" ]; then if [ "$verbose" ]; then
echo "Create backup-path $backup_root/$snapper_config" echo "Create backup-path $backup_root ..."
fi fi
$ssh mkdir --mode=0700 --parents $backup_root/$snapper_config $ssh mkdir --mode=0700 --parents $backup_root
fi fi
if $ssh [ ! -d $backup_root/$snapper_subvol ]; then
# if not accessible, create subvolume to hold snappers snapshot structure if $ssh [ ! -f $SNAPPER_TEMPLATES/snap-sync ]; then
create_subvol="btrfs subvolume create $backup_root/$snapper_config/$snapper_subvol" die "A snapper template %s to configure the snapper subvolume %s is missing in %s. Did you miss to install the package default template?\n" "snap-sync" "$snapper_config" "$SNAPPER_TEMPLATES"
# check if given snapper_subvol is a subvol
if $ssh [ ! -d $backup_root/$snapper_config/$snapper_subvol ]; then
if [ "$verbose" ]; then
echo "Create new subvolume $backup_root/$snapper_config/$snapper_subvol"
fi fi
if [ "$verbose" ]; then
printf "Create new snapper capable subvolume in '%s' ...\n" "$backup_root/$snapper_subvol"
fi
create_subvol="btrfs subvolume create $backup_root/$snapper_subvol"
$ssh $create_subvol || die "BTRFS subvolume %s to hold snapshots for config %s could not be created in directory on %s.\n" "$snapper_subvol" "$snapper_config" "$backup_root" $ssh $create_subvol || die "BTRFS subvolume %s to hold snapshots for config %s could not be created in directory on %s.\n" "$snapper_subvol" "$snapper_config" "$backup_root"
$ssh snapper --config $snapper_subvol create-config --template snap-sync $backup_root/$snapper_subvol
$ssh chmod --mode=0700 $backup_root/$snapper_config
else else
if $ssh [ `stat --format=%i $backup_root/$snapper_config/$snapper_subvol` -ne 256 ]; then if $ssh [ `stat --format=%i $backup_root/$snapper_subvol` -ne 256 ]; then
die "%s needs to be a BTRFS subvolume. But given %s is just a directory.\n" "$snapper_subvol" "$backup_root/$snapper_config/$snapper_subvol" die "%s needs to be a BTRFS subvolume. But given %s is just a directory.\n" "$snapper_subvol" "$backup_root/$snapper_subvol"
fi fi
fi fi
if $ssh [ ! -d $backup_root/$snapper_config/$snapper_subvol/$snapper_id ]; then if $ssh [ ! -d $backup_root/$snapper_subvol/$snapper_snapshots/$snapper_id ]; then
if [ "$verbose" ]; then if [ "$verbose" ]; then
echo "Create backup-path $backup_root/$snapper_config/$snapper_subvol/$snapper_id" echo "Create backup-path $backup_root/$snapper_subvol/$snapper_snapshots/$snapper_id"
fi fi
$ssh mkdir --mode=0700 $backup_root/$snapper_config/$snapper_subvol/$snapper_id $ssh mkdir --mode=0700 $backup_root/$snapper_subvol/$snapper_snapshots/$snapper_id
fi fi
} }

View File

@@ -0,0 +1,58 @@
###
# snapper template for snap-sync handling
###
# subvolume to snapshot
SUBVOLUME="/var/lib/snap-sync"
# filesystem type
FSTYPE="btrfs"
# users and groups allowed to work with config
ALLOW_USERS=""
ALLOW_GROUPS="adm"
# sync users and groups from ALLOW_USERS and ALLOW_GROUPS to .snapshots
# directory
SYNC_ACL="yes"
# start comparing pre- and post-snapshot in background after creating
# post-snapshot
BACKGROUND_COMPARISON="yes"
# run daily number cleanup
NUMBER_CLEANUP="no"
# limit for number cleanup
NUMBER_MIN_AGE="1800"
NUMBER_LIMIT="50"
NUMBER_LIMIT_IMPORTANT="10"
# "no": we will use systemd.timer
TIMELINE_CREATE="no"
# create cron based cleanup entries
# "no": we will use systemd.timer
TIMELINE_CLEANUP="no"
# snap-sync: timeline settings
TIMELINE_MIN_AGE="1800"
TIMELINE_LIMIT_HOURLY="1"
TIMELINE_LIMIT_DAILY="2"
TIMELINE_LIMIT_MONTHLY="1"
TIMELINE_LIMIT_YEARLY="1"
# cleanup empty pre-post-pairs
EMPTY_PRE_POST_CLEANUP="yes"
# limits for empty pre-post-pair cleanup
EMPTY_PRE_POST_MIN_AGE="1800"
# uncomment to exclude this subvol when calling
# snap-sync as timer unit
# SNAP_SUNC_EXCLUDE="yes"