Когда дома есть в наличии большой компьютер и маленький, может возникнуть необходимость держать некоторые данные и там, и там. Причем, держать синхронизированно. Лично я, например, регулярно пользуюсь мега-программой для заметок Basket, и конечно мне хотелось бы, чтобы все заметки были доступны и на нотбуке, и на десктопе.
В Линуксе для решения задачи синхронизации есть все необходимое. В принципе, есть и готовые программы для синхронизации, но я считаю, что для моего случая достаточно rsync, md5sum и небольшого скрипта на bash. Ну и сетевого раздела NFS — чтобы через него синхронизироваться. Далее о том, как все делается.
Синхронизацию я провожу через сетевой раздел NFS, открытый на десктопе. В openSUSE, которая у меня установлена везде, поднять раздел NFS — дело трех кликов в yast2. Стоит однако заметить, что если на нотбуке используется Network Manager, имеет смысл немного изменить запись в /etc/fstab. Дело в том, что Network Manager подключает WiFi только после логина и загрузки оконного менеджера (KDE, в моем случае), а монтирование происходит раньше. Естественно, ввиду отсутствия доступа к сети, раздел NFS смонтирован не будет, да к тому же загрузка замедлится на несколько минут в ожидании ответа от сервера. Поэтому строчку, отвечающую за нужный раздел в /etc/fstab лучше подредактировать, добавив ключи noauto и users:
:<...>/Share <...>/Share nfs users,noauto,rw 0 0
Ключ noauto отключает автоматическое монтирование раздела при загрузке, а users — разрешает простым смертным (а не только root-у) монтировать этот раздел по желанию. После этого достаточно (для KDE) в ~/.kde4/Autostart/ добавить шелл-скрипт:
#!/bin/sh
mount PATH/TO/Share
и выставить ему права на исполнение. Тогда при загрузке NFS-раздел будет монтироваться уже после старта KDE.
Теперь обозначу основные особенности рассматриваемого случая. Basket хранит каждую заметку в отдельном файле, корзины с заметками — это директории, причем вложенные корзины — это вложенные директории. Синхронизировать нужно, соответственно:
- группу файлов;
- с соблюдением вложенности директорий;
- с поддержкой удаления файлов;
- с пересылкой только изменившейся информации (файлов может быть более 9000!).
Для этого очень хорошо подходит консольное приложение rsync. Строчка
rsync -arz --progress --delete $LOCAL_DIR $SRV_DIR >> $LOG_FILE
проводит синхронизацию из директории $LOCAL_DIR в $SRV_DIR с удалением файлов и записью информации о ходе пересылки в файл $LOG_FILE. В принципе на этом можно было бы и остановиться, но к сожалению эта строчка сама по себе едва ли годится для автоматической синхронизации. Дело в том, что команда хотя и проводит синхронизацию как надо, но при этом не подразумевает никакого решения вопроса «что новее — источник или приемник». То есть пользователь должен решить это сам, что конечно исключает автоматизм.
То есть нужно автоматически принимать решение, где данные новее — на сервере, или в локальной копии — и уже на основании этого вывода запускать rsync. Алгоритм придумывается простой, на основе хэша синхронизируемых данных:
- При каждом обновлении сервера вычисляется хэш данных и сохраняется в файл.
- Файл с хэшем копируется на компьютер, с которого было обновление.
- При следующей синхронизации локальная копия хэша сравнивается с хранимой на сервере.
- Если они различаются, значит данные на сервере обновились и нужно обновить локальную копию.
- Если они одинаковы, вычисляется хэш локальных данных и сравнивается с серверным хэшем.
- Если хэши отличаются, значит обновились локальные данные и нужно залить их на сервер.
Под сервером здесь подразумевается, конечно, раздел NFS.
Для вычисления хэша синхронизируемой директории используются команды:
cd $LOCAL_DIR
find ./ -type f -print0 | sort -z | xargs -0 md5sum | md5sum
Такое вычисление хэша учитывает как иерархию поддиректорий, так и содержимое файлов. При этом обязательно предварительно переходить в директорию (cd $LOCAL_DIR). Записать просто find $LOCAL_DIR/ ... нельзя, так как тогда учитываемый в вычислении хэша путь к файлам будет вместо ./ содержать полный путь, а пути к сетевой и локальной копии, естественно, отличаются, то есть хэши всегда будут различными.
Это, в общем-то, все. Имеет смысл отметить недостатки получившейся системы. Их я вижу два:
- Синхронизация очевидно не имеет средств разрешения конфликтов (когда данные изменились и локально, и на сервере). Но на это она и не рассчитана, я ведь один и не буду скакать по компьютерам и менять все подряд.
- Из-за сложности вычисления хэша, синхронизация будет очень долго работать по директориям с большими объемами данных (фото, видео, аудио). Это проблема, ага.
Финальный скрипт:
#!/bin/sh
#файл для лога синхронизации
LOG_FILE="$HOME/logs/sync.log"
#путь к серверной копии данных
SRV_DIR="$HOME/Share/baskets/"
#путь к локальной копии данных
LOCAL_DIR="$HOME/.kde4/share/apps/basket/baskets/"
#файл с хэшем сервера
SRV_HASH_FILE="$HOME/Share/.basket_hash"
#файл с локальной копией хэша сервера
LOCAL_SRV_HASH_FILE="$HOME/.basket_hash_local"
#файлы с хэшем должны быть _вне_ синхронизируемых директорий!
#читаем хэши
SRV_HASH=$(cat $SRV_HASH_FILE)
SRV_LOCAL_HASH=$(cat $LOCAL_SRV_HASH_FILE)
echo 'Начало синхронизации' > $LOG_FILE
echo "серверный хэш = $SRV_HASH" >> $LOG_FILE
echo "локальная копия серверного хэша = $SRV_LOCAL_HASH" >> $LOG_FILE
# сравниваем хэши
if [ "$SRV_HASH" == "$SRV_LOCAL_HASH" ]; then
echo "хэш на сервере и его локальная копия совпадают" >> $LOG_FILE
#сравниваем хэши клиента и сервера
cd $LOCAL_DIR
LOCAL_HASH=$(find ./ -type f -print0 | sort -z | xargs -0 md5sum | md5sum);
echo "хэш клиента = $LOCAL_HASH" >> $LOG_FILE
if [ "$LOCAL_HASH" != "$SRV_HASH" ]; then
echo "хэш клиента не совпадает с сервером, засылаем данные" >> $LOG_FILE
rsync -arz --progress --delete $LOCAL_DIR $SRV_DIR >> $LOG_FILE
#обновляем серверный хэш
cd $SRV_DIR
find ./ -type f -print0 | sort -z | xargs -0 md5sum | md5sum > $SRV_HASH_FILE
cp $SRV_HASH_FILE $LOCAL_SRV_HASH_FILE
echo "новый серверный хэш = $(cat $SRV_HASH_FILE)" >> $LOG_FILE
fi
else
echo "хэш на сервере не совпал с локальной копией, скачиваем данные" >> $LOG_FILE
rsync -arz --progress --delete $SRV_DIR $LOCAL_DIR >> $LOG_FILE
# скачиваем серверный хэш
cp $SRV_HASH_FILE $LOCAL_SRV_HASH_FILE
#выводим хэши
echo "копия серверного хэша = $(cat $LOCAL_SRV_HASH_FILE)" >> $LOG_FILE
#для отладки выводим новый клиентский хэш
cd $LOCAL_DIR
echo "новый хэш клиента = $(find ./ -type f -print0 | sort -z | xargs -0 md5sum | md5sum)" >> $LOG_FILE
fi
echo 'Синхронизация завершена!' >> $LOG_FILE
Просто отредактируйте пути в начале скрипта, положите его в ~/bin, выдайте права на запуск и добавьте, например, в cron на всех машинах, участвующих в синхронизации. И все.
Кстати. Перед установкой обязательно бэкапните синхронизируемые данные. Мало ли что.