Search by

ibaranov / db-backup-sender

IgorAlexandrovich

Почасовое резервное копирование баз MySQL/MariaDB Laravel-проектов с отправкой дампа на удалённый приёмник

Package info

github.com/IgorAlexandrovich/db-backup-sender

Issues

pkg:composer/ibaranov/db-backup-sender

Statistics

Installs: 10

Dependents: 0

Suggesters: 0

Stars: 0

v1.0.6 2026-11-04 16:27 UTC

This package is auto-updated.

Last update: 2026-11-04 16:32:40 UTC


README

Composer-пакет для автоматического почасового резервного копирования базы MySQL/MariaDB любого Laravel-проекта с отправкой архива на удалённый приёмник (go-backup-receiver).

Пакет делает всё сам: создаёт дамп mysqldump --single-transaction, сжимает его в gzip, считает SHA-256, отправляет по HTTPS, удаляет временные файлы, пишет отдельный лог и присылает письмо при ошибке.

  • PHP 8.2+ (проверено на 8.3)
  • Laravel 10, 11, 12
  • MySQL / MariaDB
  • Linux-сервер (на Windows не тестировался)

Содержание

  1. Как это работает
  2. Установка
  3. Настройка .env
  4. Планировщик
  5. Команды
  6. Логи
  7. Временные файлы
  8. Уведомления на email
  9. Требование HTTPS
  10. Несколько проектов на одном сервере
  11. Диагностика
  12. Запуск тестов
  13. Смена vendor-имени
  14. Безопасность

Как это работает

php artisan backup:run
        │
        ├─ 1. проверка конфигурации (URL, токен, имя проекта)
        ├─ 2. проверка наличия mysqldump
        ├─ 3. mysqldump --single-transaction → dump.sql
        ├─ 4. gzip → dump.sql.gz
        ├─ 5. SHA-256 архива
        ├─ 6. POST multipart на приёмник (токен в заголовке)
        └─ 7. удаление временных файлов

Приёмник определяет проект по токену, проверяет контрольную сумму и только после этого сохраняет файл в подкаталог проекта. Имя файла формирует сервер: 2026-11-04_17-00-00.sql.gz.

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

Установка

1. Подключить пакет

cd /path/to/project
composer require your-vendor/db-backup-sender

Пакет поддерживает Laravel Package Auto Discovery: сервис-провайдер YourVendor\DbBackup\BackupServiceProvider регистрируется автоматически, команды backup:run и backup:check появляются сразу.

Если авто-обнаружение отключено (composer.json проекта содержит "extra": {"laravel": {"dont-discover": [...]}}), добавьте провайдер вручную в config/app.php:

'providers' => [
    // ...
    YourVendor\DbBackup\BackupServiceProvider::class,
],

2. Опубликовать конфигурацию

php artisan vendor:publish --tag=db-backup-config

Появится файл config/db-backup.php со всеми параметрами и комментариями. Он не обязателен: пакет работает и на значениях по умолчанию из .env. Публикация нужна, если вы хотите менять параметры в коде проекта.

3. Прописать параметры в .env

BACKUP_ENABLED=true
BACKUP_PROJECT_NAME=my-project
BACKUP_REMOTE_URL=https://backup.example.com/api/upload
BACKUP_TOKEN=вставьте_токен_из_установщика_приёмника

BACKUP_EMAIL_ENABLED=true
BACKUP_EMAIL=admin@example.com

BACKUP_PROJECT_NAME и BACKUP_TOKEN выдаёт установщик приёмника (deploy/install.sh): на каждый проект — свой токен, имя проекта совпадает с именем папки на сервере резервных копий.

4. Проверить готовность

php artisan backup:check

Команда проверит: включён ли пакет, корректно ли имя проекта, валидны ли URL и токен, отвечает ли приёмник, найден ли mysqldump, доступен ли каталог временных файлов, работает ли подключение к БД, есть ли таблицы не на InnoDB.

5. Сделать первую копию вручную

php artisan backup:run

Ожидаемый вывод:

Проект: my-project
mysqldump: /usr/bin/mysqldump
Версия клиента: mysqldump  Ver 8.0.36 ...
Дамп создан: 12.4 МБ
Архив gzip: 1.8 МБ (сжатие 15% от исходного)
SHA-256: 9f2c...
Резервная копия отправлена успешно
   Файл на приёмнике: my-project/2026-11-04_17-00-00.sql.gz
   Попытки отправки:  1

Настройка .env

Обязательные параметры

Переменная По умолчанию Описание
BACKUP_PROJECT_NAME APP_NAME Имя проекта на приёмнике. Только латиница, цифры, ., -, _; до 64 символов
BACKUP_REMOTE_URL — Полный адрес https://домен/api/upload
BACKUP_TOKEN — Секретный токен проекта (64 hex-символа)

Основные

Переменная По умолчанию Описание
BACKUP_ENABLED true Полное включение/выключение. false — команда завершается успешно и ничего не делает
BACKUP_HEADER_NAME X-Backup-Token Имя заголовка с токеном. Должно совпадать с auth_header в конфигурации приёмника. Не используйте _ в имени: Nginx такие заголовки отбрасывает
BACKUP_TIMEOUT 180 Таймаут HTTP-запроса, секунды
BACKUP_VERIFY_SSL true Проверять TLS-сертификат приёмника
BACKUP_RETRIES 3 Всего попыток отправки (1 — без повторов)
BACKUP_RETRY_DELAY 5 Пауза между попытками, секунды

Дамп

Переменная По умолчанию Описание
BACKUP_MYSQLDUMP_PATH поиск в PATH Полный путь к mysqldump
BACKUP_DUMP_TIMEOUT 0 Ограничение времени дампа, секунды (0 — без ограничения)
BACKUP_DB_CONNECTION default Имя подключения из config/database.php
BACKUP_CHECK_ENGINES true Предупреждать о таблицах не на InnoDB
BACKUP_GZIP_LEVEL 6 Уровень сжатия 1–9

Дополнительные аргументы mysqldump задаются только в config/db-backup.php:

'mysqldump' => [
    'extra_arguments' => [
        '--set-gtid-purged=OFF',   // MySQL 8 с GTID
        '--column-statistics=0',   // клиент MySQL 8 против сервера 5.7
        '--skip-routines',         // нет прав на процедуры
    ],
],

Файлы, почта, логи, расписание

Переменная По умолчанию Описание
BACKUP_TEMP_PATH storage/app/backups Каталог временных дампов (относительно корня проекта)
BACKUP_EMAIL_ENABLED true Отправлять письмо при ошибке
BACKUP_EMAIL — Адрес получателя
BACKUP_EMAIL_FROM config('mail.from') Адрес отправителя
BACKUP_SCHEDULE_ENABLED true Регистрировать задачу в планировщике
BACKUP_SCHEDULE_CRON 0 * * * * Расписание (по умолчанию каждый час)
BACKUP_SCHEDULE_LOCK_MINUTES 90 Сколько держится блокировка от параллельных запусков
BACKUP_LOG_CHANNEL db-backup Имя канала логирования
BACKUP_LOG_PATH storage/logs/db-backup.log Путь к файлу лога
BACKUP_LOG_DAYS 14 Сколько дней хранить лог (драйвер daily)
BACKUP_LOG_LEVEL debug Уровень логирования

Служебные варианты (использовать осознанно):

Переменная По умолчанию Описание
BACKUP_ALLOW_INSECURE_HTTP false Разрешить http:// вместо https://. Только для локальных проверок

Планировщик

Пакет сам регистрирует задачу:

Schedule::command('backup:run')
    ->withoutOverlapping(90)   // защита от наложения тяжёлых дампов
    ->cron('0 * * * *');       // каждый час

От Laravel-проекта нужен только стандартный cron планировщика:

* * * * * cd /path/to/project && php artisan schedule:run >> /dev/null 2>&1

Проверка, что задача зарегистрирована:

php artisan schedule:list

Отключить автоматическую регистрацию (например, если вы запускаете backup:run из собственного cron): BACKUP_SCHEDULE_ENABLED=false.

Важно про InnoDB. --single-transaction даёт согласованный снимок только для таблиц InnoDB. Для MyISAM (и других не-MySQL движков без транзакций) снимок может быть несогласованным. Команда backup:check и каждый запуск backup:run предупреждают об этом в логе и выводе. Если в базе есть MyISAM — переведите таблицы на InnoDB или делайте эти таблицы отдельным дампом с блокировкой.

Важно про время выполнения. Задача запускается синхронно: пока дамп создаётся и отправляется, schedule:run ждёт. Для дампа до 100 МБ это обычно секунды-десятки секунд. Если в проекте много тяжёлых задач, вынесите резервное копирование в отдельный cron-вызов php artisan backup:run.

Команды

php artisan backup:run

Создаёт и отправляет резервную копию.

Опция Назначение
--force Выполнить даже при BACKUP_ENABLED=false
--keep-temp Не удалять временные файлы после успешной отправки (для отладки)
--no-notify Не отправлять письмо при ошибке

Коды возврата: 0 — успех или отключённый пакет, 1 — ошибка.

php artisan backup:check

Диагностика без создания дампа. Возвращает 0, если критичных проблем нет, и 1, если что-то настроено неверно.

Опция Назначение
--skip-remote Не проверять доступность приёмника (полезно на закрытых стендах)

Пример вывода:

Диагностика резервного копирования (db-backup-sender)
────────────────────────────────────────────────────────────────────────
 [ OK ] Резервное копирование включено    да (BACKUP_ENABLED=true)
 [ OK ] Имя проекта                       my-project
 [ OK ] Адрес приёмника                   https://backup.example.com/api/upload
 [ OK ] Токен проекта                     задан, длина 64 символов
 [ OK ] Доступность приёмника             https://backup.example.com/health — отвечает
 [ OK ] Утилита mysqldump                 /usr/bin/mysqldump — mysqldump  Ver 8.0.36
 [ OK ] Каталог временных файлов          /var/www/project/storage/app/backups
 [ OK ] Подключение к БД                  mysql, база "project", версия 8.0.36
 [ OK ] Движки таблиц                     все таблицы на InnoDB — снимок согласован
 [ OK ] Уведомления на email               admin@example.com
────────────────────────────────────────────────────────────────────────
Все проверки пройдены.

Логи

Пакет пишет события в отдельный канал db-backup, чтобы они не терялись в общем логе приложения. По умолчанию — storage/logs/db-backup-YYYY-MM-DD.log (драйвер daily), 14 дней хранения.

Что логируется:

  • старт и параметры запуска (имя проекта, PID, версия PHP);
  • путь к mysqldump и версия клиента;
  • размер дампа, размер архива, SHA-256;
  • каждая попытка отправки и ответ приёмника;
  • предупреждения о таблицах не на InnoDB;
  • ошибка с этапом, причиной и путём к сохранённым временным файлам.

Чего в логе нет никогда: пароля БД, токена проекта, содержимого дампа.

Если нужно писать в свой канал, объявите его в config/logging.php и укажите BACKUP_LOG_CHANNEL=<имя канала> — пакет будет использовать его.

Временные файлы

На каждый запуск создаётся каталог:

storage/app/backups/<project>/<20261104-170000-1a2b3c4d>/
    dump.sql       # исходный дамп
    dump.sql.gz    # архив для отправки
  • после успешной отправки каталог удаляется;
  • при ошибке файлы остаются — дамп нельзя удалять до подтверждённой отправки, иначе разбираться в причине будет нечем (в письме и логе путь указывается). Удалите каталог вручную, когда проблема разобрана;
  • --keep-temp оставляет файлы и после успеха (для отладки).

Чтобы каталог не рос бесконечно, раз в неделю удаляйте старые:

find storage/app/backups -mindepth 2 -maxdepth 2 -type d -mtime +14 -exec rm -rf {} +

Уведомления на email

Письмо отправляется при любой ошибке резервного копирования, в том числе когда приёмник недоступен:

Тема: Ошибка резервного копирования: my-project

**Проект:** my-project
**Дата:** 04.11.2026 17:00
**Этап:** Отправка на удалённый сервер
**Причина:** приёмник вернул HTTP 401 (invalid_token): неверный токен проекта — проверьте BACKUP_TOKEN
**Попытки:** 3 из 3
**Следующее действие:** повтор при следующем запуске по расписанию.

Используется стандартный почтовый транспорт Laravel, поэтому в .env проекта должен быть настроен MAIL_MAILER, MAIL_HOST и остальные MAIL_*. Проверить отправку почты можно штатными средствами Laravel (php artisan tinker).

Требование HTTPS

Передача резервных копий по открытому HTTP запрещена по умолчанию: backup:run завершится ошибкой ещё до создания дампа.

  • рабочая схема: HTTPS с сертификатом Let's Encrypt на поддомене приёмника;
  • для локальной отладки можно временно включить BACKUP_ALLOW_INSECURE_HTTP=true (в лог попадёт предупреждение).

Проверка TLS-сертификата отключается BACKUP_VERIFY_SSL=false, но это снижает защиту от подмены сервера: используйте только на тестовом стенде.

Несколько проектов на одном сервере

Один приёмник обслуживает любое число Laravel-проектов. На приёмнике у каждого проекта свой токен и своя папка:

"projects": {
  "project-one":   {"token": "…"},
  "project-two":   {"token": "…"}
}

В каждом Laravel-проекте — свой BACKUP_TOKEN и BACKUP_PROJECT_NAME. Установка пакета в проекты независима: ошибка одного проекта не влияет на другие (у каждого свой процесс PHP и своя папка на приёмнике).

Добавление нового проекта описано в документации системы (documentation/INSTALL-RU.md, раздел «Подключение нового Laravel-проекта»).

Диагностика

Симптом Вероятная причина Что делать
утилита mysqldump не найдена в PATH не установлен mysql-client apt install default-mysql-client (MariaDB: mariadb-client), либо указать BACKUP_MYSQLDUMP_PATH
mysqldump завершился с ошибкой: Access denied неверные логин/пароль в config/database.php проверить доступы; backup:check покажет версию сервера, если подключение работает
не задан адрес приёмника пустой BACKUP_REMOTE_URL прописать URL из документации приёмника
адрес приёмника должен начинаться с https:// указан http:// использовать HTTPS или (только локально) BACKUP_ALLOW_INSECURE_HTTP=true
приёмник вернул HTTP 401 (invalid_token) чужой/устаревший токен взять токен проекта из config.json приёмника
приёмник вернул HTTP 401 (project_mismatch) имя проекта не соответствует токену BACKUP_PROJECT_NAME должен совпадать с именем проекта на приёмнике
приёмник вернул HTTP 413 файл больше лимита увеличить max_upload_size в приёмнике и client_max_body_size в Nginx
приёмник вернул HTTP 422 (checksum_mismatch) файл повреждён при передаче повторить запуск; проверить прокси/антивирус на пути
приёмник вернул HTTP 422 (bad_format) отправлен не gzip проверить BACKUP_GZIP_LEVEL, версию пакета
приёмник вернул HTTP 502/504 Go-служба не запущена или Nginx не может до неё достучаться systemctl status backup-receiver, journalctl -u backup-receiver -n 50
сетевая ошибка: … Connection refused неверный порт/домен, служба остановлена проверить curl https://backup.example.com/health
письмо не приходит не настроен MAIL_* или стоит BACKUP_EMAIL_ENABLED=false проверить настройки почты Laravel и лог db-backup
задача не запускается каждый час не добавлен cron schedule:run добавить cron; проверить php artisan schedule:list
задача «пропускается» не истекла блокировка withoutOverlapping блокировка живёт до 90 минут; при зависшем процессе удалить ключ из кеша или подождать

Полный список проблем и решений: documentation/TROUBLESHOOTING.md.

Запуск тестов

composer install
vendor/bin/phpunit                     # все тесты
vendor/bin/phpunit --exclude-group integration   # без внешних зависимостей

Что покрыто:

  • создание дампа (аргументы, безопасная передача пароля, ошибки, пустой дамп);
  • gzip-сжатие и распаковка;
  • SHA-256;
  • отправка: заголовок, multipart-поля, повторы, отказ при 4xx, таймауты;
  • отключённый режим, --force, --keep-temp, --no-notify;
  • удаление временных файлов и сохранение их при ошибке;
  • письмо об ошибке (содержимое и отсутствие секретов);
  • регистрация в планировщике и публикация конфигурации;
  • интеграция с настоящим Go-приёмником (тест EndToEndTest, группа integration);
  • тест с настоящим mysqldump (группа integration, включается переменной BACKUP_TEST_MYSQL_URL='mysql://user:pass@127.0.0.1:3306/db').

Интеграционный тест находит бинарник приёмника по пути ../go-backup-receiver/.local/backup-receiver-darwin-* или в bin/backup-receiver-linux-*; путь можно задать явно:

BACKUP_RECEIVER_BIN=/path/to/backup-receiver vendor/bin/phpunit --group integration

Смена vendor-имени

Пакет поставляется с условным пространством имён YourVendor\DbBackup. Чтобы выпустить его под своим именем, замените your-vendor и YourVendor в composer.json, src/, tests/:

# Linux
grep -rl 'YourVendor' composer.json src tests \
  | xargs sed -i 's/YourVendor/Acme/g'
sed -i 's#your-vendor/db-backup-sender#acme/db-backup-sender#' composer.json
# macOS
grep -rl 'YourVendor' composer.json src tests \
  | xargs sed -i '' 's/YourVendor/Acme/g'
sed -i '' 's#your-vendor/db-backup-sender#acme/db-backup-sender#' composer.json

После этого выполните composer dump-autoload и прогоните тесты.

Безопасность

  • Пароль MySQL передаётся mysqldump только через временный файл опций с правами 0600 и удаляется в finally. В аргументах процесса и в переменных окружения пароля нет.
  • Токен передаётся только в HTTP-заголовке, никогда не пишется в лог и не попадает в текст исключений (вырезается даже из ответа сервера).
  • Передача разрешена только по HTTPS; сертификат проверяется.
  • Временные файлы создаются с правами 0750 (каталог) и внутри storage/app.
  • Контрольная сумма SHA-256 защищает от порчи файла при передаче.

Что не реализовано (по требованиям этапа): шифрование дампа на стороне клиента, HMAC-подпись запроса. Данные защищены транспортом (TLS) и токеном.

Лицензия

MIT, см. LICENSE.