Резервное копирование SQL-базы с shared-хостинга
В первой части мы разбирались с сохранением файлов, теперь подумаем, какие препятствия нас ждут при попытке создать и скачать резервную копию базы:
- отсутствие SSH-доступа, т.е. невозможность вызвать mysqldump из консоли;
- директива disable_functions в общесистемных настройках PHP, содержащая функции passthru, popen и так далее, то есть невозможность вызова mysqldump через PHP-страницу;
- либо отсутствие утилиты mysqldump в доступном для пользователя дереве каталогов на хостинге (например, с помощью chroot);
- а также отсутствие возможности запускать на сервере собственные исполняемые файлы (например, с помощью "mount -o noexec");
- отсутствие подходящих плагинов для используемой CMS.
Нам нужен инструмент со следующими характеристиками:
- выполнение дампа по внешнему запросу, а не по внутреннему расписанию;
- немедленное предоставление дампа вызывающей стороне, а не самостоятельное выкладывание во внешнее хранилище;
- отправка дампа без промежуточного сохранения на диск сайта;
- отсутствие лишнего функционала (комбайны наподобие b374k или phpMiniAdmin неприемлемы);
- предназначение для вызова в программном режиме, а не вручную через веб-интерфейс;
- чтение параметров подключения к базе (в первую очередь паролей) из настроек CMS, без дублирования на вызывающей стороне или на сервере.
Альтернатива mysqldump:
- Для замены утилите mysqldump существует совместимая с ней библиотека, написанная на чистом PHP — https://github.com/ifsnop/mysqldump-php.
- Нам потребуется из неё только один файл — Mysqldump.php.
- Для её вызова мы напишем дополнительную обёртку.
Назначение обёртки:
- Авторизация клиента.
- Чтение параметров подключения к базе из настроек CMS (в нашем случае это будет Wordpress).
- Вызов библиотеки с правильными параметрами.
Авторизация клиента:
- Mysqldump.php и обёртка должны находиться на сайте в подкаталоге с длинным случайным именем, которое известно только удалённому клиенту и невозможно определить перебором.
- Кроме того, обёртка в нашем примере проверяет IP-адрес клиента и поле X-Secret в заголовке HTTP-запроса.
- Вы можете усовершенствовать защиту — добавить .htaccess, проверять логин-пароль через HTDigest и так далее.
- Кашу маслом не испортишь — чем больше уровней защиты потребуется преодолевать для доступа к данным, тем больше шансов сохранить их от посторонних глаз.
Исходный текст обёртки:
<?php
header('Content-Type: text/html; charset=utf-8');
error_reporting(E_ALL);
if (empty($_SERVER['HTTP_X_SECRET']) || $_SERVER['HTTP_X_SECRET'] !== 'VERY_LONG_AND_SECRET_STRING') {
die;
}
if (isset($_SERVER['HTTP_CLIENT_IP']) && !empty($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
if ($ip !== '1.2.3.4' && $ip !== '5.6.7.8') {
die(); // our offices only!
}
require_once(dirname(__FILE__) . '/../wp-config.php');
require_once('Mysqldump.php');
$settings = array(
'extended-insert' => false
);
$dump = new Ifsnop\Mysqldump\Mysqldump('mysql:host='.DB_HOST.';dbname='.DB_NAME, DB_USER, DB_PASSWORD, $settings);
$dump->start('php://output');
Вызывающий сценарий на сервере-получателе:
#!/bin/sh -e
URL="https://example.org/DatabaseBackup-SECRET_SUFFIX/dbdump.php"
DIR="/opt/backups/example.org-db"
mkdir -pm700 "$DIR"
cd "$DIR"
test -d ".git" || git init
curl -sS -H 'X-Secret: VERY_LONG_AND_SECRET_STRING' "$URL" \
| egrep -v -- '^-- (Date|Dump completed on): ' > dbdump.sql
SZ="$(stat -c %s dbdump.sql)"
test "$SZ" -le 100000 && exit 1
git add -A
LANG=C git status | grep -c '^nothing to commit' && exit 0 || :
git commit -am "Autocommit-$(date +%Y-%m-%d-%H%M)"
git remote | grep -q '' || exit 0
git push
Пояснения:
- Дамп автоматически сохраняется в Git-репозитарий — это хорошо работает, если объём базы и объём ежедневных изменений относительно невелики.
- По умолчанию дамп содержит очень длинные строки заполнения таблиц всеми записями за один запрос.
- Этот режим хорош для быстрого восстановления, но неприемлемо плох для построчного сравнения и сохранения изменений.
- Поэтому обёртка устанавливает флаг “extended-insert” в “false” (аналог ключа “--skip-extended-insert” для mysqldump).
- В этом режиме каждая запись будет вставляться в таблицу отдельным запросом на отдельной строке.
- Mysqldump вставляет в конец дампа строку с датой создания.
- Эта строка отрезается, потому что с нею дамп будет всегда выглядеть изменившимся, даже если данные в базе не менялись.
- Дополнительно проверяется размер скачанного файла — если он меньше 100 килобайт, считается, что база либо повреждена, либо скачана с ошибками.