Бэкап сетевой шары (samba) в Linux по мотивам Windows Server Backup

Делаем удобный доступ к архивам (и создаем эти архивы) сетевых шар, для клиентов работающих под Windows.

Введение


Чем хороша служба Windows Server Backup и теневые копии? Они входят в поставку Windows Server и не требуют доплаты (если не использовать облачную архивацию), а также хорошо справляются с возложенными на них задачами. Для простых сценариев использования — очень достойное решение. А доступ к теневым копиям через диалог свойств файла — вообще очень удобен. Теперь попробуем сделать аналогично для файлового сервера Linux с Samba.

Доступ к предыдущим версиям файлов


Эту возможность нам дает модуль Samba shadow_copy2.
Его надо прописывать в секции сетевого ресурса в файле smb.conf
[share]
vfs objects = shadow_copy2
shadow:snapdir = /mnt/.share
path = /mnt/share

В отличие от модуля первой версии, этот позволяет разместить папку с копиями в разных местах и с разными именами.
Теперь, если внутри папки path = /mnt/.share мы создадим подпапку @GMT-2016.12.25–10.17.52
то у нас ничего не выйдет. Добавим такие настройки в секции [general]
   wide links = yes # разрешаем samba проходить по символьным ссылкам
   unix extensions = no # запретим *nix клиентам создание символьных ссылок (и еще кучу возможностей,
# которые вам могут не понадобиться)
   allow insecure wide links = no # эту опцию включаем в yes только при включенных unix extensions
# иначе wide links останутся недоступны, и дыры в безопасности не будет

Теперь в свойствах сетевой шары, в разделе предыдущих версий, мы увидим нашу «копию». Обратите внимание, время указывается в UTC и преобразуется в локальное по часовому поясу.

Создание архивов и snapshot


Иметь механизм доступа к копиям без механизма их создания — бесполезно. В этом нам поможет следующий скрипт:
thin_lv_backup.sh
#!/bin/bash
#
#     LVM-ThinVolume BackUp with rsync script set
#
#     (c) 2016 -
#         Andrew Leshkevich (magicgts@gmail.com)
#
#
#	This script set is free software; you can redistribute it and/or modify
#	it under the terms of the GNU General Public License as published by the
#       Free Software Foundation, either version 2 of the license or, at your
#       option, any later version.
#
#	This program is distributed in the hope that it will be useful,
#	but WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#	GNU General Public License for more details.
#
#	You should have received a copy of the GNU General Public License
#	along with this program; if not, see .
#
#       For a list of supported commands, type 'thin_lv_backup help'
#
#	!!!	Please forgive me for bad english	!!!
#
################################################################################################

################################################################################################
#Mount the snapshot to the specified mount point, if a snapshot is not active, then activate it
# Arguments:
#   ${1} - Short path to Volume (in VG/LV format)
#   ${2} - Mount point
#   ${3} - Optional LMV Volume attribute
# Returns:
#   Return 0 if no errors
################################################################################################
mount_snapshot(){
	local SRC=${1}
	local MNT_TGT=${2}
	[ "$#" -lt 2 ] && echo 'Error: expected /  []' && return 1
	if [ "$#" -eq 2 ]; then 
		local ATTR=$(lvs --noheadings -o lv_attr ${SRC})
	else
		local ATTR=${3}
	fi
	findmnt -nf --source /dev/${SRC} >/dev/null 2>&1 && echo "Skip: LV ${SRC} is already mounted!" && return 0
	findmnt -nf --target ${MNT_TGT} >/dev/null 2>&1 | grep -v -q ${MNT_TGT} && echo "Skip: the directory ${MNT_TGT} is already a mount point" && return 3
	if [ ! -d "${MNT_TGT}" ]; then
		mkdir -p "${MNT_TGT}" || echo "Error: Creating directory ${MNT_TGT}" || return 4
		echo "Info: directory ${MNT_TGT} has been created"
	fi
	find ${MNT_TGT} -prune -empty | grep -v -q ${MNT_TGT} && echo "Skip: ${MNT_TGT} directory is not empty" && return 5
	[[ ${ATTR} =~ .*a.* ]] || lvchange -ay -K ${SRC} || echo "Error: Volume Activation ${SRC}" || return 6
	mount -o ro,nouuid /dev/${SRC} ${MNT_TGT} || echo "Error: Mounting ${MNT_TGT}" || return 7
	return 0
}

################################################################################################
# UnMount snaphot, deactivate volume and remove it mount point directory
# Arguments:
#   ${1} - Short path to Volume (in VG/LV format)
# Returns:
#   Return 0 if no errors
################################################################################################
umount_snapshot(){
	local SRC=${1}
	local TGT
	[ "$#" -ne 1 ] && echo 'Error: expected /' && return 1
	local _TGT=("$( findmnt -nf --source /dev/${SRC} | cut -d ' ' -f 1 )")
	if [ ! -z "$_TGT" ]; then
		umount -A /dev/${SRC} || echo "Error: Umounting ${SRC}" || return 2
		for TGT in "${_TGT[@]}"; do
			find ${TGT} -prune -empty | grep -q "${TGT}" && rm --one-file-system -df ${TGT}
			[ -d "${TGT}" ] && echo "Info: Fail to remove target directory ${TGT}"
		done
	fi
	lvchange -an -K ${SRC} || echo "Error: Volume Deactivation ${SRC}" || return 3
	return 0
}

################################################################################################
# Mount all associated snapshots of the volume to its origin mount points
# All snapshots must be named on the template: -GMT-%Y.%m.%d-%H.%M.%S
# Arguments:
#   ${1} - Short path to Origin Volume (in VG/LV format)
#   ${2} - Optional archive volume group, that used to mount all archive snapshots
# Returns:
#   Return 0 if no errors
################################################################################################
mount_all_snapshot(){
	local SRC=${1}
	local A_VG=${2}
	local ATTR_S
	local SNAP
	[ "$#" -lt 1 ] && echo 'Error: expected / []' && exit 1
	IFS=$'/' read -r -a ATTR_S <<< "${SRC}"
	[ "$#" -eq 2 ] && ATTR_S[0]=${A_VG}
	local SRC="$( findmnt -nf --source /dev/${SRC} | cut -d ' ' -f 1 )/"
	local DST_BASE="$( dirname ${SRC} )/.$( basename ${SRC} )/"
	while IFS='' read -r SNAP; do
		IFS=$' \t' read -r -a ATTR <<< "${SNAP}"
		local DST=${ATTR[0]//${ATTR_S[1]}-/}
		mount_snapshot ${ATTR_S[0]}/${ATTR[0]} ${DST_BASE}@${DST} ${ATTR[1]}  || echo "Error: mounting ${ATTR_S[0]}/${ATTR[0]}"
	done < <( lvs --noheadings -o lv_name,lv_attr -S Origin=${ATTR_S[1]} ${ATTR_S[0]} )
}

################################################################################################
# UnMount and Remove snapshot
# Arguments:
#   ${1} - Short path to Snapshot Volume (in VG/LV format)
# Returns:
#   Return 0 if no errors
################################################################################################
remove_snaphot(){
	local TGT=${1}
	local ATTR_S
	[ "$#" -ne 1 ] && echo 'Error: expected /' && return 1
	IFS=$'/' read -r -a ATTR_S <<< "${TGT}"
	[ -z $(lvs --noheadings -o Origin -S lv_name=${ATTR_S[1]} ${ATTR_S[0]}) ] && echo "Error: not a snapshot ${TGT}" && return 2
	umount_snapshot ${TGT} || echo "Error: umounting snapshot ${TGT}" || return 3
	lvremove -f /dev/${TGT} || echo "Error: removing snapshot ${TGT}" || return 4
	return 0
}

################################################################################################
# Create and Mount it to hidden folder on top level with same name as Original mount point
# Arguments:
#   ${1} - Short path to Origin Volume (in VG/LV format)
#   ${2} - Optional postfix, that replace default postfix GMT-%Y.%m.%d-%H.%M.%S
# Returns:
#   Return 0 if no errors
################################################################################################
create_snaphot(){
	local TGT=${1}
	local ATTR_S
	[ "$#" -lt 1 ] && echo 'Error: expected / []' && exit 1
	local DATE=$(date -u +GMT-%Y.%m.%d-%H.%M.%S)
	[ "$#" -eq 2 ] && DATE="${2}"
	IFS=$'/' read -r -a ATTR_S <<< "${TGT}"
	lvcreate -n ${ATTR_S[1]}-${DATE} -s /dev/${TGT} || echo "Error: Creating snapshot of ${TGT}" || return 2
	local SRC="$( findmnt -nf --source /dev/${TGT} | cut -d ' ' -f 1 )/"
	local DST_BASE="$( dirname $SRC )/.$( basename $SRC )/"
	mount_snapshot ${TGT}-${DATE} ${DST_BASE}@${DATE} || echo "Error: Mounting snapshot ${TGT}-${DATE}" || return 3
}

################################################################################################
# Remove old snaphots and keep last N snapshot
# Arguments:
#   ${1} - Short path to Origin Volume (in VG/LV format)
#   ${2} - Number of keeping snapshot
# Returns:
#   Return 0 if no errors
################################################################################################
remove_old_snapshot_copy(){
	local TGT=${1}
	local NUM=${2}
	local SNAP
	local ATTR_S
	local ATTR
	[ "$#" -ne 2 ] && echo 'Error: expected / ' && return 1
	IFS=$'/' read -r -a ATTR_S <<< "${TGT}"
	while IFS='' read -r SNAP; do
		IFS=$' \t' read -r -a ATTR <<< "${SNAP}"
		local DST=${ATTR[0]//${ATTR_S[1]}-/}
		remove_snaphot ${ATTR_S[0]}/${ATTR[0]} || echo "Error: removing snapshot ${ATTR_S[0]}/${ATTR[0]}"
	done < <( (lvs --noheadings -O -lv_name -o lv_name -S Origin=${ATTR_S[1]} ${ATTR_S[0]}) | head -n -${NUM} )
	return 0
}

################################################################################################
# Prepare archive operation
# Arguments:
#   ${1} - Short path to Origin Volume (in VG/LV format)
#   ${2} - Mount point for ${1}
# Returns:
#   Return 0 if no errors
################################################################################################
pre_archive(){
	[ "$#" -ne 2 ] && echo 'Error: expected / ' && return 1
	local VOL_SRC=${1}
	local MNT_TGT=${2}
	mkdir -p ${MNT_TGT}
	mount /dev/${VOL_SRC} ${MNT_TGT} || echo "Error: Mounting ${MNT_TGT}" || return 7
}

################################################################################################
# Post archive operation: unmount target volume, remove its mount point, create its snaphot
# Arguments:
#   ${1} - Short path to Origin Volume (in VG/LV format)
#   ${2} - Mount point for ${1}
# Returns:
#   Return 0 if no errors
################################################################################################
post_archive(){
	[ "$#" -ne 3 ] && echo 'Error: expected /  /' && return 1
	local VOL_SRC=${1}
	local MNT_TGT=${2}
	local TGT=${3}
	umount /dev/${VOL_SRC} && rm -rd ${MNT_TGT} && lvcreate -n ${TGT} -s /dev/${VOL_SRC} && return 0
	return 1
}
################################################################################################
# Create rsync archive
# Arguments:
#   ${1} - Short path to Origin Volume (in VG/LV format)
#   ${2} - Name of archive Volume Group
#   ${3} - Optional connection string in @ format
#   ${4} - Optional path to this script on remote machine
#   ${5} - Optional prefix name for volume name on remote machine (--GMT-%Y.%m.%d-%H.%M.%S)
#   ${6} - Optional also make local archive
# Returns:
#   Return 0 if no errors
################################################################################################
create_archive(){
	local SRC=${1}
	local TGT=${2}
	local CONN=${3}
	local CALL=${4}
	local PREFIX=${5}
	local ATTR_S
	local ATTR_D
	local RESULTS
	local RET
	[ "$#" -lt 2 ] && echo 'Error: expected /  [