Skip to main content

Скрипт бекапа на Яндекс.Диск

Выкладывать бекапы проектов (сайтов) на Яндекс.Диск может понадобиться по нескольким причинам, например, из-за нехватки места на сервере (хостинге, VDS, VPS) или для повышения безопасности хранения бекапов (на случай, если сервер без рейда и он выйдет из строя).

В связи с этим я написал для себя и решил выложить для других небольшой bash-скрипт для бекапа на Яндекс.Диск. Функции скрипта:
— Создание на сервере бекапа проектов (файлов + баз данных MySQL);
— Авторизация на Яндекс.Диске в качестве приложения (по токену, более безопасный способ, чем использование логина и пароля);
— Отправка бекапов с сервера на Яндекс.Диск;
— Удаление старых бекапов с Яндекс.Диска для экономии места (настраивается максимальное количество хранимых бекапов);
— Запись и отправка лога на e-mail (настраивается).

Для того, чтобы воспользоваться скриптом, необходимо сначала получить токен от Яндекс.Диска. Приступим.

1. Логинимся на Яндексе под аккаунтом, на диск которого будем делать бекап, заходим на oauth.yandex.ru и нажимаем «Зарегистрировать новое приложение».

Яндекс.Диск - регистрация нового приложения

2. Заполняем название приложения (например, «backup») и выдаём нужные права в разделе «Яндекс.Диск REST API», а именно: «Доступ к информации о Диске» и «Доступ к папке приложения на Диске».

Яндекс.Диск - предоставление прав приложению

Ниже на той же странице под полем «Callback URL» нажимаем «подставить URL для разработки» и нажимаем «Сохранить»:

Яндекс.Диск - Callback URL

3. После сохранения параметров приложения нас перенаправят на страницу с данными о приложении:

Яндекс.Диск - данные приложения

4. Теперь получим сам токен (если хотите, можете почитать подробнее об этом в мануале Яндекса), для этого копируем ID, подставляем в конец URL https://oauth.yandex.ru/authorize?response_type=token&client_id=, переходим по получившемуся адресу и подтверждаем выдачу разрешений приложению:

Яндекс.Диск - разрешения приложения

В итоге на странице будет отображён токен, который выдается не менее, чем на 1 год, поэтому если скрипт бекапа вдруг перестанет работать, мы сможем получить новый токет и подставить его в скрипт. Протестировать возможности работы с Яндекс.Диском, используя полученный токен, можно на специальном полигоне.

А теперь сам bash-скрипт для бекапа на Яндекс.Диск:

#!/bin/bash
#
# Yandex.Disk backup script v1.0 by Sergey Lukonin (neblog.info)
#
# # # # # # # # # # НАСТРОЙКИ БЕКАПА MYSQL # # # # # # # # # #

# Сервер БД
MYSQL_SERVER=mysql.some-server.ru

# Юзер, под которым будем делать бекап доступных баз, руту mysql обычно доступны все БД, отдельному пользователю обычно доступна БД конкретного проекта
MYSQL_USER=some-user

# Пароль пользователя базы данных (Пароль от рута сервера и от рута mysql разные не путайте)
MYSQL_PASSWORD=some-password

# # # # # # # # # # ОБЩИЕ НАСТРОЙКИ # # # # # # # # # #

# Директория для временного хранения бекапов, которые удаляются после отправки на Яндекс.Диск
BACKUP_DIR='/home/www/backup'

# Название проекта, используется в логах и именах архивов
PROJECT='neblog.info'

# Максимальное количество хранимых на Яндекс.Диске бекапов (0 - хранить все бекапы):
MAX_BACKUPS='14'

# Дата, используется в именах архивов
DATE=`date '+%Y-%m-%d'`

# Директории для архивации (указываются через пробел), которые будут помещены в единый архив и отправлены на Яндекс.Диск
DIRS='/home/www/projects/neblog'

# Yandex.Disk токен (как получить - см. на neblog.info)
TOKEN=''

# Имя лог-файла, хранится в директории, указанной в $BACKUP_DIR
LOGFILE='backup.log'

# E-mail для отправки результата выполнения скрипта. Оставьте пустым, если отправлять результаты не требуется.
sendLog='your-mail@yandex.ru'

# Отправлять только ошибки (true). Укажите false, если нужно отправлять логи при любом результате выполнения скрипта.
sendLogErrorsOnly='false'

# # # # # # # # # # КОНЕЦ НАСТРОЕК # # # # # # # # # # # # # 
# # # # # # # # ДАЛЬШЕ НИЧЕГО НЕ МЕНЯЕМ! # # # # # # # # # #

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:/$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:/&sort=created&limit=100" | tr "{},[]" "\n" | grep "name[[:graph:]]*.tar.gz" | cut -d: -f 2 | tr -d '"'
}

function backups_count() {
    local bkps=$(backups_list | wc -l)
    # Если мы бекапим и файлы, и БД, то на 1 бекап у нас приходится 2 файла. Поэтому количество бекапов = количество файлов / 2:
    expr $bkps / 2
}

function remove_old_backups() {
    bkps=$(backups_count)
    old_bkps=$((bkps - MAX_BACKUPS))
    if [ "$old_bkps" -gt "0" ];then
        logger "Удаляем старые бекапы с Яндекс.Диска"
        # Цикл удаления старых бекапов:
        # Выполняем удаление первого в списке файла 2*old_bkps раз
        for i in `eval echo {1..$((old_bkps * 2))}`; do
            curl -X DELETE -s -H "Authorization: OAuth $TOKEN" "https://cloud-api.yandex.net:443/v1/disk/resources?path=app:/$(backups_list | awk '(NR == 1)')&permanently=true"
        done
    fi
}

logger "--- $PROJECT START BACKUP $DATE ---"
logger "Выгружаем дампы баз"
mkdir $BACKUP_DIR/$DATE
for i in `mysql -h $MYSQL_SERVER -u $MYSQL_USER -p$MYSQL_PASSWORD -e'show databases;' | grep -v information_schema | grep -v Database`;
    do mysqldump -h $MYSQL_SERVER -u $MYSQL_USER -p$MYSQL_PASSWORD $i > $BACKUP_DIR/$DATE/$i.sql;
done

logger "Создаем архив mysql $BACKUP_DIR/$DATE-mysql-$PROJECT.tar.gz"
tar -czf $BACKUP_DIR/$DATE-mysql-$PROJECT.tar.gz $BACKUP_DIR/$DATE
rm -rf $BACKUP_DIR/$DATE

logger "Создаем архив каталогов $BACKUP_DIR/$DATE-files-$PROJECT.tar.gz"
tar -czf $BACKUP_DIR/$DATE-files-$PROJECT.tar.gz $DIRS

FILENAME=$DATE-mysql-$PROJECT.tar.gz
logger "Выгружаем на Яндекс.Диск архив mysql $BACKUP_DIR/$DATE-mysql-$PROJECT.tar.gz"
backupName=$DATE-mysql-$PROJECT.tar.gz
uploadFile $BACKUP_DIR/$DATE-mysql-$PROJECT.tar.gz

FILENAME=$DATE-files-$PROJECT.tar.gz
logger "Выгружаем на Яндекс.Диск архив с файлами $BACKUP_DIR/$DATE-files-$PROJECT.tar.gz"
backupName=$DATE-files-$PROJECT.tar.gz
uploadFile $BACKUP_DIR/$DATE-files-$PROJECT.tar.gz

logger "Удаляем архивы с диска"
find $BACKUP_DIR -type f -name "*.gz" -exec rm '{}' \;

# Удаляем старые бекапы с Яндекс.Диска (если MAX_BACKUPS > 0)
if [ $MAX_BACKUPS -gt 0 ];then remove_old_backups; fi

logger "Завершение скрипта бекапа"

Также вы можете скачать готовый файл скрипта. Скрипт следует расположить на сервере, заменить в нём параметры на свои, дать права на запуск (chmod +x) и поставить на ежедневное выполнение в cron. Если вы планируете выполнять несколько таких заданий, задайте время между их запуском (5-10 минут).

P.S. Архивы можно создавать с шифрованием, см. комментарий от Walkman.

114 комментариев к записи “Скрипт бекапа на Яндекс.Диск

  1. Подскажите, пожалуйста, а как модифицировать чтобы работало с cloud.mail.ru? Там всё-таки места 1 Тб

  2. Если я правильно понял, то 1 бекап = 1 архив? Может есть смысл прикрутить dupliсity? Сам тут задумался над бекапом на Яндекс.Диск, вот и думаю пока над скриптом (в Ваш пока не вникал ибо зашел по другим делам).

    1. 1 бекап = 2 архива (1 — данные, 2 — БД).
      Прикрутить можно, что угодно, хоть Duplicity, хоть Amanda. Здесь используются только стандартные утилиты и авторизация на Яндекс.Диске по токену.

  3. Спасибо, отличный скрипт. Но ситуация такая: корректно бэкапится только файловая система, а архив БД оказывается пустым. Точнее, не пустым, а матрёшкой папок: home/user_name/backups/2016-10-06

    Может быть есть какие-либо нюансы именно для бэкапа MySQL?

    1. БД на том же сервере или удаленном? Если удаленном, разрешен ли нелокальный вход? В логах есть какие-нибудь ошибки?

        1. А в логах скрипта есть что-нибудь?
          Вручную дамп создается?

          mysqldump -h localhost -u USERNAME -p PASSWORD DBNAME > ~/DBNAME.sql
          1. через mysqldump всё ok, лог скрипта:

            [2016-10-07 04:12:01] File /home/u/backups: Выгружаем дампы баз
            [2016-10-07 04:12:01] File /home/u/backups: Создаем архив mysql /home/u/backups/2016-10-07-mysql-site.ru.tar.gz
            [2016-10-07 04:12:01] File /home/u/backups: Создаем архив каталогов /home/u/backups/2016-10-07-files-site.ru.tar.gz
            [2016-10-07 04:12:01] File /home/u/backups: Выгружаем на Яндекс.Диск архив mysql /home/u/backups/2016-10-07-mysql-site.ru.tar.gz
            [2016-10-07 04:12:02] File /home/u/backups: site.ru — Copying file to Yandex.Disk success
            [2016-10-07 04:12:02] File /home/u/backups: Выгружаем на Яндекс.Диск архив с файлами /home/u/backups/2016-10-07-files-site.ru.tar.gz
            [2016-10-07 04:12:03] File /home/u/backups: site.ru — Copying file to Yandex.Disk success
            [2016-10-07 04:12:03] File /home/u/backups: Удаляем архивы с диска
            [2016-10-07 04:12:04] File /home/u/backups: Удаляем старые бекапы с Яндекс.Диска
            [2016-10-07 04:12:06] File /home/u/backups: Завершение скрипта бекапа

            В чём причина самостоятельно понять, увы, не удаётся:(

          2. Так вручную дамп создается? Я пример команды приводил в предыдущем своем комментарии.

  4. Спасибо за терпение:) На выходных разобрался, указал вместо айпи localhost — теперь всё отлично. Для автопродления летсэнкрипт скрипта случайно не писали?

    1. Так там всё просто, в кроне строчка:

      0 0 */85 * * /root/certbot/certbot-auto renew

      Период можно выставить и поменьше.

      Сам дистрибутив certbot на github.

      Подробнее можно почитать на хабре.

          1. Спасибо, разобрался. Не нашёл email, поэтому позволю себе ещё один вопрос оффтоп: у вас есть опыт работы с libapache2‐mpm‐itk? Где-то прочёл, что этот модуль решает проблемы с безопасностью (ну там, права 755 для директорий, в которые загружаются файлы через веб-интерфейс, права на запись в лог-файлы и т.п.), но реализация не ясна. P.s. перешёл с виртуального хостинга на Ubuntu около года назад, «не велите казнить» за обилие вопросов:)

  5. Приветствую!

    Подскажите в чем может быть проблема? Нужно, чтобы бекапилось 3 архива за сутки. Для этого изменил название сохраненных архивов, добавил к дате еще время. Иначе ошибка на дубли и повторный архив не заливается.
    # Дата, используется в именах архивов
    DATE=`date ‘+%Y-%m-%d_%H:%M’`
    на выходе
    2016-10-24_16:31-mysql-_vps.tar

    Но теперь перестали удаляться лишние архивы, стала появляться ошибка в консоли:
    {«message»:»Не удалось найти запрошенный ресурс.»,»description»:»Resource not found.»,»error»:»DiskNotFoundError»}{«message»:»Не удалось найти запрошенный ресурс.»,»description»:»Resource not found.»,»error»:»DiskNotFoundError»}[

    Возможно регулярку нужно поправить, помогите пожалуйста.

    1. Попробуйте выполнить запрос из консоли (через curl, например). Возможно, какие-то символы мешают выполнению запроса, например, двоеточие, т.к. в API Яндекса в URL тоже есть двоеточия и они могут восприниматься уже не как часть имени файла.

      1. Скорей всего двоеточие и была проблема, решения не нашел. Убрал «:» из названия архивов и все путем.

        1. Если это так критично, можете попробовать в urlencode передавать название файла для удаления. Тогда двоеточие будет заменено на %3A.

          1. Да в принципе уже нет времени разбираться, так как не прогер. Но за пол дня изучил не мало инфы.)))

            Кстати если кому нужно, для себя добавил функцию очистки корзины:

            function clear_trash() {
            curl -X DELETE -s -H «Authorization: OAuth $TOKEN» «https://cloud-api.yandex.net/v1/disk/trash/resources/?path=»
            }

            В конце добавляем:
            logger «Очищаем корзину от ненужных файлов»
            clear_trash

          2. В скрипте прописано удаление, минуя корзину (ключ permanently=true).

  6. Так же появляется предупреждение:

    — Warning: Skipping the data of table mysql.event. Specify the —events option explicitly.
    mysqldump: Got error: 1142: SELECT,LOCK TABL command denied to user ‘root’@’localhost’ for table ‘cond_instances’ when using LOCK TABLES

    1. Тут не отвечу, надо смотреть и разбираться. Но пишет, что нет прав на выполнение команды.

  7. Но тем не менее почему то файлы помещаются в корзину, а не без возвратно удаляются хотя ключ (permanently=true)

  8. Здравствуйте. Ваш скрипт замечательно работает, только вот ограничение на размер файла 10ГБ на Я.Диске. Как можно сделать, если размер бэкапа больше 10ГБ? Надо разбивать архив на части и заливать, как скрипт можно модифицировать, чтобы заливал все части архива? Алгоритм: надо tar разбивать на части и сохранять имена фалов в массив, потом циклом проходить по массиву и заливать эти файлы, также проверять кол-во бэкапов, чтобы потом их удалять с я.диска. Не силен в программировании, так бы сам сделал. У вас нет желания модифицировать ваш скрипт?

  9. ** ну и неплохо было бы в качестве фичи для неубиения сервера прикрутить проверку свободного места на самом сервере при создании бэкапа. Хостинги «нерезиновые» нынче ))

  10. Всем привет. Собственно огромнейшая просьба. Дома запущен сервер видео наблюдения(на Debian7+motion). Нужен скрипт который будет архивировать папки из одной постоянной директории на другой винт. Но фишка вся в том, что все папки создаются по дате то есть имеют вид /var/www/motion/in1/2017-05-15 и так каждые сутки новая папка по дате. Но ещё дело в том, что новая папка создаётся только после обнаружения движения детектором(а движение может быть и в 00:01 и в 05:30). В программировании я ноль. Если можете помогите прикрутить данное задание к этому скрипту.

    1. Из этого скрипта Вам только пара строк нужна. В чем загвоздка то у Вас, я так и не пойму? Папки уже создаются? Нужно просто настроить копирование и удаление старых? Или нужно копировать сразу же, как только создалась новая папка или файлы в ней?

      1. Я в прграммировании полный ноль. 1)Да папки создаются. 2)Нет сразу копировать не надо. 3)Да папки с их содержимым. 4)Можно с задержкой на сутки.

  11. Я чуть не правильно выразился. Нужно, что бы предыдущие папки загружались на яндекс диск и с сервера удалялись. Яндекс диск уже настроил и токен получил.

      1. Да. Но как прописать в скрипте, что бы он находил нужную папку (каждая папка в имени имеет год-месяц-число) и тд.

        1. Можете использовать find и ключи -ctime / -mtime или в маске для ключа -name указать содержание даты в имени. Ключ «-type d» для поиска только среди директорий.

          1. Если можешь приведи пожалуйста пример.

  12. Я так понял, что пока сам лично не напишешь скрипт то ни чего из того, что публикуют в интернете оно не работает. Обидно конечно.

  13. Скрипт не работает. На я.диск загрузки нет, хотя бекапы создаются и письма что бекап загружен приходят..автор скрипт рабочий?

  14. root# /usr/bin/backup-ya-disk.sh
    mysqldump: Got error: 1142: «SELECT,LOCK TABL command denied to user ‘root’@’localhost’ for table ‘cond_instances'» when using LOCK TABLES
    tar: Removing leading `/’ from member names
    tar: Removing leading `/’ from member names
    Some errors occured. Check log file for detail
    Some errors occured. Check log file for detail

    результат..+ файлов на я.диске нет

    1. Ну так у Вас ошибка блокировки БД, причем тут скрипт то? Смотрите логи, ищите ошибку в правах на mysql. Гугл в России тоже пока не заблокирован.

  15. Ну так у меня та же самая фишка. Базы сохраняет, архивирует а на Я.Д не отправляет. Я чуть раньше выкладывал исправленные ошибки которые присутствуют в вашем скрипте. Лог работы скрипта после исправления. Хотя сейчас моих постов нет. Скрипт запускаю от root. На Я.Д создается папка в логах пишет, что всё отправлено но на Я.Д ничего не появляется.
    Если кто то запустил скрипт в работу продублируйте пожалуйста рабочий скрипт мне на почту admin@viris42.zyns.com.
    Да ещё такой вопрос. У меня сервер на Debian 7. И если в скрипте указать такую строку > function mailing() то скрипт останавливается на это строке, помогает только удаление >functionfunction< и скрипт заработал. Но там есть ещё косяк нет скобок() в uploadFile. Так же в строке запроса на Я.Д прописан не нужный слэш между upload/?path .
    https://cloud-api.yandex.net:443/v1/disk/resources/upload/?path=app:/$backupName&overwrite=true

  16. Ответ от IT yadex.
    Yandex.Disk8 июн. в 20:38
    Здравствуйте!

    В указанном скрипте ошибка в 84 ой строке
    84 json_out=`curl -s -H «Authorization: OAuth $TOKEN» https://cloud-api.yandex.net:443/v1/disk/resources/upload?path=app:/backupName&overwrite=true`
    URL запроса необходимо оборачивать в двойные или одинарные ковычки. Иначе символ & интерпретируется как оператор языка shell и тем самым ломает строку запроса.

    1. Хм, у меня не ломает, проверил специально скрипт, которым бекапится блог, всё так же. У Вас в значении переменной $backupName пробелов часом нет?

      P.S. Оборачивайте код в тег pre, иначе парсер WP преобразует двойные кавычки в « и ».
      1. Нет пробелов нету. Скинь пожалуйста свой скрипт мне на admin@***.com только в архиве.

  17. в ЯД доках написано, что&overwrite=true должен разрешать перезапись файла.
    Этот параметр используется в приведённом скрипте, но при попытке загрузки файла с тем же именем в логи мне пишет:
    Yandex.Disk error: DiskResourceAlreadyExistsError

    Что не так?

  18. Отличное решение! Разобрался за 5 минут. Автор спасибо большое за скрипт!
    Хотелось бы чтобы бэкапы хранились не в папке Приложения.

    1. В статье представлен способ бекапа по токену, т.е. для Яндекса это «стороннее приложение». Чтобы бекапить в другие места диска, используйте другие способы бекапа, например, WebDAV.

  19. Не совсем программист под баш, но подпилил под свои нужды. Дело в том что базы некоторые у меня под 7GB если не архивировать *.sql, и место заканчивалось при создании нового архива. Придумал бекапить частями (и сразу же удалять файл бекапа mysql) для экономии. Вот такое вот решение:
    for file in $BACKUP_DIR/$DATE/*; do
    echo «Processing ${file}»
    gzip ${file}
    done

    tar -cf $BACKUP_DIR/$DATE-mysql-$PROJECT.tar.gz $BACKUP_DIR/$DATE
    rm -rf $BACKUP_DIR/$DATE:

  20. 2 часа воюю со скриптом. Сначала ругался на то, что употребляются ` — заменил на «. Теперь крон выдает такие ошибки http://i.imgur.com/5HlCEFu.png
    Возможно, у кого все хорошо с администрированием адаптирует скрипт под выгрузку на ftp? Потому что сами архивы хорошо создаются.
    В идеале было бы делать выгрузку на https://www.backblaze.com/b2/cloud-storage-pricing.html — у них есть и API

    1. 1 — предупреждение о том, что не надо логин и пароль в открытом виде передавать.
      2 — вообще не ошибка.
      3 — смотрите в логах, что за ошибки.

  21. Привет!

    Скрипт выдает ошибку:
    Line 132 remove_old_backups command not found и не удаляет лишние архивы с ЯД. Сможете вылечить?

    1. Не может выполниться функция, внимательно изучите код в месте определения function remove_old_backups().

      1. Реально ничего не менял, даже не могу понять, что там может не работать.

        function remove_old_backups() { bkps=$(backups_count) old_bkps=$((bkps — MAX_BACKUPS)) if [ «$old_bkps» -gt «0» ];then logger «Удаляем старые бекапы с Яндекс.Диска» # Цикл удаления старых бекапов: # Выполняем удаление первого в списке файла 2*old_bkps раз for i in `eval echo {1..$((old_bkps * 2))}`; do curl -X DELETE -s -H «Authorization: OAuth $TOKEN» «https://cloud-api.yandex.net:443/v1/disk/resources?path=app:/$(backups_list | awk ‘(NR == 1)’)&permanently=true» done fi }

        1. У меня в 132 строке закрытие функции backups_count(), поэтому нужно смотреть именно Ваш скрипт, что у Вас в этой строчке.

          1. Сорри не ту ошибку написал, запамятовал, с телефона писал….

            Ошибка была в переменной счетчика бекапа

            line 132: backups_count: command not found

  22. Вот мой код:
    function remove_old_backups() {
    bkps=$(backups_count)
    old_bkps=$((bkps — MAX_BACKUPS))
    if [ «$old_bkps» -gt «0» ];then
    logger «Удаляем старые бекапы с Яндекс.Диска»
    # Цикл удаления старых бекапов:
    # Выполняем удаление первого в списке файла 2*old_bkps раз
    for i in `eval echo {1..$((old_bkps * 2))}`; do
    curl -X DELETE -s -H «Authorization: OAuth $TOKEN» «https://cloud-api.yandex.net:443/v1/disk/resources?path=app:/$(backups_list | awk ‘(NR == 1)’)&permanently=true»
    done
    fi
    }

    132 строчка, тоже backups_count()

    1. Удаленно определять проблему по кусочкам кода я не буду. Можете добавить побольше логгеров и выяснять, в каком месте происходит затык. Но судя по ошибке, у Вас не определена функция backups_count().

        1. У Вас ошибка в ненахождении функции backups_count(), при чем тут URL, обозначенный в функции remove_old_backups()?

      1. По поводу скрипта, скрипт взят полностью с вашей темы. Прописаны только логи и пароль. Скрипт отрабатывает на ура, создает архивы, загружает в ЯД, очищает директорию временную, а вот чистить ЯД не хочет(((

  23. А как бы сделать чтобы он директории пихал в разные файлы и кидал их на яндекс.диск?А то запихивать все в ожин архив не очень удобно
    Например:
    /var/www/dome1.ru архив dome1.ru.gz
    /var/www/dome2.ru архив dome2.ru.gz

        1. Ну тогда замените все изменяющиеся для этих заданий переменные на параметры и вызывайте скрипт с параметрами.

  24. ./backup-ya.sh: line 84: curl: command not found
    Some errors occured. Check log file for detail
    ./backup-ya.sh: line 84: curl: command not found
    Some errors occured. Check log file for detail
    ./backup-ya.sh: line 125: curl: command not found

    ранее такого небыло, скрипт отрабатывал нормально

    1. Ну в ошибке ж всё указано, значит вы каким-то образом curl удалили. Установите заново и всё.

        1. Ну если скрипт не менялся, и в какой-то момент начались ошибки, указывающие на то, что команда curl неизвестна, то логично предположить, что проблема на сервере?
          И зачем вы показываете вывод curl для php, если скрипт на bash и использует системный curl?

  25. Может кто подскажет, как в конце вывести содержимое папки яндекс.диска в лог ?
    спасибо

  26. автор не указал
    1. что функция отправки на мыло с помощью bash работает не всех хостингах (на нормальных точно не работает)
    2. что backup.log как лог вообще бесполезен и не показывает ничего, кроме «выгружаем дампы + путь до временной папки», «Выгружаем на Яндекс.Диск архив mysql …», блаблабла и ни одной строчки в случае возникновения ошибок. проверено лично.
    3. что ошибка в строке function mailing () Syntax error: «(» unexpected возникает тогда, когда скрипт пытаешься выполнить так sh или так ./
    скрипт нужно выполнять с помощью cron
    4. что осутствуют скобки () в функции function uploadFile
    5. что прописан не нужный слеш между upload/?path . в https://cloud-api.yandex.net:443/v1/disk/resources/upload/?path=app:/$backupName&overwrite=true
    6. что в функции getUploadUrl url не обернут в двойные кавычки https://cloud-api.yandex.net:443/v1/disk/resources/upload?path=app:/$backupName&overwrite=true`
    7. что на шаред хостингах (пользователей которых большинство), повторюсь на нормальных хостингах, следящих за безопасностью нет доступа к mysql серверу, а только к базам данных и поэтому АДРЕС сервера в переменной указывать не нужно, также, не нужно указывать название базы, а нужно указать localhost

    при этом дефолтный скрипт, бекапит бд и сайт, удаляет бд и сайт, не отправляет на мыло ничего, пишет в лог блабла, не запускается из консоли обычными способами, не копирует на ядиск ничего, не удаляет на ядиске ничего

    если автор (если конечно он автор сего скрипта) действительно выложил скрипт для пользователей, то он забыл выложить самое ГЛАВНОЕ — инструкцию! Без которой приобретенный мобильник — это обычная поделка для аборигенов, не стоящая связки бананов (читай потраченное время на отладку скрипта и поиск ошибок) , потому как на хрен никому не нужен, а бананы можно съесть в голодный год.
    если автор преднамерено выкладывает полурабочий скрипт, дабы повысить свое ЧСВ, или потому, что «так все неврот е….ые прогеры делают», то повысит он его только в своих глазах, не заботясь при этом о своей аудитории и управлении репутацией своего сайта.

    спасибо пользователю Роман за некоторые найденные ошибки.
    автору спасибо за код, хоть и полурабочий

    после устранения ошибок скрипт заработал полностью, кроме мыла конечно.

    1. Вам дали удочку, а вы жалуетесь, что рыбу вам не выловили, не почистили, не пожарили. Такой себе подход.

  27. Привет! Несколько лет пользовался скриптом, и перестал работать, что может быть?
    [root@admin ~]# sh /usr/bin/backup_sh/backup_1.sh
    /usr/bin/backup_sh/backup_1t.sh: line 6: $’\r’: command not found
    /usr/bin/backup_sh/backup_1.sh: line 9: $’\r’: command not found
    /usr/bin/backup_sh/backup_1.sh: line 12: $’\r’: command not found
    /usr/bin/backup_sh/backup_1.sh: line 15: $’\r’: command not found
    /usr/bin/backup_sh/backup_1.sh: line 17: $’\r’: command not found
    /usr/bin/backup_sh/backup_1.sh: line 20: $’\r’: command not found
    /usr/bin/backup_sh/backup_1.sh: line 23: $’\r’: command not found
    /usr/bin/backup_sh/backup_1.sh: line 26: $’\r’: command not found
    /usr/bin/backup_sh/backup_1.sh: line 29: $’\r’: command not found
    /usr/bin/backup_sh/backup_1.sh: line 32: $’\r’: command not found
    /usr/bin/backup_sh/backup_1.sh: line 35: $’\r’: command not found
    /usr/bin/backup_sh/backup_1.sh: line 38: $’\r’: command not found
    /usr/bin/backup_sh/backup_1.sh: line 41: $’\r’: command not found
    /usr/bin/backup_sh/backup_1.sh: line 44: $’\r’: command not found
    /usr/bin/backup_sh/backup_1.sh: line 47: $’\r’: command not found
    /usr/bin/backup_sh/backup_1.sh: line 48: syntax error near unexpected token `$’\r»
    ‘usr/bin/backup_sh/backup_1.sh: line 48: `function mailing()
    [root@admin ~]#

    1. Проверьте кодировку файла. У вас там перевод каретки (символ ‘\r’) откуда-то взялся. Возможно, правили файл в винде.

      1. А какая должна быть кодировка? Да в винде в notepad++ редактировал, подскажите как лучше сделать, а так скрипт отличный, бэкапит года 4 уже….

  28. Да, действительно, кодировка windows, исправил на unix все заработало. Но одно но, как сделать, так, чтобы удалялись последние 2 архива (files и mysql)? В настройках MAX_BACKUPS=’4′. А то получается так, 2 бэкапа по 2 архива весят, но как только загружается еще 2, удаляются 4 архива, а не 2. И получается на выходе архивы только за 1 день, а должно по идее за 2 дня быть. И не могу запустить по крону, уже что только не пробовал

    1. Бекапы создаются архивами по 2 файла (файлы и база). Соответственно, если нужно хранить бекапы за последние 4 дня (при условии, что бекап производится 1 раз в сутки), то MAX_BACKUPS должен быть равен 4. На 5-й день у вас будет 10 файлов, из которых удалятся первые 2, останется 8.

  29. Служба поддержки сказала, если не работает скрипт по крону, виноват разработчик скрипта. Кто прав то, задолбали они меня?

    1. Если техподдержка хостинга Вам так ответила, то мой Вам совет — меняйте хостера.
      Для бекапа данного сайта используется задание cron:

      # Бекап neblog в 0:10 каждый день
      10 0 * * * /home/del/backup-ya-disk-neblog.sh
      1. Скрипт запустили и тут такой ответ поддержки, цитирую:

        В данном случае был виноват разработчик, т.к. не предвидел, что запускать скрипт нужно в папке пользователя, если у него нет sudo прав.

        1. А при чем тут разработчик? У вас на хостинге не будет sudo и доступ у вас есть только в свою домашнюю директорию, откуда еще можно скрипт в таком случае запускать? Либо хостер тупой, не знает про chroot и разрешил юзерам бегать по всему дереву файловой системы, поздравляю его.

          1. Хостер сказал, чтобы я вам передал, что у меня не хостинг, а VPS)))) А что можно на обычном хостинге этот скрипт установить? Я думал вы догадываетесь, что использую ВПС)))

          2. Я не телепат. И как-то привык, что если есть техподдержка, то это хостинг, а VDS/VPS управляешь сам и к техподдержке не обращаешься (по крайней мере, я ни разу не обращался). А скрипт можно запускать на обычном хостинге, в нем не используются никакие специальные утилиты, которые могли бы быть доступны только с правами sudo.

  30. Вот реально скажите, если бы вы знали,что ВПС, вы бы тоже ответили как постом выше?

    «Для бекапа данного сайта используется задание cron:

    # Бекап neblog в 0:10 каждый день
    10 0 * * * /home/del/backup-ya-disk-neblog.sh»

    Если бы так ответили, вопрос бы и не решился и никто бы и не знал, что нужно скрипт кидать в папку пользователя

    1. Господи, да запускайте вы скрипт хоть откуда, он будет работать без sudo, если у пользователя есть права на его запуск и создание файла лога по указанному в скрипте пути! Изучайте Linux!

  31. Супер! Спасибо за скрипт.
    Единственное выкладывать в облако свои резервные копии в открытом виде не очень хорошо.
    Я немного дописал скрипт, внесете изменения в свой?

    # # # # # # # # # # НАСТРОЙКИ ШИФРОВАНИЯ # # # # # # # # # #
    
    # Включение шифрования резервных копий. Укажите 'false' для отключения шифрования
    ENCRYPT='true'
    
    # Пароль для шифрования резервных копий
    KEY=aaaaaaa
    
    
    if [ -n "$KEY" ] && [ "$ENCRYPT" == true ]; then
        logger "Шифрование включено и ключ установлен. Данные будут зашифрованы"
        export KEY
        logger "Создаем архив mysql $BACKUP_DIR/$FILEDBNAME"
        tar -cz $BACKUP_DIR/$DATE | openssl enc -aes-256-cbc -pass env:KEY -e -out $BACKUP_DIR/$FILEDBNAME
        rm -rf $BACKUP_DIR/$DATE
        logger "Создаем архив каталогов $BACKUP_DIR/$FILENAME"
        tar -cz $DIRS | openssl enc -aes-256-cbc -pass env:KEY -e -out $BACKUP_DIR/$FILENAME
        export -n KEY
    else
        if [ ! -n "$KEY" ]; then
            logger "Ключ не установлен. ВНИМАНИЕ! Данные не будут зашифрованы!"
        fi
        if [ $ENCRYPT != "true" ]; then
            logger "Шифрование не включено. ВНИМАНИЕ! Данные не будут зашифрованы!"
        fi
        logger "Создаем архив mysql $BACKUP_DIR/$FILEDBNAME"
        tar -czf $BACKUP_DIR/$FILEDBNAME $BACKUP_DIR/$DATE
        rm -rf $BACKUP_DIR/$DATE
        logger "Создаем архив каталогов $BACKUP_DIR/$FILENAME"
        tar -czf $BACKUP_DIR/$FILENAME $DIRS
    fi
    1. У меня получился такой скрипт:

      #!/bin/bash
      #
      # Yandex.Disk backup script v1.0 by Sergey Lukonin (neblog.info)
      #
      # # # # # # # # # # НАСТРОЙКИ БЕКАПА MYSQL # # # # # # # # # #
      
      # Сервер БД
      MYSQL_SERVER=mysql.some-server.ru
      
      # Юзер, под которым будем делать бекап доступных баз, руту mysql обычно доступны все БД, отдельному пользователю обычно доступна БД конкретного проекта
      MYSQL_USER=some-user
      
      # Пароль пользователя базы данных (Пароль от рута сервера и от рута mysql разные не путайте)
      MYSQL_PASSWORD=some-password
      
      # # # # # # # # # # НАСТРОЙКИ ШИФРОВАНИЯ # # # # # # # # # #
      
      # Включение шифрования резервных копий. Укажите 'false' для отключения шифрования
      ENCRYPT='true'
      
      # Пароль для шифрования резервных копий
      KEY=some-encrypt-key
      
      # # # # # # # # # # ОБЩИЕ НАСТРОЙКИ # # # # # # # # # #
      
      # Директория для временного хранения бекапов, которые удаляются после отправки на Яндекс.Диск
      BACKUP_DIR='/home/www/backup'
      
      # Название проекта, используется в логах и именах архивов
      PROJECT='neblog.info'
      
      # Максимальное количество хранимых на Яндекс.Диске бекапов (0 - хранить все бекапы):
      MAX_BACKUPS='14'
      
      # Дата, используется в именах архивов
      DATE=`date '+%Y-%m-%d'`
      
      # Директории для архивации (указываются через пробел), которые будут помещены в единый архив и отправлены на Яндекс.Диск
      DIRS='/home/www/projects/neblog'
      
      # Yandex.Disk токен (как получить - см. на neblog.info)
      TOKEN=''
      
      # Имя лог-файла, хранится в директории, указанной в $BACKUP_DIR
      LOGFILE='backup.log'
      
      # E-mail для отправки результата выполнения скрипта. Оставьте пустым, если отправлять результаты не требуется.
      sendLog='your-mail@yandex.ru'
      
      # Отправлять только ошибки (true). Укажите false, если нужно отправлять логи при любом результате выполнения скрипта.
      sendLogErrorsOnly='false'
      
      # # # # # # # # # # КОНЕЦ НАСТРОЕК # # # # # # # # # # # # # 
      # # # # # # # # ДАЛЬШЕ НИЧЕГО НЕ МЕНЯЕМ! # # # # # # # # # #
      
      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:/$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:/&sort=created&limit=100" | tr "{},[]" "\n" | grep "name[[:graph:]]*.tar.gz" | cut -d: -f 2 | tr -d '"'
      }
      
      function backups_count() {
          local bkps=$(backups_list | wc -l)
          # Если мы бекапим и файлы, и БД, то на 1 бекап у нас приходится 2 файла. Поэтому количество бекапов = количество файлов / 2:
          expr $bkps / 2
      }
      
      function remove_old_backups() {
          bkps=$(backups_count)
          old_bkps=$((bkps - MAX_BACKUPS))
          if [ "$old_bkps" -gt "0" ];then
              logger "Удаляем старые бекапы с Яндекс.Диска"
              # Цикл удаления старых бекапов:
              # Выполняем удаление первого в списке файла 2*old_bkps раз
              for i in `eval echo {1..$((old_bkps * 2))}`; do
                  curl -X DELETE -s -H "Authorization: OAuth $TOKEN" "https://cloud-api.yandex.net:443/v1/disk/resources?path=app:/$(backups_list | awk '(NR == 1)')&permanently=true"
              done
          fi
      }
      
      logger "--- $PROJECT START BACKUP $DATE ---"
      logger "Выгружаем дампы баз"
      mkdir $BACKUP_DIR/$DATE
      for i in `mysql -h $MYSQL_SERVER -u $MYSQL_USER -p$MYSQL_PASSWORD -e'show databases;' | grep -v information_schema | grep -v Database`;
          do mysqldump -h $MYSQL_SERVER -u $MYSQL_USER -p$MYSQL_PASSWORD $i > $BACKUP_DIR/$DATE/$i.sql;
      done
      
      FILEDBNAME=$DATE-mysql-$PROJECT.tar.gz
      FILENAME=$DATE-files-$PROJECT.tar.gz
      
      if [ -n "$KEY" ] && [ "$ENCRYPT" == true ]; then
          logger "Шифрование включено и ключ установлен. Данные будут зашифрованы"
          export KEY
          logger "Создаем архив mysql $BACKUP_DIR/$FILEDBNAME"
          tar -cz $BACKUP_DIR/$DATE | openssl enc -aes-256-cbc -pass env:KEY -e -out $BACKUP_DIR/$FILEDBNAME
          rm -rf $BACKUP_DIR/$DATE
          logger "Создаем архив каталогов $BACKUP_DIR/$FILENAME"
          tar -cz $DIRS | openssl enc -aes-256-cbc -pass env:KEY -e -out $BACKUP_DIR/$FILENAME
          export -n KEY
      else
          if [ ! -n "$KEY" ]; then
              logger "Ключ не установлен. ВНИМАНИЕ! Данные не будут зашифрованы!"
          fi
          if [ $ENCRYPT != "true" ]; then
              logger "Шифрование не включено. ВНИМАНИЕ! Данные не будут зашифрованы!"
          fi
          logger "Создаем архив mysql $BACKUP_DIR/$FILEDBNAME"
          tar -czf $BACKUP_DIR/$FILEDBNAME $BACKUP_DIR/$DATE
          rm -rf $BACKUP_DIR/$DATE
          logger "Создаем архив каталогов $BACKUP_DIR/$FILENAME"
          tar -czf $BACKUP_DIR/$FILENAME $DIRS
      fi
      
      logger "Выгружаем на Яндекс.Диск архив mysql $BACKUP_DIR/$FILEDBNAME"
      backupName=$FILEDBNAME
      uploadFile $BACKUP_DIR/$FILEDBNAME
      
      logger "Выгружаем на Яндекс.Диск архив с файлами $BACKUP_DIR/$FILENAME"
      backupName=$FILENAME
      uploadFile $BACKUP_DIR/$FILENAME
      
      logger "Удаляем архивы с диска"
      find $BACKUP_DIR -type f -name "*.gz" -exec rm '{}' \;
      
      # Удаляем старые бекапы с Яндекс.Диска (если MAX_BACKUPS > 0)
      if [ $MAX_BACKUPS -gt 0 ];then remove_old_backups; fi
      
      logger "Завершение скрипта бекапа"
      1. Архивы, созданные с помощью вашего скрипта — не открываются никакими архиваторами и не распаковываются. Как делаете это вы?

          1. Уточню — архивы, которые зашифрованы скриптом пользователя Walkman методом «openssl enc -aes-256-cbc -pass env:KEY».
            Я пытался сделать архивам «tar -zxvf» или просто открыть в 7-Zip. Понятно, что нужно их сперва расшифровать… но как?

          2. Всё, разобрался, получилось расшифровать архив с помощью:
            openssl enc -d -aes-256-cbc -in .tar.gz.enc -out .ru.tar.gz
            А затем уже распаковывать стандартно:
            tar -zxvf .ru.tar.gz

            А можно ли как-то получать на сервер одной командой архив бэкапа с Яндекс.диска? Пока только попробовал перекачиванием на свой локальный комп и затем на сервер. А хотелось бы напрямую.

          3. Можно примонтировать ЯД, можно покопаться в API, думаю, там должна быть описана функция получения файла с ЯД.

  32. Что то я понять не могу, в каком месте в коде определяется переменная $backupName ????

  33. Сергей, здравствуйте. Спасибо большое за Ваше творение!!! Сильно не обижайтесь, но на его основе я сделал свой скрипт, немного его расширив 😉
    Возможно, кто-то скажет: «плагиат», но о Вас я там упомянул 😉
    https://github.com/FajeSu/site-backup-script

    Я не очень силен в bash. Если не сложно, посмотрите, пожалуйста, наверняка есть то, что можно оптимизировать.
    В первую очередь мне не нравится конструкция в строке 323 (https://github.com/FajeSu/site-backup-script/blob/master/backup_script.sh#L323). Но не могу придумать, как её переписать. Как-то оптимальнее можно получить только значения ключей name из строки, вида ‘{«_embedded»:{«items»:[{«name»:»20191112-000001″},{«name»:»20191108-000001″},{«name»:»20191106-000001″}]}}’ ?

    Второй вопрос. С середины октября резко снизилась скорость загрузки файлов на Яндекс.Диск. Причина пока не ясна. Вы не сталкивались с таким? Не знаете, можно ли как-то использовать curl, чтобы показывалась текущая моментальная скорость загрузки, а не средняя после завершения?

    1. Отвечу по порядку:
      1) Да ради бога, используйте скрипт как хотите, я выкладывал для себя и для других, копирайты мне не интересны.
      2) Из-за высокой нагрузки на работе я пока не готов ни скрипт смотреть, ни новые посты выкладывать 🙂
      3) Я как настроил и оттестировал, скорость больше не замерял.

      1. 1) Спасибо 🙂 Но информацию о Вас я оставлю.
        2) Это я так, на всякий случай спросил. Если будет возможность, гляньте 😉
        3) Можете посмотреть по своим логам, сколько времени проходит с начала загрузки бОльшего по объему архива до начала следующей операции? И сколько этот файл весит? У меня файлы 2 ГБ (2 млн. байт) стали загружаться по 4 часа, хотя раньше за 10-15 минут улетали. Причем такое одинаковое поведение на разных хостингах и для разных аккаунтов Яндекса. Такое впечатление, что Яндекс просто глобально как-то ограничил скорость для curl запросов…

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *