From f8e0267b54fd0e4a24084e77083ca9237e6a55b1 Mon Sep 17 00:00:00 2001 From: "S.Gromov" Date: Wed, 22 Oct 2025 10:24:53 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82=D1=8B=20?= =?UTF-8?q?=D0=B8=20=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=D1=83=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D1=83=D0=BB=D1=83?= =?UTF-8?q?=D1=87=D1=88=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=80=D0=B5=D0=B7=D0=B5?= =?UTF-8?q?=D1=80=D0=B2=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BA=D0=BE=D0=BF=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Изменено имя папки в S3 на имя проекта по умолчанию - Обновлено количество хранимых версий бэкапов вместо дней - Изменено расписание запуска бэкапов на 05:00 - Обновлены переменные окружения в примерах и документации - Улучшено логирование и обработка старых бэкапов --- Dockerfile | 3 + README.md | 263 ++++++++++---------------------------------- backup.sh | 90 +++++++-------- docker-compose.yaml | 12 +- entrypoint.sh | 10 +- env.example | 12 +- 6 files changed, 130 insertions(+), 260 deletions(-) diff --git a/Dockerfile b/Dockerfile index 97edf74..135e46e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,6 +36,9 @@ RUN chmod +x /scripts/backup.sh /scripts/entrypoint.sh # Устанавливаем рабочую директорию WORKDIR /scripts +# Устанавливаем дефолтный часовой пояс +ENV TZ=Europe/Moscow + # Запускаем entrypoint CMD ["/scripts/entrypoint.sh"] diff --git a/README.md b/README.md index 19beb53..e44056b 100644 --- a/README.md +++ b/README.md @@ -1,138 +1,99 @@ -# 🗄️ Universal Docker Backup Service +# Docker S3 Backup -![Build Status](https://github.com/USERNAME/docker-s3-backup/actions/workflows/docker-build.yml/badge.svg) +Автоматическое резервное копирование Docker-проектов в S3-хранилище по расписанию. -Универсальная система автоматического резервного копирования Docker-проектов с загрузкой в S3-совместимое хранилище. +## Что делает -## ✨ Возможности +- Создаёт полный архив директории проекта +- Загружает в S3-совместимое хранилище (Yandex Cloud, Timeweb, AWS S3, MinIO) +- Работает по расписанию cron (по умолчанию: каждый день в 05:00 МСК) +- Хранит только последние N версий бэкапов (по умолчанию: 30) +- Сохраняет бекапы в папку с именем проекта: `s3://bucket/project-name/` +- Опционально останавливает сервисы перед бекапом -- ✅ Автоматическое создание tar.gz архивов по расписанию (cron) -- ✅ Загрузка бэкапов в S3-совместимые хранилища (Yandex Cloud, AWS S3, MinIO, Timeweb и т.д.) -- ✅ Опциональная остановка сервисов перед бекапом для консистентности данных -- ✅ Автоматическое удаление старых бэкапов -- ✅ Подробное логирование всех операций -- ✅ Запуск в отдельном Docker-контейнере +## Быстрый старт -## 🚀 Быстрый старт - -### Вариант 1: Использование готового образа (рекомендуется) - -Создайте `docker-compose.yaml` в директории вашего проекта: +Создайте `docker-compose.yaml`: ```yaml services: backup: - image: ghcr.io/USERNAME/docker-s3-backup:latest + image: ghcr.io/gromov-io/docker-s3-backup:latest container_name: project-backup restart: unless-stopped environment: - # Ваши настройки (см. ниже) + - BACKUP_PROJECT_NAME=my-project + - BACKUP_S3_BUCKET=my-backups + - BACKUP_S3_ENDPOINT=s3.twcstorage.ru + - BACKUP_S3_ACCESS_KEY=your_key + - BACKUP_S3_SECRET_KEY=your_secret volumes: - .:/backup-source:ro - /var/run/docker.sock:/var/run/docker.sock:ro ``` -### Вариант 2: Сборка из исходников - -Скопируйте файлы в директорию вашего проекта, который нужно бекапить: - -```bash -git clone backup-system -cd your-project -cp -r backup-system/* . -``` - -### Настройка - -Отредактируйте `docker-compose.yaml` и укажите свои значения переменных окружения: - -```yaml -environment: - # Обязательные параметры - - BACKUP_PROJECT_NAME=my-project - - BACKUP_S3_BUCKET=my-backups - - BACKUP_S3_ENDPOINT=s3.twcstorage.ru - - BACKUP_S3_ACCESS_KEY=your_access_key - - BACKUP_S3_SECRET_KEY=your_secret_key - - # Необязательные параметры - - BACKUP_SCHEDULE=0 3 * * * # Каждый день в 03:00 - - BACKUP_RETENTION_DAYS=30 -``` - -### Запуск +Запустите: ```bash docker compose up -d backup ``` -Проверить логи: +**По умолчанию:** +- Часовой пояс: `Europe/Moscow` +- Расписание: каждый день в `05:00` +- Хранение: `30` последних версий +- Папка в S3: `{project-name}/` + +## Ручной запуск бекапа ```bash -docker compose logs -f backup +docker exec project-backup /scripts/backup.sh ``` -> **Примечание:** Замените `USERNAME` в URL образа на ваш GitHub username после публикации проекта. +## Переменные окружения -## 📋 Переменные окружения +### Обязательные -Все параметры подробно описаны в `docker-compose.yaml` с примерами использования. +| Переменная | Описание | Пример | +|------------|----------|--------| +| `BACKUP_S3_BUCKET` | S3 бакет | `my-backups` | +| `BACKUP_S3_ENDPOINT` | S3 эндпоинт | `s3.twcstorage.ru` | +| `BACKUP_S3_ACCESS_KEY` | Ключ доступа | `your_key` | +| `BACKUP_S3_SECRET_KEY` | Секретный ключ | `your_secret` | -### Обязательные параметры +### Опциональные -| Параметр | Описание | Пример | -|----------|----------|--------| -| `BACKUP_S3_BUCKET` | Имя S3 бакета | `my-backups` | -| `BACKUP_S3_ENDPOINT` | Эндпоинт S3 сервиса | `s3.twcstorage.ru` | -| `BACKUP_S3_ACCESS_KEY` | Ключ доступа S3 | `your_key` | -| `BACKUP_S3_SECRET_KEY` | Секретный ключ S3 | `your_secret` | - -### Необязательные параметры - -| Параметр | Значение по умолчанию | Описание | -|----------|----------------------|----------| -| `BACKUP_PROJECT_NAME` | `project` | Имя проекта для архивов | -| `BACKUP_S3_FOLDER` | ` ` (корень) | Папка внутри бакета | +| Переменная | По умолчанию | Описание | +|------------|--------------|----------| +| `TZ` | `Europe/Moscow` | Часовой пояс | +| `BACKUP_PROJECT_NAME` | `project` | Имя проекта | +| `BACKUP_SCHEDULE` | `0 5 * * *` | Расписание cron | +| `BACKUP_RETENTION_COUNT` | `30` | Количество версий | | `BACKUP_S3_REGION` | `ru-1` | Регион S3 | -| `BACKUP_RETENTION_DAYS` | `30` | Дни хранения бэкапов | -| `BACKUP_SCHEDULE` | `0 3 * * *` | Расписание cron | +| `BACKUP_S3_FOLDER` | `{project}` | Папка в бакете | | `BACKUP_ON_START` | `false` | Бекап при старте | -| `BACKUP_STOP_SERVICES` | ` ` | Сервисы для остановки | -| `BACKUP_COMPOSE_FILE` | ` ` | Путь к compose-файлу | +| `BACKUP_STOP_SERVICES` | - | Сервисы для остановки | -## 🛠️ Расширенные сценарии -### Остановка сервисов перед бекапом +## Полезные команды -Для консистентности данных можно останавливать сервисы перед созданием архива: - -```yaml -environment: - # ... другие параметры ... - - BACKUP_STOP_SERVICES=gitea gitea-db - - BACKUP_COMPOSE_FILE=/backup-source/docker-compose.yml +**Просмотр логов:** +```bash +docker logs -f project-backup +docker exec project-backup tail -f /var/log/backup.log ``` -**Важно:** -- Сервисы будут остановлены через `docker compose down` -- После создания архива сервисы автоматически запустятся через `docker compose up -d` -- Если бекап упадет с ошибкой - сервисы все равно запустятся обратно - -### Изменение директории для бекапа - -По умолчанию бекапится текущая директория (`.`). Чтобы изменить: - -```yaml -volumes: - - /path/to/your/project:/backup-source:ro - # ... остальные volumes ... +**Проверка S3:** +```bash +docker exec project-backup aws s3 ls s3://your-bucket --endpoint-url=https://your-endpoint ``` -### Примеры расписаний (cron) + +## Расписания cron ```yaml -# Каждый день в 03:00 -- BACKUP_SCHEDULE=0 3 * * * +# Каждый день в 05:00 +- BACKUP_SCHEDULE=0 5 * * * # Каждые 6 часов - BACKUP_SCHEDULE=0 */6 * * * @@ -142,123 +103,21 @@ volumes: # Каждые 30 минут - BACKUP_SCHEDULE=*/30 * * * * - -# Дважды в день: 03:00 и 15:00 -- BACKUP_SCHEDULE=0 3,15 * * * ``` -### Тестовый бекап +## Остановка сервисов -Для проверки настроек запустите бекап вручную: - -```bash -docker compose exec backup /scripts/backup.sh -``` - -Или включите бекап при старте: +Для консистентности данных можно останавливать сервисы: ```yaml -- BACKUP_ON_START=true +environment: + - BACKUP_STOP_SERVICES=app db ``` -## 📂 Формат архивов +Сервисы автоматически запустятся после создания архива. -Архивы создаются в формате: -``` -{BACKUP_PROJECT_NAME}-backup-YYYY-MM-DD_HH-MM-SS.tar.gz -``` - -Пример: `my-project-backup-2025-10-22_03-00-00.tar.gz` - -## 🔧 Структура проекта - -``` -. -├── backup.sh # Основной скрипт бекапа -├── entrypoint.sh # Точка входа контейнера -├── crontab # Шаблон cron (генерируется автоматически) -├── Dockerfile # Образ контейнера -├── docker-compose.yaml # Конфигурация сервиса -└── README.md # Документация -``` - -## 🐛 Решение проблем - -### Проверка логов - -```bash -# Все логи -docker compose logs backup - -# Логи в реальном времени -docker compose logs -f backup - -# Внутренние логи бекапа -docker compose exec backup cat /var/log/backup.log -``` - -### Проверка доступа к S3 - -```bash -docker compose exec backup aws s3 ls s3://your-bucket --endpoint-url=https://your-endpoint -``` - -### Ручной запуск бекапа - -```bash -docker compose exec backup /scripts/backup.sh -``` - -## 📊 S3-провайдеры - -### Yandex Cloud Object Storage - -```yaml -- BACKUP_S3_ENDPOINT=storage.yandexcloud.net -- BACKUP_S3_REGION=ru-central1 -``` - -### Timeweb Cloud S3 - -```yaml -- BACKUP_S3_ENDPOINT=s3.twcstorage.ru -- BACKUP_S3_REGION=ru-1 -``` - -### AWS S3 - -```yaml -- BACKUP_S3_ENDPOINT=s3.amazonaws.com -- BACKUP_S3_REGION=us-east-1 -``` - -### MinIO (self-hosted) - -```yaml -- BACKUP_S3_ENDPOINT=minio.your-domain.com -- BACKUP_S3_REGION=us-east-1 -``` - -## 🔄 CI/CD - -Проект использует GitHub Actions для автоматической сборки и публикации Docker-образов: - -- **Сборка:** При каждом push в main/master -- **Публикация:** Автоматическая публикация в GitHub Container Registry (ghcr.io) -- **Платформы:** Поддержка amd64 и arm64 архитектур -- **Теги:** - - `latest` - последняя версия из main/master - - `v1.0.0` - семантическое версионирование при создании тегов - - `main-sha` - образы с хешем коммита - -Готовые образы доступны по адресу: `ghcr.io/USERNAME/docker-s3-backup` - -## 📝 Лицензия +## Лицензия MIT -## 🤝 Поддержка - -При возникновении проблем создайте issue в репозитории. - diff --git a/backup.sh b/backup.sh index e8450f3..dd737d2 100644 --- a/backup.sh +++ b/backup.sh @@ -169,17 +169,17 @@ upload_to_s3() { fi local backup_filename=$(basename "$backup_path") + local project_name="${BACKUP_PROJECT_NAME:-project}" # Формируем путь с учетом BACKUP_S3_FOLDER - local s3_folder="${BACKUP_S3_FOLDER}" - if [ -n "$s3_folder" ]; then - # Удаляем начальный и конечный слэш, если есть - s3_folder="${s3_folder#/}" - s3_folder="${s3_folder%/}" - local s3_path="s3://${BACKUP_S3_BUCKET}/${s3_folder}/${backup_filename}" - else - local s3_path="s3://${BACKUP_S3_BUCKET}/${backup_filename}" - fi + # По умолчанию используется имя проекта как папка + local s3_folder="${BACKUP_S3_FOLDER:-$project_name}" + + # Удаляем начальный и конечный слэш, если есть + s3_folder="${s3_folder#/}" + s3_folder="${s3_folder%/}" + + local s3_path="s3://${BACKUP_S3_BUCKET}/${s3_folder}/${backup_filename}" log "Загрузка бэкапа в S3: ${s3_path}" @@ -193,51 +193,55 @@ upload_to_s3() { # Удаление старых бэкапов из S3 cleanup_old_backups() { - local retention_days="${BACKUP_RETENTION_DAYS:-30}" + local retention_count="${BACKUP_RETENTION_COUNT:-30}" - log "Очистка старых бэкапов (старше ${retention_days} дней)..." + log "Очистка старых бэкапов (храним последние ${retention_count} версий)..." - # Вычисляем дату отсечения (совместимо с BusyBox) - local retention_seconds=$((retention_days * 86400)) - local current_timestamp=$(date +%s) - local cutoff_timestamp=$((current_timestamp - retention_seconds)) - local cutoff_date=$(date -d "@${cutoff_timestamp}" '+%Y-%m-%d' 2>/dev/null || date -r "${cutoff_timestamp}" '+%Y-%m-%d' 2>/dev/null || echo "") + # Формируем путь с учетом BACKUP_S3_FOLDER + # По умолчанию используется имя проекта как папка + local project_name="${BACKUP_PROJECT_NAME:-project}" + local s3_folder="${BACKUP_S3_FOLDER:-$project_name}" - if [ -z "$cutoff_date" ]; then - log_warning "Не удалось вычислить дату отсечения, пропускаем очистку" + # Удаляем начальный и конечный слэш, если есть + s3_folder="${s3_folder#/}" + s3_folder="${s3_folder%/}" + + local s3_list_path="s3://${BACKUP_S3_BUCKET}/${s3_folder}/" + + # Получаем список всех бэкапов и сортируем по дате (новые первые) + local backup_list=$(aws s3 ls "${s3_list_path}" $AWS_ENDPOINT_ARG | grep "\-backup-" | awk '{print $4}' | sort -r) + + if [ -z "$backup_list" ]; then + log "Бэкапы не найдены" return 0 fi - log "Удаление бэкапов старше ${cutoff_date}..." + local total_backups=$(echo "$backup_list" | wc -l) + log "Найдено бэкапов: ${total_backups}" - # Формируем путь с учетом BACKUP_S3_FOLDER - local s3_folder="${BACKUP_S3_FOLDER}" - if [ -n "$s3_folder" ]; then - s3_folder="${s3_folder#/}" - s3_folder="${s3_folder%/}" - local s3_list_path="s3://${BACKUP_S3_BUCKET}/${s3_folder}/" - else - local s3_list_path="s3://${BACKUP_S3_BUCKET}/" + # Если бэкапов меньше или равно лимиту, ничего не удаляем + if [ "$total_backups" -le "$retention_count" ]; then + log "Количество бэкапов в пределах лимита, удаление не требуется" + return 0 fi - # Получаем список всех бэкапов - aws s3 ls "${s3_list_path}" $AWS_ENDPOINT_ARG | grep "\-backup-" | while read -r line; do - # Извлекаем дату из имени файла (*-backup-YYYY-MM-DD_HH-MM-SS.tar.gz) - local filename=$(echo "$line" | awk '{print $4}') - local file_date=$(echo "$filename" | sed -n 's/.*-backup-\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/p') + # Удаляем старые бэкапы (все после N последних) + local deleted_count=0 + local index=0 + + echo "$backup_list" | while read -r filename; do + index=$((index + 1)) - if [ -n "$file_date" ] && [ "$file_date" \< "$cutoff_date" ]; then - log "Удаление старого бэкапа: ${filename}" - if [ -n "$s3_folder" ]; then - aws s3 rm "s3://${BACKUP_S3_BUCKET}/${s3_folder}/${filename}" $AWS_ENDPOINT_ARG || { - log_warning "Не удалось удалить ${filename}" - } - else - aws s3 rm "s3://${BACKUP_S3_BUCKET}/${filename}" $AWS_ENDPOINT_ARG || { - log_warning "Не удалось удалить ${filename}" - } - fi + # Пропускаем первые N (самые новые) + if [ "$index" -le "$retention_count" ]; then + continue fi + + log "Удаление старого бэкапа: ${filename}" + aws s3 rm "s3://${BACKUP_S3_BUCKET}/${s3_folder}/${filename}" $AWS_ENDPOINT_ARG || { + log_warning "Не удалось удалить ${filename}" + } + deleted_count=$((deleted_count + 1)) done log "Очистка завершена" diff --git a/docker-compose.yaml b/docker-compose.yaml index 6f2a65d..7b51ec5 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -10,7 +10,7 @@ services: restart: unless-stopped environment: - - TZ=Europe/Moscow + - TZ=${TZ:-Europe/Moscow} # Имя проекта (используется в названии архива: {name}-backup-2025-10-22_03-00-00.tar.gz) - BACKUP_PROJECT_NAME=${BACKUP_PROJECT_NAME:-project} @@ -18,7 +18,7 @@ services: # S3 бакет для хранения бэкапов (обязательно) - BACKUP_S3_BUCKET=${BACKUP_S3_BUCKET} - # Папка внутри бакета (необязательно, по умолчанию - корень) + # Папка внутри бакета (необязательно, по умолчанию - имя проекта) - BACKUP_S3_FOLDER=${BACKUP_S3_FOLDER:-} # Эндпоинт S3 сервиса (обязательно): s3.twcstorage.ru, storage.yandexcloud.net и т.д. @@ -31,11 +31,11 @@ services: # Регион S3 - BACKUP_S3_REGION=${BACKUP_S3_REGION:-ru-1} - # Количество дней хранения бэкапов (автоматически удаляются старые) - - BACKUP_RETENTION_DAYS=${BACKUP_RETENTION_DAYS:-30} + # Количество последних версий бэкапов для хранения (автоматически удаляются старые) + - BACKUP_RETENTION_COUNT=${BACKUP_RETENTION_COUNT:-30} - # Расписание запуска в формате cron: "0 3 * * *" = каждый день в 03:00 - - BACKUP_SCHEDULE=${BACKUP_SCHEDULE:-0 3 * * *} + # Расписание запуска в формате cron: "0 5 * * *" = каждый день в 05:00 МСК + - BACKUP_SCHEDULE=${BACKUP_SCHEDULE:-0 5 * * *} # Запускать ли бэкап сразу при старте контейнера (true/false) - BACKUP_ON_START=${BACKUP_ON_START:-false} diff --git a/entrypoint.sh b/entrypoint.sh index 9a43c90..d5e5c4a 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -5,11 +5,11 @@ echo "Universal Docker Project Backup Service" echo "================================================" echo "Project: ${BACKUP_PROJECT_NAME:-project}" echo "Source Path: ${BACKUP_SOURCE_PATH:-.} (mounted as /backup-source)" -echo "Timezone: ${TZ:-UTC}" -echo "Backup Schedule: ${BACKUP_SCHEDULE:-0 3 * * *}" +echo "Timezone: ${TZ:-Europe/Moscow}" +echo "Backup Schedule: ${BACKUP_SCHEDULE:-0 5 * * *}" echo "S3 Endpoint: ${BACKUP_S3_ENDPOINT:-не указан}" echo "S3 Bucket: ${BACKUP_S3_BUCKET:-не указан}" -echo "Retention Days: ${BACKUP_RETENTION_DAYS:-30}" +echo "Retention Count: ${BACKUP_RETENTION_COUNT:-30} последних версий" echo "Backup Type: Full project directory" if [ -n "$BACKUP_STOP_SERVICES" ]; then echo "Services to stop: ${BACKUP_STOP_SERVICES}" @@ -25,9 +25,9 @@ if [ -z "$BACKUP_S3_BUCKET" ] || [ -z "$BACKUP_S3_ACCESS_KEY" ] || [ -z "$BACKUP fi # Создаем crontab файл с заданным расписанием -echo "${BACKUP_SCHEDULE:-0 3 * * *} /scripts/backup.sh >> /var/log/backup.log 2>&1" > /scripts/crontab +echo "${BACKUP_SCHEDULE:-0 5 * * *} /scripts/backup.sh >> /var/log/backup.log 2>&1" > /scripts/crontab -echo "Расписание установлено: ${BACKUP_SCHEDULE:-0 3 * * *}" +echo "Расписание установлено: ${BACKUP_SCHEDULE:-0 5 * * *}" echo "Логи сохраняются в: /var/log/backup.log" echo "================================================" diff --git a/env.example b/env.example index 0aaaeb9..42b061b 100644 --- a/env.example +++ b/env.example @@ -5,6 +5,9 @@ # в S3-совместимое хранилище. Бэкап запускается по расписанию в отдельном Docker контейнере. # =========================================== +# Часовой пояс (по умолчанию: Europe/Moscow) +TZ=Europe/Moscow + # Путь к директории для бекапа (необязательно, по умолчанию - текущая директория) # Примеры: /var/www/project, /home/user/my-project BACKUP_SOURCE_PATH= @@ -16,7 +19,8 @@ BACKUP_PROJECT_NAME=project # S3 бакет для хранения бэкапов (обязательно) BACKUP_S3_BUCKET= -# Папка внутри S3 бакета (необязательно, по умолчанию - корень) +# Папка внутри S3 бакета (необязательно, по умолчанию - имя проекта из BACKUP_PROJECT_NAME) +# Если не указано, бекапы сохраняются в s3://bucket/[BACKUP_PROJECT_NAME]/ # Примеры: backups, project/backups, prod/backups BACKUP_S3_FOLDER= @@ -31,9 +35,9 @@ BACKUP_S3_SECRET_KEY= # Регион S3 (по умолчанию: ru-1) BACKUP_S3_REGION=ru-1 -# Количество дней хранения старых бэкапов (по умолчанию: 30) -# Бэкапы старше указанного количества дней будут автоматически удалены -BACKUP_RETENTION_DAYS=30 +# Количество последних версий бэкапов для хранения (по умолчанию: 30) +# Будут сохранены только последние N бэкапов, остальные автоматически удалятся +BACKUP_RETENTION_COUNT=30 # Расписание запуска бэкапа в формате cron (по умолчанию: 0 3 * * * - каждый день в 03:00) # Формат: минута час день месяц день_недели