Резервное копирование
SQL-баз с shared-хостинга

18 сентября 2020

Резервное копирование 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 килобайт, считается, что база либо повреждена, либо скачана с ошибками.


← Назад в Блог

Подпишитесь на новые статьи: