Задача – написать скрипт для домашней машинки, что бы создавать бекапы по расписанию используя 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
, и проанализировать что будет удаляться.
Ну и в кроне её использовать смысла не вижу, ибо бекап в том числе должен быть для того, что бы восстановить удалённые данные (корзина – корзиной, а в бекапе пусть валяется – места на внешнем диске пока много).
Аналогично можно добавить опции, что бы переопределять любые другие параметры для скрипта – хомик юзера, каталог, куда складывать бекапы и прочее.