В рамках поддержки этого и параллельного форумов возникла задача резервного копирования баз дынных, вложений и системных конфигураций. На данный момент общий рабочий объём БД двух форумов примерно 160 гигов и примерно 30 гигов вложений.
Так как используется сервис Я.360 для нормальной работы форумной электронной почты, то логично воспользоваться доступным пространством Яндекс Диска 1 ТБ на пользователя для осуществления задачи резервного копирования данных.
Я искал способы, как туда можно копировать архивы, пробовал официальную программу, доступную в консольном варианте, однако функционирование программы не понравилось - программа очевидно предназначена для пользователя и сделана явным образом максимально неудобно для автоматизациия какой-либо инфраструктурной задачи, оно и понятно, тут претензий нет. Извернуться можно, но неудобно.
Случайным образом было найдено решение здесь
https://neblog.info/skript-bekapa-na-yandeks-disk?ysclid=lqaufsd6g9584100748 - простой скрипт бэкапа штатными средствами ОС и публикация бэкапов на Яндекс.Диск через API. То, что надо! Я и не знал до этого, что Яндекс свободно доступ к API диска предоставляет.
Скрипт данного автора меня тоже не совсем устроил, прежде всего потому, что он там всё в один фалй архивирует как проект, а я этого в принципе сделать не могу из-за объема. Но это мелочи, главное, что там приводятся готовые методы работы с Api и не надо писать самому, подправить то легче.
Соответственно, приведу примеры используемых скриптов для резервного копирования БД и файлов. Скрипт максимально универсален, подправить для себя не составит сложностей.
Что может скрипт:
- делает бекапы штатными системными средствами
- архивирует, выбор сжатия gzip и xz
- выкладывает архивы на Яндекс.Диск через API
- пишет лог и отправляет уведомления на почту
- удаляет локальные архивные файлы бэкапов после выгрузки на Яндекс.Диск
- может удалять файлы в Яндекс.Диск старше чем за указанный период
Нюанс: Яндекс ограничивает скорость загрузки для некоторых MIME
https://yadisk.readthedocs.io/ru/dev/known_issues.html. Чтобы не ограничивал - достаточно добавить кастомное расширение файла. При необходимости восстановления расширение достаточно удалить и распаковать как обычный архив.
Немного замечаний по использованию.
Прежде всего нужно создать и зарегистрировать собственное приложение в Яндекс и получить токен. Зарегистрировать приложение необходимо именно по этой ссылке
https://oauth.yandex.ru/client/new . Если регистрировать непосредственно из интерфейса Яндекс, то там нет возможности задать приложению права на использование REST API (зачем так сделали - не ясно). После регистрации приложения в Яндекс.Диск в корне появится директория Applications c вашим приложением. Туда и будут файлы загружаться при использовании API.
Примеры двух скриптов. Для БД и архивирование файлов.
Алгоритм работы:
- подготовка архивов - отработка по циклу for... В случае БД просто перечисляю две БД подряд, в случае архивирования директорий - задаю их как переменные, при этом использую разделитель : для именований и далее как с БД, переменные в цикл for...
- выгрузка архивов
- удаление локальных временных архивов и устаревших на Я.Диск
Я полностью сохранил оригинальные комментарии автора в скрипте, ссылку на оригинальный источник + мои исправления и дополнения
Бэкап БД1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
#!/bin/bash
#
# Yandex.Disk backup script v1.0 by Sergey Lukonin (neblog.info) // modified by Anton Koudinov (itwrks.org)
#
#
# # # # # # # # # # НАСТРОЙКИ БЭКАПА MYSQL # # # # # # # # # #
#
# В домашней директории пользователя, из под которого будет запускаться программа mysqldump, создайте конфигурационный файл ~/.my.cnf с параметрами доступа к серверу MYSQL следующего содержания:
#
## [mysqldump]
## host = 127.0.0.1
## user = backup
## password= "backup123"
#
# Параметры mysqldump
MYSQLDUMP_PARAMS='--routines --triggers --single-transaction --quick --max_allowed_packet=64M --lock-tables=false --complete-insert'
# # # # # # # # # # ОБЩИЕ НАСТРОЙКИ # # # # # # # # # #
# Директория для временного хранения бэкапов, которые удаляются после отправки на Яндекс.Диск
BACKUP_DIR='/mnt/localfs/nfs/share/_backup/upload'
# Название проекта, используется в логах и именах архивов
PROJECT='itwrks.org'
# Сколько дней хранятся бэкапы на Яндекс.Диске (0 - хранить все бэкапы). Учитывайте расписание резервного копирования, так как дата для автоматического удаления рассчитывается от текущей минус количество дней, указанных в параметре.
# Например, делаете архивы каждый день и хотите хранить за 14 дней - задаёте 14. Делаете еженедельно и хотите хранить 4 архива - задаёте 30
MAX_BACKUPS='14'
# Базы данных для архивации (указываются через пробел), которые будут отправлены на Яндекс.Диск
DBS='NOSQLRU RESQLRU'
# Yandex.Disk токен (как получить - см. на neblog.info)
TOKEN='123qweasdzxc '
# Директория приложения на Яндекс.Диске, куда будут отправлены архивы. Необходимо создать предварительно
APP_DIR='DB'
# Произвольное имя расширения (с точкой!) для архива (Яндекс ограничивает скорость загрузки для некоторых MIME https://yadisk.readthedocs.io/ru/dev/known_issues.html. При необходимости восстановления расширение достаточно удалить и распаковать как обычный архив)
ext='.yarch'
# Имя лог-файла, хранится в директории, указанной в $BACKUP_DIR
LOGFILE='backup.log'
# E-mail для отправки результата выполнения скрипта. Оставьте пустым, если отправлять результаты не требуется
sendLog='root@itwrks.org'
# Отправлять только ошибки (true). Укажите false, если нужно отправлять логи при любом результате выполнения скрипта
sendLogErrorsOnly='false'
# Выбор сжатия. xz - сильнее сжатие, меленнее скорость. gzip - слабее сжатие, быстрее скорость. Оптимальный вариант - gzip
arch='gzip'
# # # # # # # # # # КОНЕЦ НАСТРОЕК # # # # # # # # # # # # #
# # # # # # # # ДАЛЬШЕ НИЧЕГО НЕ МЕНЯЕМ! # # # # # # # # # #
# Дата, используется в именах архивов для расчёта количества бэкапов для автоматического удаления
DATE="`date '+%Y%m%d'`.`date '+%H%M%S'`"
# # # # # # # # ДАЛЬШЕ НИЧЕГО НЕ МЕНЯЕМ! # # # # # # # # # #
cd $BACKUP_DIR || exit 88
function mailing()
{
if [ ! $sendLog = '' ];then
if [ "$sendLogErrorsOnly" == true ];
then
if echo "$1" | grep -q 'error'
then
echo "$2" | mail -s "$1" $sendLog > /dev/null
fi
else
echo "$2" | mail -s "$1" $sendLog > /dev/null
fi
fi
}
function logger()
{
echo "["`date "+%Y-%m-%d %H:%M:%S"`"] File $BACKUP_DIR: $1" >> $BACKUP_DIR/$LOGFILE
}
function parseJson()
{
local output
regex="(\"$1\":[\"]?)([^\",\}]+)([\"]?)"
[[ $2 =~ $regex ]] && output=${BASH_REMATCH[2]}
echo $output
}
function checkError()
{
echo $(parseJson 'error' "$1")
}
function getUploadUrl()
{
json_out=`curl -s -H "Authorization: OAuth $TOKEN" "https://cloud-api.yandex.net:443/v1/disk/resources/upload/?path=app:/$APP_DIR/$backupName&overwrite=true"`
json_error=$(checkError "$json_out")
if [[ $json_error != '' ]];
then
logger "$PROJECT - Yandex.Disk error: $json_error"
mailing "$PROJECT - Yandex.Disk backup error" "ERROR copy file $FILENAME. Yandex.Disk error: $json_error"
echo ''
else
output=$(parseJson 'href' $json_out)
echo $output
fi
}
function uploadFile
{
local json_out
local uploadUrl
local json_error
uploadUrl=$(getUploadUrl)
if [[ $uploadUrl != '' ]];
then
echo $UploadUrl
json_out=`curl -s -T $1 -H "Authorization: OAuth $TOKEN" $uploadUrl`
json_error=$(checkError "$json_out")
if [[ $json_error != '' ]];
then
logger "$PROJECT - Yandex.Disk error: $json_error"
mailing "$PROJECT - Yandex.Disk backup error" "ERROR copy file $FILENAME. Yandex.Disk error: $json_error"
else
logger "$PROJECT - Copying file to Yandex.Disk success"
mailing "$PROJECT - Yandex.Disk backup success" "SUCCESS copy file $FILENAME"
fi
else
echo 'Some errors occured. Check log file for detail'
fi
}
function backups_list() {
# Ищем в директории приложения все файлы бэкапов и выводим их названия:
curl -s -H "Authorization: OAuth $TOKEN" "https://cloud-api.yandex.net:443/v1/disk/resources?path=app:/$APP_DIR/&sort=created&limit=100" | tr "{},[]" "\n" | grep "name[[:graph:]]*.sql.*$ext" | cut -d: -f 2 | tr -d '"' | grep -v ^http
}
function backups_count() {
local bkps=$(backups_list | wc -l)
echo $bkps
}
function remove_old_backups() {
logger "Удаляем старые бэкапы с Яндекс.Диска"
# Цикл удаления старых бэкапов:
# - От текущей даты отсчитываем количество дней, указанных в параметре MAX_BACKUPS и определяем дату для удаления
# - Основываясь на имени файла, удаляем бэкапы до необходимой даты
bkps=$(backups_count)
if [ $bkps -gt 0 ]; then
old_bkps=$(date '+%Y%m%d' -d "-$MAX_BACKUPS day")
while read entry; do
old_entry=$(echo $entry | cut -f1 -d.)
if [ $old_entry -lt $old_bkps ]; then
logger "Удаляем $entry"
curl -X DELETE -s -H "Authorization: OAuth $TOKEN" "https://cloud-api.yandex.net:443/v1/disk/resources?path=app:/$APP_DIR/$(backups_list | awk '(NR == 1)')&permanently=true"
fi
done <<< `backups_list`
fi
}
logger "--- $PROJECT START BACKUP $DATE ---"
logger "Делаем дамп баз данных"
# Определяем расширение файла в зависомости от выбора архиватора
[ $arch = 'gzip' ] && ext_arch='gz'
[ $arch = 'xz' ] && ext_arch='xz'
for DB in $DBS
do
logger "Делаем дампы базы данных $DB"
mysqldump $MYSQLDUMP_PARAMS $DB | $arch -c > $BACKUP_DIR/$DATE.$DB.sql.$ext_arch$ext
done
for file in `ls $BACKUP_DIR --hide=$LOGFILE`
do
FILENAME=$file
logger "Выгружаем на Яндекс.Диск архив $file"
backupName=$file
uploadFile $file
done
logger "Удаляем архивы с диска"
find $BACKUP_DIR -maxdepth 1 -type f -name "*.sql.*$ext" -exec rm '{}' \;
# Удаляем старые бэкапы с Яндекс.Диска (если MAX_BACKUPS > 0)
if [ $MAX_BACKUPS -gt 0 ]; then remove_old_backups; fi
logger "Завершение скрипта бэкапа"
Бэкап директорий данных1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
#!/bin/bash
#
# Yandex.Disk backup script v1.0 by Sergey Lukonin (neblog.info) // modified by Anton Koudinov (itwrks.org)
#
#
# # # # # # # # # # НАСТРОЙКИ TAR # # # # # # # # # # #
# Параметры tar
TAR_PARAMS='-cvf'
# # # # # # # # # # ОБЩИЕ НАСТРОЙКИ # # # # # # # # # #
# Директория для временного хранения бэкапов, которые удаляются после отправки на Яндекс.Диск
BACKUP_DIR='/mnt/localfs/nfs/share/_backup/upload'
# Название проекта, используется в логах и именах архивов
PROJECT='itwrks.org'
# Сколько дней хранятся бэкапы на Яндекс.Диске (0 - хранить все бэкапы). Учитывайте расписание резервного копирования, так как дата для автоматического удаления рассчитывается от текущей минус количество дней, указанных в параметре.
# Например, делаете архивы каждый день и хотите хранить за 14 дней - задаёте 14. Делаете еженедельно и хотите хранить 4 архива - задаёте 30
MAX_BACKUPS='0'
# Директории для архивации (указываются через пробел), которые будут отправлены на Яндекс.Диск. Необходимо задать префикс для названия архива. Формат: <prefix:/path/to/dir>
DIR1='NOSQLRU:/mnt/localfs/nfs/share/_backup/WWW_DATA/nosql.ru/forum/user_data'
DIR2='RESQLRU:/mnt/localfs/nfs/share/_backup/WWW_DATA/resql.ru/forum/user_data'
DIRS="$DIR1 $DIR2"
# Yandex.Disk токен (как получить - см. на neblog.info)
TOKEN='123qweasdzxc'
# Директория приложения на Яндекс.Диске, куда будут отправлены архивы. Необходимо создать предварительно
APP_DIR='WWW_DATA'
# Произвольное имя расширения (с точкой!) для архива (Яндекс ограничивает скорость загрузки для некоторых MIME https://yadisk.readthedocs.io/ru/dev/known_issues.html. При необходимости восстановления расширение достаточно удалить и распаковать как обычный архив)
ext='.yarch'
# Имя лог-файла, хранится в директории, указанной в $BACKUP_DIR
LOGFILE='backup.log'
# E-mail для отправки результата выполнения скрипта. Оставьте пустым, если отправлять результаты не требуется
sendLog='root@itwrks.org'
# Отправлять только ошибки (true). Укажите false, если нужно отправлять логи при любом результате выполнения скрипта
sendLogErrorsOnly='false'
# Выбор сжатия. xz - сильнее сжатие, меленнее скорость. gzip - слабее сжатие, быстрее скорость. Оптимальный вариант - gzip
arch='gzip'
# # # # # # # # # # КОНЕЦ НАСТРОЕК # # # # # # # # # # # # #
# # # # # # # # ДАЛЬШЕ НИЧЕГО НЕ МЕНЯЕМ! # # # # # # # # # #
# Дата, используется в именах архивов для расчёта количества бэкапов для автоматического удаления
DATE="`date '+%Y%m%d'`.`date '+%H%M%S'`"
# # # # # # # # ДАЛЬШЕ НИЧЕГО НЕ МЕНЯЕМ! # # # # # # # # # #
cd $BACKUP_DIR || exit 88
function mailing()
{
if [ ! $sendLog = '' ];then
if [ "$sendLogErrorsOnly" == true ];
then
if echo "$1" | grep -q 'error'
then
echo "$2" | mail -s "$1" $sendLog > /dev/null
fi
else
echo "$2" | mail -s "$1" $sendLog > /dev/null
fi
fi
}
function logger()
{
echo "["`date "+%Y-%m-%d %H:%M:%S"`"] File $BACKUP_DIR: $1" >> $BACKUP_DIR/$LOGFILE
}
function parseJson()
{
local output
regex="(\"$1\":[\"]?)([^\",\}]+)([\"]?)"
[[ $2 =~ $regex ]] && output=${BASH_REMATCH[2]}
echo $output
}
function checkError()
{
echo $(parseJson 'error' "$1")
}
function getUploadUrl()
{
json_out=`curl -s -H "Authorization: OAuth $TOKEN" "https://cloud-api.yandex.net:443/v1/disk/resources/upload/?path=app:/$APP_DIR/$backupName&overwrite=true"`
json_error=$(checkError "$json_out")
if [[ $json_error != '' ]];
then
logger "$PROJECT - Yandex.Disk error: $json_error"
mailing "$PROJECT - Yandex.Disk backup error" "ERROR copy file $FILENAME. Yandex.Disk error: $json_error"
echo ''
else
output=$(parseJson 'href' $json_out)
echo $output
fi
}
function uploadFile
{
local json_out
local uploadUrl
local json_error
uploadUrl=$(getUploadUrl)
if [[ $uploadUrl != '' ]];
then
echo $UploadUrl
json_out=`curl -s -T $1 -H "Authorization: OAuth $TOKEN" $uploadUrl`
json_error=$(checkError "$json_out")
if [[ $json_error != '' ]];
then
logger "$PROJECT - Yandex.Disk error: $json_error"
mailing "$PROJECT - Yandex.Disk backup error" "ERROR copy file $FILENAME. Yandex.Disk error: $json_error"
else
logger "$PROJECT - Copying file to Yandex.Disk success"
mailing "$PROJECT - Yandex.Disk backup success" "SUCCESS copy file $FILENAME"
fi
else
echo 'Some errors occured. Check log file for detail'
fi
}
function backups_list() {
# Ищем в директории приложения все файлы бэкапов и выводим их названия:
curl -s -H "Authorization: OAuth $TOKEN" "https://cloud-api.yandex.net:443/v1/disk/resources?path=app:/$APP_DIR/&sort=created&limit=100" | tr "{},[]" "\n" | grep "name[[:graph:]]*.tar.*$ext" | cut -d: -f 2 | tr -d '"' | grep -v ^http
}
function backups_count() {
local bkps=$(backups_list | wc -l)
echo $bkps
}
function remove_old_backups() {
logger "Удаляем старые бэкапы с Яндекс.Диска"
# Цикл удаления старых бэкапов:
# - От текущей даты отсчитываем количество дней, указанных в параметре MAX_BACKUPS и определяем дату для удаления
# - Основываясь на имени файла, удаляем бэкапы до необходимой даты
bkps=$(backups_count)
if [ $bkps -gt 0 ]; then
old_bkps=$(date '+%Y%m%d' -d "-$MAX_BACKUPS day")
while read entry; do
old_entry=$(echo $entry | cut -f1 -d.)
if [ $old_entry -lt $old_bkps ]; then
logger "Удаляем $entry"
curl -X DELETE -s -H "Authorization: OAuth $TOKEN" "https://cloud-api.yandex.net:443/v1/disk/resources?path=app:/$APP_DIR/$(backups_list | awk '(NR == 1)')&permanently=true"
fi
done <<< `backups_list`
fi
}
logger "--- $PROJECT START BACKUP $DATE ---"
logger "Делаем архивы директорий"
# Определяем расширение файла в зависомости от выбора архиватора
[ $arch = 'gzip' ] && ext_arch='gz'
[ $arch = 'xz' ] && ext_arch='xz'
for DIR in $DIRS
do
tar_name=$(echo $DIR | cut -f1 -d:)
DIR=$(echo $DIR | cut -f2 -d:)
logger "Делаем архив директории $DIR"
tar $TAR_PARAMS - $DIR | $arch -c > $BACKUP_DIR/$DATE.$tar_name.tar.$ext_arch$ext
done
for file in `ls $BACKUP_DIR --hide=$LOGFILE`
do
FILENAME=$file
logger "Выгружаем на Яндекс.Диск архив $file"
backupName=$file
uploadFile $file
done
logger "Удаляем архивы с диска"
find $BACKUP_DIR -maxdepth 1 -type f -name "*.tar.*$ext" -exec rm '{}' \;
# Удаляем старые бэкапы с Яндекс.Диска (если MAX_BACKUPS > 0)
if [ $MAX_BACKUPS -gt 0 ]; then remove_old_backups; fi
logger "Завершение скрипта бэкапа"
Точка вызова для cron1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
#!/bin/bash
#
sname="srv-adm.srv.itwrks"
scdir="/srv/scripts/backup"
sc_backup_sql="$scdir/backup_sql.sh"
#sc_backup_sys="$scdir/backup_sys.sh"
sc_backup_www_app="$scdir/backup_www_app.sh"
sc_backup_www_data="$scdir/backup_www_data.sh"
email=root
RETVAL=0
do_backup_sql() {
$sc_backup_sql
[ $? == 0 ] || exit 88
}
do_backup_sys() {
echo "sys"
#$sc_backup_sys
[ $? == 0 ] || exit 88
}
do_backup_www_app() {
$sc_backup_www_app
[ $? == 0 ] || exit 88
}
do_backup_www_data() {
$sc_backup_www_data
[ $? == 0 ] || exit 88
}
do_backup_all() {
do_backup_sql
sleep 5
do_backup_www_data
sleep 5
do_backup_www_app
sleep 5
do_backup_sys
}
#####
case "$1" in
--backup-all)
do_backup_all
;;
--backup-sql)
do_backup_sql
;;
--backup-sys)
do_backup_sys
;;
--backup-www-app)
do_backup_www_app
;;
--backup-www-data)
do_backup_www_data
;;
*)
echo "Usage: $0 --backup-all | --backup-sql | --backup-sys | --backup-www-app | --backup-www-data"
esac