Задача — написать скрипт для домашней машинки, что бы создавать бекапы по расписанию используя rsync, с уведомлениями на почту и в трее.
Бекапы хранятся на отдельном USB SSD.
Описание настройки отправки почты пользователю есть в посте Arch Linux: ssmtp – отправка локальной почты.
Ниже — процесс написания такого скрипта с примерами функций. Сам скрипт доступен в Github тут>>>.
Создаём каталог для скрипта:
[simterm]
$ sudo mkdir /opt/home-backups-bash $ sudo chown setevoy:setevoy /opt/home-backups-bash
[/simterm]
И начинаем писать скрипт.
Нам нужны будут следующие функции:
- проверка того, что
/backupсмонирован - функция для создания уведомлений, которые будут выводиться на рабочий стол и на почту
- сама функция бекапа
Содержание
check-backups-fs()
Начнём с проверки наличия /backups:
#!/usr/bin/env bash
BKP_ROOT="/backups"
check-backups-fs() {
bkp_root=$1
findmnt $bkp_root
}
[[ $(check-backups-fs $BKP_ROOT) ]] && echo "OK: $BKP_ROOT found." || { echo "ERROR: $BKP_ROOT not found. Exit."; exit 1; }
Проверяем:
[simterm]
$ ./home-backups.sh OK: /backups found.
[/simterm]
Меняем BKP_ROOT на несуществующую директорию, проверяем ещё раз:
[simterm]
$ ./home-backups.sh ERROR: /backups1 not found. Exit.
[/simterm]
notify()
Добавляем функцию для отправки уведомлений на экран, пока в минимальном варианте, потом можно будет добавить иконки в зависимости от статуса:
#!/usr/bin/env bash
BKP_ROOT="/backups"
HOME_DIR="/home/setevoy/"
check-backups-fs() {
bkp_root=$1
findmnt $bkp_root
}
notify () {
event=$1
home_dir=$2
at=$(date +"%d-%m-%Y %H:%M:%S")
echo $now
notify-send "Backup $event" "Backup $event for the $home_dir at $at" --icon=dialog-information
echo "Backup $event for the $home_dir at $at" | mail -v -s "Backup $event" setevoy
}
[[ $(check-backups-fs $BKP_ROOT) ]] && echo "OK: $BKP_ROOT found." || { echo "ERROR: $BKP_ROOT not found. Exit."; exit 1; }
notify "started" $HOME_DIR
В переменной HOME_DIR указание каталога заканчиваем слешом, см. тут>>>.
Через notify-send — создаём уведомление в трее, через mail — уведомление в почту.
Проверяем:
ОК, теперь можно начинать писать непосредственно функцию бекапирования.
mkbackup()
Создаём каталог для бекапов по крону:
[simterm]
$ sudo mkdir /backups/Backups/Cron
[/simterm]
Обновляем скрипт — добавляем перменную BKP_DIR, функцию выполнения rsync, пока в --dry-run и --verbose:
#!/usr/bin/env bash
BKP_ROOT="/backups"
BKP_DIR="$BKP_ROOT/Backups/Cron"
HOME_DIR="/home/setevoy/"
check-backups-fs() {
bkp_root=$1
findmnt $bkp_root
}
notify () {
event=$1
home_dir=$2
at=$(date +"%d-%m-%Y %H:%M:%S")
notify-send "Backup $event" "Backup $event for the $home_dir at $at" --icon=dialog-information
}
mkbackup() {
bkp_dir=$1
home_dir=$2
sudo rsync --dry-run --archive --verbose $home_dir $bkp_dir
}
[[ $(check-backups-fs $BKP_ROOT) ]] && echo "OK: $BKP_ROOT found." || { echo "ERROR: $BKP_ROOTH not found. Exit."; exit 1; }
notify "started" $HOME_DIR
if mkbackup $BKP_DIR $HOME_DIR; then
notify "finished" $HOME_DIR
else
notify "FAILED" $HOME_DIR
fi
Проверяем:
ОК, теперь укажем несуществую директорию, для проверки — rsync --dry-run --archive --verbose $home_dir/blabla:
Вроде всё работает — пробуем добавить задачу в cron.
Находим D-Bus сессию пользователя:
[simterm]
$ echo $DBUS_SESSION_BUS_ADDRESS unix:path=/run/user/1000/bus
[/simterm]
Добавляем задание переменной в скрипт:
#!/usr/bin/env bash
BKP_ROOT="/backups"
BKP_DIR="$BKP_ROOT/Backups/Cron"
HOME_DIR="/home/setevoy/"
check-backups-fs() {
...
Создаём файл лога:
[simterm]
$ sudo touch /var/log/home-backups.log $ sudo chown setevoy:setevoy /var/log/home-backups.log
[/simterm]
Создаём cron-задачу:
[email protected] DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/1000/bus" * * * * * /opt/home-backups-bash/home-backups.sh &> /var/log/home-backups.log
Сохраняем, проверяем уведомления в трее и почте:
Лог:
[simterm]
$ tail /var/log/home-backups.log -f Work/RTFM/Github/tests/.git/refs/heads/ Work/RTFM/Github/tests/.git/refs/heads/master Work/RTFM/Github/tests/.git/refs/remotes/ Work/RTFM/Github/tests/.git/refs/remotes/origin/ Work/RTFM/Github/tests/.git/refs/remotes/origin/HEAD Work/RTFM/Github/tests/.git/refs/remotes/origin/master Work/RTFM/Github/tests/.git/refs/tags/ sent 8,865,794 bytes received 710,489 bytes 2,736,080.86 bytes/sec total size is 260,297,908,025 speedup is 27,181.52 (DRY RUN)
[/simterm]
Уведомления:
UPD
Подумал, подумал, и решил, что нет — надо скрипт ещё обновить.
Во-первых — добавить getops(), и возможность вызова скрипта с разными опциями/параметрами, во-вторых — добавить исключения для rsync — зачем, например, бекапить /home/username/.cache?
Или можно сделать даже иначе — использовать не исключения, а наоборот — указывать только то, какие каталоги из /home/username добавлять в бекап.
Но, наверно, лучше задавать явный exclude — тогда, если в /home добавится новый каталог и его забудешь добавить в include — он не потеряется при бекапе.
Да и список исключений будет меньше, чем список каталогов, которые надо бекапить.
rsync() include и exclude
Сначала потестим.
Создаём каталог:
[simterm]
$ mkdir -p /tmp/include_this/dir1/dir2
[/simterm]
Добавляем файлы:
[simterm]
$ touch include_this/file && touch include_this/dir1/{.hidden,file1} && touch include_this/dir1/dir2/file2
[/simterm]
Выполняем rsync с опциями include && exclude:
[simterm]
$ rsync -r -v --include=include_this/*** --exclude=* /tmp/ /backups/test/ sending incremental file list include_this/ include_this/file include_this/dir1/ include_this/dir1/.hidden include_this/dir1/file1 include_this/dir1/dir2/ include_this/dir1/dir2/file2 sent 382 bytes received 104 bytes 972.00 bytes/sec total size is 0 speedup is 0.00
[/simterm]
Проверяем:
[simterm]
$ tree /backups/test/
/backups/test/
└── include_this
├── dir1
│ ├── dir2
│ │ └── file2
│ └── file1
└── file
3 directories, 3 files
[/simterm]
ОК, это работает.
Теперь проверим exclude-from — что бы все файлы-каталоги для исключения держать в одном txt-файле.
Добавляем каталоги и файлы:
[simterm]
$ mkdir -p exclude_this/dir1/dir2 $ touch exclude_this/file && touch exclude_this/dir1/file1 && touch exclude_this/dir1/dir2/file2
[/simterm]
Создаём файл exclude.txt, в котором задаём каталог exclude_this:
[simterm]
$ cat exclude.txt exclude_this/***
[/simterm]
Проверяем rsync:
[simterm]
$ rsync -r -v --exclude-from exclude.txt /tmp/ /backups/test/ sending incremental file list .X0-lock .org.chromium.Chromium.ZKtvRZ dropbox-antifreeze-AeMiDY dropbox-antifreeze-CMWTrD dropbox-antifreeze-bjUr5j dropbox-antifreeze-fcG7VA dropbox-antifreeze-iH8IiP exclude.txt keepassxc-setevoy.lock qtsingleapp-Viber-0-3e8-lockfile ...
[/simterm]
И каталог бекапа:
[simterm]
$ tree /backups/test/ | grep excl ├── exclude.txt
[/simterm]
ОК — каталога exclude_this нет, всё работает.
Возвращаемся к скрипту бекапа.
getopts()
Описание есть тут>>>.
Добавим две опции: -e, для указания exclude-файла, и -d — для запуска в --dry-run:
...
while getopts "e:d" opt; do
case $opt in
i) echo "Found Exclude option $opt"
echo "Found Exclude file for $opt - $OPTARG"
;;
d) echo "Found Dry-Run option $opt"
;;
*) echo "No reasonable options found!";;
esac
done
...
Проверяем:
[simterm]
$ ./home-backups.sh -e exclude.txt Found Exclude option e Found Exclude file for e - exclude.txt $ ./home-backups.sh -d Found Dry-Run option d
[/simterm]
Теперь добавим переменные, по которым будем определять параметры:
...
EXCLUDE_FILE="/opt/home-backups-bash/exclude.txt"
DRY_RUN=
while getopts ":e:d" opt; do
case $opt in
e) EXCLUDE_FILE=$OPTARG
;;
d) DRY_RUN="--dry-run"
;;
*) echo "No reasonable options found!";;
esac
done
...
EXCLUDE_FILE имеет дефолтное значение, которое можно переопределить с помощью -e.
DRY_RUN по умолчанию пустой, но если вызвать скрипт с -d — то сюда будет подставлено --dry-run.
И обновим функцию mkbackup():
...
mkbackup() {
bkp_dir=$1
home_dir=$2
exclude_file=$3
dry_run=$4
sudo rsync $dry_run --exclude-from $exclude_file --archive --verbose $home_dir $bkp_dir
}
...
И её вызов:
...
if mkbackup $BKP_DIR $HOME_DIR $EXCLUDE_FILE $DRY_RUN; then
notify "finished" $HOME_DIR
echo finished
else
echo failed
notify "FAILED" $HOME_DIR
fi
Создаём файл /opt/home-backups-bash/exclude.txt с исключениями:
[simterm]
$ cat /opt/home-backups-bash/exclude.txt .cache/ .directory .dropbox/*** .dropbox-dist/ .esd_auth .fehbg .gmrun_history .gnome2/ .gtkrc-2.0 .java/ .mono/ .mozilla/ .pki/ .PyCharmCE2018.2/ .rnd .temp/ .wget-hsts .Xauthority .xneur
[/simterm]
Запускаем, проверяем:
[simterm]
$ ./home-backups.sh -d
[/simterm]
.config в аутпуте нет, прочих каталогв и файлов из exclude.txt тоже — ОК.
Ещё можно добавить опцию -D — вызывать rsync с опцией --delete.
Повторяем всё тоже самое, как делали для --exclude-from и --dry-run:
...
EXCLUDE_FILE="/opt/home-backups-bash/exclude.txt"
DELETE=
DRY_RUN=
while getopts ":e:dD" opt; do
case $opt in
e) EXCLUDE_FILE=$OPTARG
;;
d) DRY_RUN="--dry-run"
;;
D) DELETE="--delete"
;;
*) echo "No reasonable options found!"
exit 1
;;
esac
done
...
mkbackup() {
bkp_dir=$1
home_dir=$2
exclude_file=$3
dry_run=$4
delete=$5
sudo rsync $dry_run $delete --exclude-from $exclude_file --archive --verbose $home_dir $bkp_dir
}
...
if mkbackup $BKP_DIR $HOME_DIR $EXCLUDE_FILE $DRY_RUN $DELETE; then
notify "finished" $HOME_DIR
echo finished
else
echo failed
notify "FAILED" $HOME_DIR
fi
Проверяем с -d и -D:
[simterm]
$ ./home-backups.sh -d -D
[/simterm]
--delete сама по себе достаточно опасная опция, так что сначала лучше несколько ра запустить с -d для --dry-run, и проанализировать что будет удаляться.
Ну и в кроне её использовать смысла не вижу, ибо бекап в том числе должен быть для того, что бы восстановить удалённые данные (корзина — корзиной, а в бекапе пусть валяется — места на внешнем диске пока много).
Аналогично можно добавить опции, что бы переопределять любые другие параметры для скрипта — хомик юзера, каталог, куда складывать бекапы и прочее.







