Бекап сайта и mysql на яндекс диск REST API + Bash
Резервная копия сайта нужна не тогда, когда всё работает, а в тот день, когда хостинг «упал», обновление плагина сломало шаблон или кто-то случайно удалил базу. Хранить копии на том же сервере бессмысленно: если пропадёт сервер, пропадут и они. Удобное и недорогое решение для небольшого проекта — отправлять архивы в облако. Разберём, как это сделать при помощи обычного bash-скрипта и REST API Яндекс Диска, без сторонних программ и клиентов.
Что понадобится
- сервер или хостинг с доступом по SSH (Linux) и планировщиком
cron; - установленные
curl,tar,gzipиmysqldump(клиент MySQL); - аккаунт на Яндексе с Яндекс Диском и свободным местом;
- OAuth-токен, которым скрипт будет подписывать запросы к API.
Получаем OAuth-токен
API Яндекс Диска работает с OAuth-авторизацией. Токен выдаётся приложению, которое нужно зарегистрировать на странице oauth.yandex.ru: указываем название, выбираем платформу «Веб-сервисы» и в списке доступов отмечаем права для Яндекс Диска (достаточно записи в отдельную папку приложения, но можно выдать и полный доступ). После создания приложения вы получите идентификатор (client_id). Откройте в браузере адрес авторизации приложения с этим идентификатором, подтвердите доступ, и сервис вернёт токен в адресной строке или на специальной странице.
Как устроена загрузка файла через REST API
Загрузка выполняется в два шага. Сначала мы просим API выдать адрес, на который можно отправить файл, затем отправляем сам файл методом PUT на этот адрес.
- Запрос адреса:
GET https://cloud-api.yandex.net/v1/disk/resources/uploadс параметромpath(путь на Диске, закодированный как часть URL) и заголовкомAuthorization: OAuth ВАШ_ТОКЕН. Параметрoverwrite=trueразрешает заменить файл с тем же именем. - В ответе приходит JSON с полями
href,methodиtemplated. Адрес вhrefдействует около 30 минут. - Отправка файла:
PUTна полученныйhref. Токен на этом шаге не нужен. Код ответа201означает, что файл загружен,202— что он принят и скоро появится на Диске.
Папку для копий создаёт запрос PUT https://cloud-api.yandex.net/v1/disk/resources?path=%2Fbackups. Если папка уже существует, API вернёт ошибку 409, и скрипту это не мешает. Удаление выполняется запросом DELETE на тот же адрес ресурса, а параметр permanently=true минует корзину.
Скрипт
Ниже рабочий вариант. Он собирает архив каталога сайта, делает дамп базы, загружает оба файла в папку backups на Диске и удаляет локальные копии. Старые копии на Диске удаляются по имени файла: хранятся копии за последние 14 дней.
#!/bin/bash
# /usr/local/bin/ya-backup.sh
TOKEN="ВАШ_OAUTH_ТОКЕН"
SITE_DIR="/var/www/site/public_html"
DB_NAME="wordpress"
TMP_DIR="/var/backups/site"
REMOTE_DIR="backups"
KEEP_DAYS=14
API="https://cloud-api.yandex.net/v1/disk/resources"
DATE=$(date +%F)
OLD=$(date -d "-${KEEP_DAYS} days" +%F)
mkdir -p "$TMP_DIR"
enc() { printf '%s' "$1" | sed 's#/#%2F#g'; }
upload() {
local file="$1" remote="$2" href code
href=$(curl -s -H "Authorization: OAuth $TOKEN" \
"$API/upload?path=$(enc "$remote")&overwrite=true" \
| sed -n 's/.*"href": *"\([^"]*\)".*/\1/p')
if [ -z "$href" ]; then
echo "$(date '+%F %T') нет адреса загрузки для $remote"
return 1
fi
code=$(curl -s -o /dev/null -w '%{http_code}' -T "$file" "$href")
echo "$(date '+%F %T') $remote -> HTTP $code"
}
remove() {
curl -s -o /dev/null -X DELETE -H "Authorization: OAuth $TOKEN" \
"$API?path=$(enc "$1")&permanently=true"
}
# папка на Диске (если уже есть, API вернёт 409)
curl -s -o /dev/null -X PUT -H "Authorization: OAuth $TOKEN" \
"$API?path=$(enc "$REMOTE_DIR")"
# архив файлов сайта
tar -czf "$TMP_DIR/files_$DATE.tar.gz" -C "$(dirname "$SITE_DIR")" "$(basename "$SITE_DIR")"
# дамп базы (логин и пароль берутся из ~/.my.cnf)
mysqldump --single-transaction "$DB_NAME" | gzip > "$TMP_DIR/db_$DATE.sql.gz"
upload "$TMP_DIR/files_$DATE.tar.gz" "$REMOTE_DIR/files_$DATE.tar.gz"
upload "$TMP_DIR/db_$DATE.sql.gz" "$REMOTE_DIR/db_$DATE.sql.gz"
# чистим локальные копии и старые файлы на Диске
rm -f "$TMP_DIR/files_$DATE.tar.gz" "$TMP_DIR/db_$DATE.sql.gz"
remove "$REMOTE_DIR/files_$OLD.tar.gz"
remove "$REMOTE_DIR/db_$OLD.sql.gz"
Пароль к базе
Не стоит передавать пароль в командной строке mysqldump: его можно увидеть в списке процессов. Надёжнее создать в домашнем каталоге пользователя, от имени которого запускается скрипт, файл ~/.my.cnf с правами 600:
[client]
user=backup
password=пароль_пользователя_backup
Пользователю достаточно прав на чтение нужной базы (SELECT, LOCK TABLES, SHOW VIEW, TRIGGER). Создайте для резервного копирования отдельную учётную запись, а не используйте root.
Запуск по расписанию
Сделайте скрипт исполняемым и ограничьте доступ к нему, потому что внутри лежит токен:
chmod 700 /usr/local/bin/ya-backup.sh
Затем добавьте задание в crontab -e, например запуск каждую ночь в 03:30 с записью журнала:
30 3 * * * /usr/local/bin/ya-backup.sh >> /var/log/ya-backup.log 2>&1
Проверка и восстановление
- Запустите скрипт вручную и посмотрите журнал: в строках должен быть код
201или202. - Убедитесь, что файлы появились в папке
backupsна Диске и их размер не нулевой. - Хотя бы раз проверьте восстановление на тестовом сервере: распакуйте архив командой
tar -xzf, а дамп загрузите командойgunzip -c db_ДАТА.sql.gz | mysql имя_базы. Копия, которую ни разу не пробовали развернуть, — это только предположение, что копия есть.
Что можно улучшить
- Использовать утилиту
jqдля разбора ответа API вместоsed, если она установлена:jq -r .href. - Присылать себе уведомление на почту, если код ответа не
201и не202. - Для больших каталогов исключать из архива кэш и временные файлы с помощью параметра
--excludeуtar. - Шифровать архивы (например,
gpg), если на сайте хранятся персональные данные.
Такая схема не заменит полноценную систему резервного копирования, но для личного блога или небольшого сайта её хватает с запасом, а настройка занимает около получаса.
