nixhacks.net/content/do_boring_backups.rst
2022-10-21 22:29:23 +03:00

247 lines
11 KiB
ReStructuredText
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

:title: Делаем скучные бэкапы
:date: 8 Oct 22
=====================
Делаем скучные бэкапы
=====================
Бэкапы, в самом деле, нескучные (:
Мотивацией для написания очередного велосипеда стало то, что даже для, казалось
бы, готовых инструментов приходится писать обвязки на шелле.
В какой-то момент мне надоело писать баш-портянки для бэкапа и я написал одну
большую, которая умеет сразу много всего. Знакомьтесь — **boring_backup**.
> **Пакеты для Debian/Ubuntu, Arch и сорцы**
`здесь <https://git.nxhs.cloud/ge/boring_backup/releases>`_.
Сначала о том, чем он *НЕ* является:
* Тупым линейным скриптом с вызовом tar и mysqldump.
* Полным аналогом `restic <https://restic.net/>`_ и похожих утилит.
* POSIX-совместимым скриптом. Хотелось бы, конечно, но это достаточно больно.
Может быть когда-нибудь я перепишу код и оно заработает на dash/ash и прочих
шеллах, но это будет уже другой скрипт.
**boring_backup** — это библиотека функций Bash и интерфейс командной строки
для скриптов резервного копирования.
Вот что в ней есть:
* Обработка ошибок (wow!)
* Логирование в файл или syslog (WOW!)
* Парсер URI практически целиком совместимый с RFC 3986!
* Готовые функции для запаковки файлов (tar) и снятия SQL-дампов (MySQL/MariaDB
и PostgreSQL)
Логика работы утилиты крайне проста. Есть два массива данных — список URI
исходных ресурсов, которые надо бэкапить и список URI хранилищ, куда предстоит
бэкапиться.
Поддерживается определённый набор схем URI, каждую схему обрабатывает свой
обработчик.
Cамый простой скрипт резервного копирования, который можно написать с
помощью **boring_backup** выглядит так:
.. code-block:: shell
sources=(/home/john)
targets=(/var/backups)
Сохраним это в файл под именем `test`.
Здесь очевидно дира /home/john будет скопирована в /var/backups, однако cперва
она будет запакована в архив tar без сжатия. Запускаем бэкап:
.. code-block:: shell
boring_backup test
Итого получим файл /var/backups/test_john_2022.10.08-1230.tar. Если мы хотим
сжать архив, то добавим компрессию:
.. code-block:: shell
sources=(/home/john)
targets=(/var/backups)
compression=xz
Здесь значение переменной ``compression`` будет интерпретироваться так же как
tar интерпретирует расширение архива с опцией ``--auto-compress``. То есть
поддерживаются все утилиты, которыми tar умеет сжимать. Если переменная пуста
или содержит что-то не то, то сжатие будет отключено.
Надо исключить лишние файлы из архива? Легко!
.. code-block:: shell
sources=(/home/john)
targets=(/var/backups)
compression=xz
tar_exclude=(.cache foo bar)
Для передачи опций для tar есть переменная ``tar_options`` в которой можно
переопределить умолчание — ``-acf``.
Теперь добавим базу данных MySQL в бэкап. Записывается URI в формате, который
ещё называют DSN (data source name):
.. code-block:: shell
sources=(
/home/john
mysql://test_usr:%2C%23s%7BRGqH@localhost/test_db
)
targets=(/var/backups)
compression=xz
tar_exclude=(.cache foo bar)
Обратите внимание, что пароль пользователя закодирован, чтобы не возникло
коллизий из-за наличия в нём спецсимволов. Закодировать пароль крайне просто,
вот пример на Perl, который можно выполнить прямо в командной строке:
.. code-block:: perl
echo ',#s{RGqH' | perl -MURI::Escape -wlne 'print uri_escape $_'
Теперь в /var/backups окажется два файла — test_john_2022.10.08-1230.tar.xz
и test_test_db_2022.10.08-1230.sql.xz. Сваливать все файлы в одну директорию
может быть неудобно, поэтому давайте сохранять файлы в папки по датам.
.. code-block:: shell
sources=(
/home/john
mysql://test_usr:%2C%23s%7BRGqH@localhost/test_db
)
today=$(date +%Y-%m-%d) # в результате даст дату yyyy-mm-dd
make_target_dir=yes
targets=(/var/backups/$today)
compression=xz
tar_exclude=(.cache foo bar)
Теперь пути до файлов будут выглядеть так:
/var/backups/2022-10-08/test_john_2022.10.08-1230.tar.xz.
Теперь поробуем передать эти файлы ещё и на удалённое хранилище. Допустим на
S3-совместимое объектное хранилище. Сперва придётся установить и настроить
утилиту s3cmd, затем скрипт примет вид:
.. code-block:: shell
sources=(
/home/john
mysql://test_usr:%2C%23s%7BRGqH@localhost/test_db
)
today=$(date +%Y-%m-%d)
make_target_dir=yes
targets=(
/var/backups/$today
s3://my_bucket/$today
)
compression=xz
tar_exclude=(.cache foo bar)
**boring_backup** сначала сделает бэкап и сохранит его в /var/backups и только
затем передаст файлы в S3-хранилище (или любое другое). Можно указывать любое
количество URI в targets и также в sources. Особенностью targets является то,
что требуется хотя бы один URI со схемой `file`, куда будут сохраняться
локальные бэкапы. Просто путь без указания схемы тоже считается за file.
Следующие три записи полностью эквивалентны:
* /var/backups
* file:/var/backups
* file:///var/backups
Пойдём дальше.
Мы можем обрабатывать ошибки. Допишем в скрипт функцию `on_error`, которая
будет нам отправлять письмо с фрагментом лога и текстом ошибки.
.. code-block:: shell
sources=(
/home/john
mysql://test_usr:%2C%23s%7BRGqH@localhost/test_db
)
today=$(date +%Y-%m-%d)
make_target_dir=yes
targets=(
/var/backups/$today
s3://my_bucket/$today
)
compression=xz
tar_exclude=(.cache foo bar)
email=me@example.com
on_error() {
local log_fragment err_message
err_message="$*"
log_fragment="$(grep -n 'Backup started' "$log_file" | tail -1 |
cut -d ':' -f 1 | xargs -I {} tail -n +{} "$log_file")"
printf \
'Текст ошибки:\n%s\n\nЛог последнего бэкапа:\n%s' \
"$err_message" "$log_fragment" |
mail -s "$HOSTNAME: Ошибка при выполнении бэкапа" "$email"
}
Также мы можем после копирование в удалённое хранилище удалять локальный
бэкап. Список всех созданных за текущее выполнение скрипта бэкапов хранится
в массиве ``backups``.
.. code-block:: shell
finalise() {
log -p "\tClean up local backups"
log -p "\tFiles to delete: ${backups[@]}"
rm -rv -- "${backups[@]}"
log -p "\tLocal backups deleted"
}
Это лишь небольшая часть того, что можно реализовать. Полная документация
есть `в виде мануала </cgi-bin/man.cgi?q=boring_backup&l=ru>`_.
Отмечу ещё несколько важных фич:
* Стандартную логику можно полностью переопределить, в том числе выкинуть
необходимость сохранять локальный бэкап. Достаточно описать функцию с
именем `backup`.
* Можно выполнять действия перед бэкапом в функции `prepare` и после бэкапа
`finalise`.
Есть и вещи, которые вам не понравятся, но решение которых вероятно появится
в слудующих версиях **boring_backup**:
* Концепция репозитория бэкапов не поддерживается. Файлы просто загружается
туда куда вы укажете.
* Из коробки можно делать только полные бэкапы.
* Нет ротации бэкапов. Её нужно реализовывать самостоятельно.
* Из коробки нет шифрования. GPG и кастомная функция `backup` вам в помощь.
В заключение дам ещё один пример скрипта для бэкапа приложения Gitea.
Попробуйте сами разобраться что тут происходит.
.. code-block:: shell
sources=(.) # just pass validation
targets=(.)
today="$(date +%d_%b_%Y)"
s3cmd_config=~/.s3cfg
prepare() {
systemctl stop gitea.service
sleep 5
}
backup() {
log -p "Dumping Gitea"
su -c "/usr/local/bin/gitea dump -c /etc/gitea/app.ini \
-f /home/git/.cache/gitea_dump.zip" - git 2>> "$log_file"
backups+=(/home/git/.cache/gitea_dump.zip)
tgt_s3cmd s3://mybucket/backups/gitea-$today
}
finalise() {
systemctl start gitea.service
rm -rv -- "${backups[@]}"
}