Бэкапы windows-шар через smbfs

Важное преимущество Linux, BSD и других никсов — наличие очень мощных базовых утилиты, с помощью которых можно автоматизировать многие необходимые задачи. В частности, ssh, rsync позволяют очень удобно и надёжно создавать, передавать (в том числе и за многие километры по интернету) и всячески управлять бэкапами, а с помощью архиваторов их можно и сжимать. Windows, к сожалению, не обладает таким функционалом «из коробки», да и сторонние решения не «из коробки» не всегда подходят по ряду параметров. Поэтому, логично организовать сервер резервных копий даже в Win-сети на основе Linux и стягивать на него в плановое время данные для бэкапа. Посмотрим, как это можно не слишком умудрённо сделать.

Будем изначально подходить наиболее экономным способом: с наименьшей конфигурацией исходных машин. Для этого монтируем нужную шару, делаем копию, размонтируем. Монтируем с помощью mkfs.cifs из пакета cifs-utils. Если на машину можно зайти анонимно, так и заходим, с помощью соответствующей опции

mount -t cifs //192.168.10.99/Data $data_dir -o user=guest,guest

Можно, конечно, добавить шару в fstab и держать постоянно смонтированной, но это скорее излишне, поэтому будем её монтировать только на время бэкапа. Также, на случай возможных ошибок, выбираем директорию каждый раз новую в /tmp, получить временную директорию можно

mktemp -d

Копию будем делать дифференциальную на основе жёстких ссылок. rsync позволяет делать делать hard-linked копии сам, на основе предыдущей копии, или их можно делать отдельно, а затем заменять новые файлы. Воспользуемся вторым способом: в случае обрыва бэкапа мы получим не обновлённый, но полный бэкап, который обновится в следующий раз. В первом случае его придётся загружать полностью, что не всегда разумно если канал тонкий.

С учётом всего вышеперечисленного пишем скрипт

#!/bin/sh

softmkdir() {
	if [ ! -d "$1" ] ; then
		mkdir "$1"
		chmod 0750 "$1"
	fi
}

# SETTINGS
host=`hostname -s`
backup_dir=/backup/$host/data_rsync
len=90

softmkdir "$backup_dir"

tmpname=new
tmp_dir="$backup_dir/$tmpname"
zero_dir="$backup_dir/d`printf "%02d" 0`"
prev_dir="$backup_dir/d`printf "%02d" 1`"
last_dir="$backup_dir/d`printf "%02d" $len`"

date=`date`
echo "================================="
echo "Starting backup: $date."

if [ -d "$zero_dir" ]; then
	echo "Rotating directories up to length of $len."

	# removing last directory
	if [ -d "$last_dir" ]; then
		rm -rf "$last_dir"
	fi

	# rotating
	for i in $(seq  $len -1 0); do
		src="$backup_dir/d`printf "%02d" $i`"
		if [ -d "$src" ]; then
			mv "$src" "$backup_dir/d`printf "%02d" $(($i+1))`"
		fi
	done
fi

if [ ! -d "$tmp_dir" ] ; then
	if [ -d "$prev_dir" ] ; then
		echo "Found previous backup. Making hard-linked copy."
		cp -al "$prev_dir" "$tmp_dir"
		echo "Hard-linked copy done."
		test -d "$tmp_dir" && touch "$tmp_dir"
	else
		echo "Previous backup not found. Creating init backup."
		softmkdir "$tmp_dir"
	fi
else
	echo "Found temporary directory (not finished last backup), resuming it."
fi

data_dir=`mktemp -d`
mount -t cifs //192.168.10.24/Data $data_dir -o user=guest,guest

echo "Starting rsync..."
rsync -azK --delete --ignore-errors --stats --numeric-ids $1 $data_dir/ "$tmp_dir/Data"

test -d "$tmp_dir" && touch "$tmp_dir"
echo "Rsync done."

# moving temporary to latest
echo "Moving temporary dir name to normal backup name."
mv "$tmp_dir" "$zero_dir"

umount $data_dir
rm -d $data_dir

echo "Backup finished: $date."
echo "================================="
echo ""

Проверяем, что всё работает корректно и добавляем в cron.

Настройка DKIM в exim4

Заставить exim4 подписывать исходящую почту совсем просто. Для этого в конфиг экзима /etc/exim4/exim4.conf.template дополняем следующими строками

#########################
## DKIM SETTINGS

DKIM_DOMAIN = ${lc:${domain:$h_from:}}
DKIM_FILE = /etc/mail/${lc:${domain:$h_from:}}.key
DKIM_PRIVATE_KEY = ${if exists{DKIM_FILE}{DKIM_FILE}{0}}
DKIM_SELECTOR = m01

## DKIM SETTINGS END
########################

По вкусу можно поместить конфигурацию не в шаблон, а в какой-нибудь из conf.d-файлов. Далее, чтобы все работало как надо:

  • Создаем пару ключей с помощью openssl,
  • Прописываем открытый ключ в DNS,
  • Закрытый ключ помещаем в /etc/mail/domain_name.key (вместо domain_name имя домена) и меняем его права и владельца на 0640 и root:Debian-exim,
  • Перезагружаем exim4, отправляем тестовое письмо на адрес с включенной верификацией DKIM-подписи и убеждаемся, что все работает правильно.

В вышеприведенной конфигурации селектор зафиксирован в конфиге намертво (m01), обычно этого вполне достаточно если серверная инфраструктура небольшая: каждому серверу можно присвоить свой селектор. По желанию селектор можно сделать и динамическим, формируемым из расширения файла, следующим образом:

DKIM_SELECTOR = ${extract{-1}{.}{DKIM_PRIVATE_KEY}}

Кроме настройки DKIM в exim полезно настроить обрезание определённых нежелательных заголовков в целях безопасности. Такими, например, могут быть имена формирующих письмо скриптов или IP-адреса отправителей письма (при отправке по SMTP с другой машины, которая может стоять во внутренней сети), которые раскрывают внутреннюю структуру системы. Для этого в тот же шаблон /etc/exim4/exim4.conf.template рядом с DKIM добавляем фильтр по заголовкам

system_filter = /etc/exim4/filter

Содержимое файла конфигурации фильтра /etc/exim4/filter может быть, например, таким

headers remove X-PHP-Originating-Script
headers remove Received

В конце обязательно релодим экзим и проверяем, что все работает корректно. Проверить почту можно прямо из шелла:

$ echo "This will go into the body of the mail." | mail -s "Hello world" -a "From: me@mydomain.com" myemail@example.com

PS: вышеприведенная конфигурация — Debian-based дистрибутивов. Для RedHat и других файлы конфигов могут быть немного другими.

Формирование DKIM ключей

При администрировании почтовых (да и не только почтовых, но и любых отсылающих почту) серверов время от времени требуется создавать DKIM ключи для почты. Эта, в целом несложная, процедура состоит из генерирования ключей, добавления открытого в DNS TXT-запись домена, а закрытого — в конфиг почтового сервера. Ниже по порядку.

Генерировать ключи удобно с помощью широкоиспользуемой утилиты openssl. Чтобы не делать это каждый раз руками, полезно написать скриптик

#!/bin/sh

if [ "$1" != "" ]; then
	openssl genrsa -out "$1.key" 2048
	openssl rsa -in "$1.key" -out "$1.pub" -pubout -outform PEM
	chmod o= "$1.key"
else
	echo "Usage: $0 domainname"
fi

вызывая который с параметром — доменным именем, для которого формируем ключи, получаем пару ключей: публичный (открытый) и приватный (закрытый). В приведенной конфигурации используется 2048-битный ключ, что несколько безопаснее и потому предпочтительнее.

Публичный ключ доступен всем через DNS. Для этого помещаем его в виде TXT-записи для настраиваемого домена с именем x._domainkey, где x — это селектор. Селектор выбирается произвольно и обычно он «привязан» к серверу. Содержимое записи — это сам публичный ключ и некоторые параметры перед ним. Общий формат записи в DNS:

x._domainkey.mydomain.com.   TXT "v=DKIM1; g=*; k=rsa; p=[PUBLIC_KEY]"

Если текст записи достаточно длинный (а 2048-битный таким является), то его лучше разбить на несколько строк. Например, в DNS-сервере bind9, запись будет выглядеть так:

x._domainkey.mydomain.com.	TXT ( "v=DKIM1; g=*; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqUkhw8hQQ4NfKAPkvrQqd04Ta2fyVTfrVdbud62HeSISpDzf1GWPGrN0ikqcTT+6DW5fRwOnGO0VkennMs7+d+WkpTJ6Y63ydrFaK/sa8HoESPBJHqrNfViQzlCt5ZCc4UZN1sLLoO3HukwvLRtM"
				"wKMzxIkavknl86PLcTebS3+ac7lWdPoqJbooHQglizs0YazLqQTLn/L6mqv1OgPCMU44seEp/CZilUchbLvHAsrfK8+AADGm+/U/5qt6/SC31ZN/BmAqtMMKvT8rMFw2qj43DPWSkn9ln5EsyUJhQiORNX3v+9rndZNuw2I90xXbuIflc30gLStXG1Jqg4IAXQIDAQAB" )

Первыми в записи идут опции: версия DKIM, гранулярность, тип ключа. Подробности опций лучше прочитать в соответствующем RTFM для стандарта DKIM.

После добавления открытого ключа в DNS лучше проверить его рабочесть. Из шелла это делается так

$ host -t txt x._domainkey.mydomain.com

В случае успеха команда вернет только что добавленный ключ.

Приватный ключ добавляется в настройки почтового сервера, которые несколько различаются. Обычно приватные ключи хранятся в /etc/mail, причем для безопасности их права лучше сразу поменять на 0640 и изменить владельца так, чтобы ключ смог читать почтовый демон, но не пользователи системы.

Резервные копии в Windows по сети

Используемые средства

К сожалению, Windows по сравнению с *nix-системами имеет очень слабый функционал для подготовки и копирования бэкапов. В последнее время имеется тенденция к улучшению (PowerShell), однако, полезных утилит все равно недостаточно. Обычные Windows-утилиты copy/xcopy очень плохо работают на большом количестве файлов. В интернете можно найти «продвинутую» версию xcopy под названием robocopy, именно её мы и будем использовать.

Полные бэкапы

Будем размещать резервные копии на сервере srv-bak. Копировать будем в сетевую шару \\srv-bak\bak\srv01, где srv01 — директория с именем сервера на бэкапном. Таким образом в одной директории будут храниться бэкапы с разных серверов.
На сервере с резервными копиями для каждой из них будем создавать директорию с текущей датой в качестве имени. Для удобства поместим исходные пути, которые нужно забэкапить, в файлик D:\backup\dirs.txt. Переносим всё это дело «на клавиатуру» и получаем такой скрипт:

set backupdir=\\srv-bak\bak\srv01
IF NOT EXIST %backupdir% (
	mkdir %backupdir%
)

set d=%date:~0,2%
set m=%date:~3,2%
set y=%date:~6,4%
set ndate=%y%%m%%d%

set dirsfile=D:\backup\dirs.txt

set dstpath=%backupdir%\%ndate%
IF NOT EXIST %dstpath% (
	mkdir %dstpath%
)

for /f "eol=; tokens=1 delims=" %%i in (%dirsfile%) do (
	mkdir "%dstpath%%%~pi%%~ni"
	robocopy "%%~di%%~pi%%~ni" "%dstpath%%%~pi%%~ni" /E /ZB /NP
)

Чтобы всё работало автоматически, добавляем скрипт в планировщик. Таким (простым!) способом можно бэкапить достаточно большие объемы. Единственный, но существенный, наблюдаемый баг — если какой-то из копируемых файлов занят, скрипт отработает некорректно.

Инкрементные/дифференциальные бэкапы

Приведённая схема с использованием robocopy позволяет делать и инкрементные бэкапы. Утилита имеет ключ, позволяющий копировать только файлы с определённой датой последнего изменения. Поэтому, можно модифицировать скрипт, чтобы он делал инкрементный бэкап: например копировал файлы изменившиеся за последние сутки.

set backupdir=\\srv-bak\bak\srv01
IF NOT EXIST %backupdir% (
	mkdir %backupdir%
)

set d=%date:~0,2%
set m=%date:~3,2%
set y=%date:~6,4%
set ndate=%y%%m%%d%

if "%d%"=="01" (
	set dstpath=%backupdir%\%ndate%
) ELSE (
	set dstpath=%backupdir%\d%ndate%
)

IF NOT EXIST %dstpath% (
	mkdir %dstpath%
)

set /A prev=1%d%-100-1
if %prev% LSS 10 (
	set prev=0%prev%
)

set dirsfile=D:\backup\dirs.txt

if "%d%"=="01" (
	for /f "eol=; tokens=1 delims=" %%i in (%dirsfile%) do (
		:: xcopy "%%~di%%~pi%%~ni" "%dstpath%%%~pi%%~ni\" /C /E /H /Y
		mkdir "%dstpath%%%~pi%%~ni"
		robocopy "%%~di%%~pi%%~ni" "%dstpath%%%~pi%%~ni" /E /ZB /NP
	)
) ELSE (
	for /f "eol=; tokens=1 delims=" %%i in (%dirsfile%) do (
		:: xcopy "%%~di%%~pi%%~ni" "%dstpath%%%~pi%%~ni\" /D:%m%-%prev%-%y% /C /S /H /Y
		mkdir "%dstpath%%%~pi%%~ni"
		robocopy "%%~di%%~pi%%~ni" "%dstpath%%%~pi%%~ni" /MAXAGE:2 /S /ZB /NP
		rmdir "%dstpath%%%~pi%%~ni"
	)
)

IF EXIST %dstpath% (
	rmdir %dstpath%
)

Заключительные замечания

Вместо копирования по сети иногда бывает полезным разбиение этой задачи на 2 части: монтирование шары как локального диска и последующее копирование (и размонтирование по окончании). Приведённые скрипты несложным образом дорабатываются до такой версии.
Также, на принимающем сервере возможно организовать архивирование резервных копий. Если для сервера бэкапов используется Samba, то в шелле это элементарно делается периодическим выполнением в директории с бэкапами архивации вроде:

find -mindepth 2 -maxdepth 2 -type d -ctime +7 -execdir /usr/local/bin/tar_and_remove.sh "{}" \;

или

find -mindepth 2 -maxdepth 2 -type d -ctime +7 -execdir rar a -t -m5 -r -rr1 -df -inul "{}.rar" "{}" \; &> /dev/null

Кроме этого, с никсового сервера эти бэкапы очень удобно копировать с помощью ssh/scp/rsync.

Настройка виртуализации Windows под KVM libvirt linux

Система будет тестовая и стабильность на ней не нужна, поэтому настраивать это все будем в дистрибутиве Debian jessie, который на данный момент является веткой testing.
Оригинальные и краткие руководства к действию можно найти на дебиане: KVM и QEMU. Ниже несколько подогнанная под мои задачи выжимка.

Шаблон

Если потребуется создавать много виртуальных машин или менять их параметры, очень удобно создать шаблон конфигурации виртуальных машин и скрипт её создания. Можно, конечно создавать командами вроде

virt-install --connect qemu:///system \
--name vm1 \
--ram 1024 \
--vcpus=1 \
--disk pool=storage,cache=none,size=40, format= qcow2\
--disk /vm/Win7.iso,device=cdrom \
--bridge=br0,model=e1000 \
--os-type=windows
--graphics vnc,port=5911,listen=0.0.0.0

но делать это постоянно очень неудобно. В качестве шаблона создаем конфиг вроде

<domain type='kvm'>
  <name>{name}</name>
  <memory>{ram}</memory>
  <currentMemory>{ram}</currentMemory>
  <vcpu>1</vcpu>
  <os>
    <type arch='x86_64' machine='pc-0.12'>hvm</type>
    <boot dev='cdrom'/>
    <boot dev='hd'/>
  </os>
  <features>
    <acpi/>
    <apic/>
    <pae/>
  </features>
  <clock offset='localtime'/>
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>restart</on_crash>
  <devices>
    <emulator>/usr/bin/kvm</emulator>
    <disk type='file' device='disk'>
      <driver name='qemu' type='raw' cache='none'/>
      <source file='/vm/{name}.img'/>
      <target dev='vda' bus='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
    </disk>
    <disk type='file' device='disk'>
      <driver name='qemu' type='raw' cache='none'/>
      <source file='/vm/virtio.img'/>
      <target dev='hda' bus='ide'/>
      <address type='drive' controller='0' bus='0' unit='0'/>
    </disk>
    <disk type='file' device='cdrom'>
      <driver name='qemu' type='raw'/>
      <source file='/vm/iso/ru_windows_8.1_professional_vl_with_update_x64_dvd_4050520.iso'/>
      <target dev='hdc' bus='ide'/>
      <readonly/>
      <address type='drive' controller='0' bus='1' unit='0'/>
    </disk>
    <disk type='file' device='cdrom'>
      <driver name='qemu' type='raw'/>
      <source file='/vm/virtio-win-0.1-30.iso'/>
      <target dev='hdc' bus='ide'/>
      <readonly/>
      <address type='drive' controller='0' bus='1' unit='1'/>
    </disk>
    <controller type='ide' index='0'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
    </controller>
    <interface type='bridge'>
      <mac address='52:54:00:9d:be:d9'/>
      <source bridge='br0'/>
      <model type='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
    </interface>
    <serial type='pty'>
      <target port='0'/>
    </serial>
    <console type='pty'>
      <target type='serial' port='0'/>
    </console>
    <input type='tablet' bus='usb'/>
    <input type='mouse' bus='ps2'/>
    <graphics type='vnc' port='{port}' autoport='no' listen='0.0.0.0' keymap='en-us' passwd='{passwd}'/>
    <video>
      <model type='vga' vram='9216' heads='1'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
    </video>
    <memballoon model='virtio'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>
    </memballoon>
  </devices>
</domain>

Типовые шаблоны можно найти в документации, идущей вместе с пакетами. Также создаём устанавливающий скрипт vmcreate.sh:

#!/bin/sh

template=template.xml
path=/vm

while true; do
	read -p "VM name: " name
	if [ "$name" ]; then
		break
	fi
done


while true; do
	read -p "VNC port: " port
	case $port in
		[0-9]* ) break;;
	esac
done

while true; do
	read -p "VNC password: " password
	if [ "$password" ]; then
		break
	fi
done

while true; do
	read -p "HDD size in Gb [50]: " hdd
	case $hdd in
		[0-9]* ) break;;
		* )	hdd=50
			break;;
	esac
done

while true; do
	read -p "RAM in Mb [1536]: " ram
	case $ram in
		[0-9]* ) break;;
		* )	ram=1536
			break;;
	esac
done


echo "VM name: $name"
echo "VNC port: $port"
echo "Password: $password"
echo "HDD : $hdd Gb"
echo "RAM : $ram Mb"

while true; do
	read -p "Continue [y/N]? " yn
	case $yn in
		[Yy]* ) cont=1
			break;;
		* ) cont=0
			break;;
	esac
done

ramb=$((1024*$ram))

if [ -f "$path/$name.img" ]; then
#	cont=0
	echo "File $path/$name.img exist."
else
	qemu-img create -f raw "$path/$name.img" ${hdd}G
fi


if [ "$cont" -eq 1 ]; then
	sed "s/{name}/$name/g" $path/$template | sed "s/{port}/$port/g" | sed "s/{passwd}/$password/g" | sed "s/{hdd}/$hdd/g" | sed "s/{ram}/$ramb/g" > $path/$name.orig.xml
#	virsh create $name.orig.xml
	virsh define $name.orig.xml
	virsh start $name
else
	echo "Aborted."
fi

После такой подготовки виртуальная машина создается фактически запуском этого скрипта и ответом на вопросы. Конечную конфигурацию можно всегда поправить в XML-конфигах /etc/libvirt/qemu или с помощью утилиты virsh.

Типовая установка Windows в виртуалке

Драйвер от Win7 x64 вполне годится и для Windows 8.1, выбираем его при установке(Red Hat VirtIO SCSI controller, VIOSTOR.INF VirtIO Balloon Driver BALLOON.INF). Там же находим драйвер для сетевой карты, он пригодится позже. После этого виртуальный HDD находится системой, запускаем установку.
Уже на установленной системе ставим VirtIO-драйвер для Ethernet от Red Hat и настраиваем параметры сети.

Сеть

Для работы сети потребуется некоторая маршрутизация на самом хосте. Ниже представлен не самый оптимальный, но рабочий вариант для большинства (не-production) применений. А именно, разрешим forwarding на основной машине и явно пропишем пути из NAT-сети в интернет.
/etc/network/interfaces

up route add -host 10.0.5.2 dev br0

Также в местном фаерволле, т.е. скрипте, который настраивает iptables-правила, добавляем локальную сеть и нужные перенаправления:

IPTABLES=/sbin/iptables

echo 1 > /proc/sys/net/ipv4/ip_forward 2> /dev/null

ip addr add 10.0.6.1./24 dev br0
$IPTABLES -F -t nat
$IPTABLES -X -t nat

LAN="10.0.5.0/24"

$IPTABLES -A INPUT -s $LAN -j ACCEPT
$IPTABLES -A OUTPUT -s $LAN -j ACCEPT

$IPTABLES -t nat -A POSTROUTING -s $LAN -o br0 -j MASQUERADE

$IPTABLES -t nat -A PREROUTING -p tcp --dport 55003 -j DNAT --to-destination 10.0.5.3:3389

Управление с помощью утилиты virsh

Утилитой virsh совершаются штатно многие операции с вируалками. Наиболее часто используются старт, мягкая и жёсткая остановка

virsh start VMNAME
virsh shutdown VMNAME
virsh destroy VMNAME

Удалить существующую виртуальную машину (совсем!) также можно этой утилитой:

virsh undefine VMNAME

Как правило, виртуальная машина ставится надолго, поэтому её целесообразно стартовать при загрузке самой машины:

virsh autostart VMNAME

Завершающие дополнения

Конфиги виртуальных машин находятся в /etc/libvirt/qemu и могут быть исправлены до нужного состояния. В нём можно поменять физические параметры контейнера (объем ОЗУ, количество ядер и т.п.), также подключить/отключить образы жёстких дисков и CD/DVD.

На *nix-системах время обычно хранится в UTC, тогда как Windows его хранит в локальном часовом поясе. Проблему решает reg-файл следующего содержания:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation]
"RealTimeIsUniversal"=dword:00000001

Ограниченный SFTP-доступ на сервер

Часто возникает задача дать доступ на сервер в определенную директорию по протоколу sftp, чтобы при этом не было доступа в весь корень файловой системы. Т.е. используя технику chroot. Делается это следующим образом.

Сначала создается пользователь в системе. Обычно по sftp заходят для правки контента и группа пользователя назначается общей для этого рода пользователей (например, www-data). Всё это делается опциями к adduser или useradd.

Далее в /etc/ssh/sshd_config созданному пользователю newuser добавляется кусок конфига, который определяет параметры входа и chroot:

Match User newuser
	ChrootDirectory %h
	ForceCommand internal-sftp
	AllowTcpForwarding no
	X11Forwarding no

После изменения конфига, естественно, нужно рестартануть sshd.

На этом все не заканчивается. Вот что гласит мануал sshd_config:

ChrootDirectory
Specifies the pathname of a directory to chroot(2) to after authentication. All components of the pathname must be root-owned directories that are not writable by any other user or group. After the chroot, sshd(8) changes the working directory to the user’s home directory.

Из соображений безопасности, чтобы работал chroot, директория пользователя должна быть от пользователя root (при этом группа может быть не рутовой). Выполняем это требование, voila и всё работает.

Также для безопасности обычно имеет смысл отключить шелл пользователя

	usermod newuser -s /bin/false

PS: Это все применимо именно для sftp. К сожалению, для scp это не работает и при попытке подключиться по ssh/scp произойдет ошибка.

Экономные дифференциальные бэкапы с hard links

Часто возникает задача делать backup больших объемов редко изменяющихся файлов. Обычно для этого используют замечательную утилиту rsync, которая синхронизирует обновленные файлы, чем существенно экономит время бэкапа и сетевой траффик. Количество копий бэкапа, на которые происходит синхронизация, произвольно и чем оно больше — тем лучше. Естественное ограничение — свободное местом носителя. И здесь на помощь приходят две полезные опции рсинка:

--compare-dest=DIR also compare received files relative to DIR
--link-dest=DIR hardlink to files in DIR when unchanged

Первая из них создает чисто дифференциальный бэкап сравнением с существующим, то есть помещает в новый бэкап только изменившиеся файлы. Вторая — создает полный бэкап, при этом не изменившиеся файлы с помощью hardlinks ссылаются на забэкапленные. В плане использования свободного места бэкапного сервера различия несущественны, но чисто дифференциальный для использования требует наличия директории сравнения. Основанный на hardlinks — можно сразу использовать «как есть», что намного удобнее. Поэтому, будем рассматривать вариант именно с жесткими ссылками.

Логика бэкапов сделаем наподобие logrotate. Новая директория добавляется с номером 0, при этом существующие сдвигаются на +1 вперед. Количество бэкапов ограничим: последняя директория будет удаляться. Получаем следующий скрипт:

#!/bin/sh

servname="mailserv"

backup_dir=/backup/$servname/vmail
len=60

tmpname=--temp--

curr_dir="$backup_dir/$tmpname"
zero_dir="$backup_dir/`printf "%02d" 0`"
prev_dir="$backup_dir/`printf "%02d" 1`"

last_dir="$backup_dir/`printf "%02d" $len`"

date=`date`
echo "Starting backup: $date."

if [ -d "$zero_dir" ]; then
	echo "Rotating directories up to $len."

	# removing last directory
	if [ -d "$last_dir" ]; then
		rm -rf "$last_dir"
	fi

	# rotating
	for i in $(seq  $len -1 0); do
		src="$backup_dir/`printf "%02d" $i`"
		if [ -d "$src" ]; then
			mv "$src" "$backup_dir/`printf "%02d" $(($i+1))`"
		fi
	done
fi

if [ -d "$curr_dir" ] ; then
	echo "Warning: \"$curr_dir\" already exists. Probably it was not renamed during last run. Resuming it."
fi


if [ -d "$prev_dir" ] ; then
	echo "Making hard-linked incremental backup."
	rsync -rtzKL --delete --stats --link-dest="$prev_dir" -v $servname:/var/vmail "$curr_dir"
#	rsync -rtzKL --delete --stats --compare-dest="$prev_dir" $servname:/var/vmail "$curr_dir"
else
	echo "Making init backup."
	#rsync -rtzKL -e "ssh -i /home/bu/.ssh/id_dsa" --delete --stats $servname:/var/vmail "$curr_dir"
	rsync -rtzKL --delete --stats $servname:/var/vmail "$curr_dir"
fi

# moving temporary to latest
mv "$curr_dir" "$zero_dir"

echo "Backup finished: $date."
echo "================================="
echo ""

В результате на файловой системе с поддержкой жестких ссылок можно разместить бэкапов размером на порядки больше, чем размер самого носителя. Например бэкап почтового сервера размером ~870 Gb состоящий из 30 копий занимает всего 970 Gb.

PS: Для систем с разделением прав доступа и разными пользователями такой бэкап желательно делать с опцией --numeric-ids, которая сохранит (цифровые) owner:group.