rsync-over-CDRW
Для чего это нужно
Есть два места (назовём их "дом" и "работа"). "На работе" есть быстрый канал и зеркало Сизифа, "дома" только зеркало, принесённое с "работы" некоторое время назад. Хочется периодически синхронизировать "домашнее" и "рабочее" зеркала. Можно таскать жёсткий диск с "работы" "домой" раз в неделю (месяц, пол года), но это быстро надоедает. Поэтому будем копировать только изменения. Единственное условие - "дома" и "на работе" зеркала должны находиться по одному пути (например /var/ftp/Sisyphus)
"Дома"
"Дома" надо понять, что именно у нас есть. Самый простой способ - пройтись по всему синхронизируемому дереву и посчитать md5 суммы файлов. Так как в самом Сизифе находится некоторое (довольно большое) количество символических ссылок, надо отслеживать и их. Для этого нарисуем простенький скрипт и назовём его 'lmd5sum':
#!/bin/sh
while [ -n "$1" ]; do
if [ -L "$1" ]; then
echo "`readlink "$1" | md5sum | cut -b 1-33` $1"
else
md5sum "$1"
fi
shift
done
exit $?
Далее помещаем lmd5sum куда-нибудь в $PATH и создаём "снимок" нашего зеркала:
find "/var/ftp/Sisyphus" \( -type f -or -type l \) -print0 | xargs -0 lmd5sum | sort -k 2 > `date +%Y%m%d`.list
С этим списком и скриптом lmd5sum благополучно едем "на работу"
"На работе"
У нас есть текущее зеркало Сизифа и "домашний" "снимок" старого репозитария. Аналогичным образом создаём "рабочий" "снимок":
find "/var/ftp/Sisyphus" \( -type f -or -type l \) -print0 | xargs -0 lmd5sum | sort -k 2 > `date +%Y%m%d`.list
И смотрим изменения:
diff -U0 домашний_снимок рабочий_снимок > difference.list
Далее надо пройтись по difference.list, для всех "удалённых" строк удалить файлы, а для "добавленных" поместить в архив. Сделаем это сразу, создав скрипт, который удалит "старые" файлы и распакует архив с "новыми". Сам архив "приклеим" к скрипту обновления:
cat difference.list | perl -e '
open UPSH, '>', "update.sh" or die;
print UPSH "#!/bin/sh\n";
print UPSH "echo -e \"\\nMaking update to $ENV{cdate}\\n\"\n";
$l = 7;
while(<>) {
chomp;
($w, $f) = ($_ =~ /(.)[0-9a-f]{32}.\s*(\S+)/);
next if $w !~ /[+-]/;
if ($w =~ /-/) {
print UPSH "rm -f \"".$f."\" && echo \"- ".$f."\"\n";
$l++;
} else {
print $f."\000";
}
}
print UPSH "\n";
print UPSH "tail +$l \"\$0\" | tar xPvf - | xargs -i echo \"+ {}\"\n";
print UPSH "apt-get update\n";
print UPSH "exit \$?;\n";
close UPSH;
' > newfiles
tar cPf - --null -T newfiles >> update.sh
У нас получился большой файл update.sh, который можно записывать на CDRW и относить "домой".
Снова "дома"
Дальше всё просто - sh /path/to/update.sh и наблюдаем за процессом. Сначала удалятся "старые" файлы, потом распакуются "новые". Сделано это по той причине, что может измениться файл с постоянным именем (например хэши apt). Если сначала распаковать "новое", а потом удалить "старое", такие файлы будут удалены.
Пишем скрипт
Так как "юниксоиды - народ ленивый, им проще потратить два часа на написание скрипта, который за две минуты сделает получасовую работу" (народная мудрость), напишем скрипт, который всё сделает за нас. Назовём его 'makeupdate':
#!/bin/sh
Usage() {
cat <<EOF
Usage: ${0##*/} {--list | DATE} DIR
EOF
exit $1
}
Exit() {
local rc=$?
trap '' EXIT
rm -f -- $tmpfile
exit $rc
}
if [ ! $# -eq 2 ]; then
Usage 1 1>&2
fi
file=""
dir=""
what=""
export cdate=`date +%Y%m%d`
tmpfile=`mktemp -t makeupdate.XXXXXXXXXX`
trap 'Exit ' EXIT HUP INT PIPE TERM QUIT
sdate="$1"
dir="$2"
find "$dir" \( -type f -or -type l \) -print0 | xargs -0 lmd5sum |
sort -k 2 |
(if [ "$sdate" = "--list" ]; then
cat > $cdate.list
else
tee $cdate.list |
diff -U0 $sdate.list - |
perl -e '
open UPSH, ">update.sh" or die;
print UPSH "#!/bin/sh\n";
print UPSH "echo -e \"\\nMaking update to $ENV{cdate}\\n\"\n";
$l = 7;
while(<>) {
chomp;
($w, $f) = ($_ =~ /(.)[0-9a-f]{32}.\s*(\S+)/);
next if $w !~ /[+-]/;
if ($w =~ /-/) {
print UPSH "rm -f \"".$f."\" && echo \"- ".$f."\"\n";
$l++;
} else {
print $f."\000";
}
}
print UPSH "\n";
print UPSH "tail +$l \"\$0\" | tar xPvf - | xargs -i echo \"+ {}\"\n";
print UPSH "apt-get update\n";
print UPSH "exit \$?;\n";
close UPSH;
' > $tmpfile
tar cPf - --null -T $tmpfile >> update.sh
fi )
rm -f $tmpfile
trap '' EXIT
exit 0
Есть два варианта запуска скрипта. "Дома":
makeupdate --list /path
Создаёт в текущем каталоге файл $curdate.list, который можно относить "на работу". "На работе":
makeupdate дата /path
Ищет в текущем каталоге файл дата.list, создаёт текуший "снимок" $curdate.list и скрипт с обновлением update.sh
Далее makeupdate можно запускать только "на работе".