add configurable backup archive
This commit is contained in:
+93
-6
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import subprocess
|
||||
import tarfile
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
@@ -23,21 +24,32 @@ from config import (
|
||||
DB_USER,
|
||||
PG_HOST,
|
||||
PG_PORT,
|
||||
BACKUP_CREATE_ARCHIVE,
|
||||
BACKUP_INCLUDE_DB,
|
||||
BACKUP_INCLUDE_CONFIG,
|
||||
BACKUP_INCLUDE_TEXTS,
|
||||
BACKUP_INCLUDE_IMG,
|
||||
)
|
||||
from logger import logger
|
||||
|
||||
|
||||
async def backup_database() -> Exception | None:
|
||||
"""
|
||||
Создает резервную копию базы данных и отправляет ее администраторам.
|
||||
Создает резервную копию базы данных (или полный архив) и отправляет его администраторам.
|
||||
|
||||
Returns:
|
||||
Optional[Exception]: Исключение в случае ошибки или None при успешном выполнении
|
||||
"""
|
||||
backup_file_path, exception = _create_database_backup()
|
||||
if BACKUP_CREATE_ARCHIVE:
|
||||
if not any([BACKUP_INCLUDE_DB, BACKUP_INCLUDE_CONFIG, BACKUP_INCLUDE_TEXTS, BACKUP_INCLUDE_IMG]):
|
||||
backup_file_path, exception = _create_database_backup()
|
||||
else:
|
||||
backup_file_path, exception = _create_backup_archive()
|
||||
else:
|
||||
backup_file_path, exception = _create_database_backup()
|
||||
|
||||
if exception:
|
||||
logger.error(f"Ошибка при создании бэкапа базы данных: {exception}")
|
||||
logger.error(f"Ошибка при создании бэкапа: {exception}")
|
||||
return exception
|
||||
|
||||
try:
|
||||
@@ -45,12 +57,12 @@ async def backup_database() -> Exception | None:
|
||||
exception = _cleanup_old_backups()
|
||||
|
||||
if exception:
|
||||
logger.error(f"Ошибка при удалении старых бэкапов базы данных: {exception}")
|
||||
logger.error(f"Ошибка при удалении старых бэкапов: {exception}")
|
||||
return exception
|
||||
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отправке бэкапа базы данных: {e}")
|
||||
logger.error(f"Ошибка при отправке бэкапа: {e}")
|
||||
return e
|
||||
|
||||
|
||||
@@ -103,9 +115,77 @@ def _create_database_backup() -> tuple[str | None, Exception | None]:
|
||||
del os.environ["PGPASSWORD"]
|
||||
|
||||
|
||||
def _create_backup_archive() -> tuple[str | None, Exception | None]:
|
||||
"""
|
||||
Создает архив (.tar.gz) с выбранными компонентами бекапа.
|
||||
|
||||
Returns:
|
||||
Tuple[Optional[str], Optional[Exception]]: Путь к файлу архива и исключение (если произошла ошибка)
|
||||
"""
|
||||
date_formatted = datetime.now().strftime("%Y-%m-%d-%H%M%S")
|
||||
backup_dir = Path(BACK_DIR)
|
||||
backup_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
archive_path = backup_dir / f"{DB_NAME}-full-backup-{date_formatted}.tar.gz"
|
||||
project_root = Path(__file__).parent.parent
|
||||
archive_folder = f"backup-{date_formatted}"
|
||||
|
||||
db_backup_path = None
|
||||
try:
|
||||
with tarfile.open(archive_path, "w:gz") as tar:
|
||||
if BACKUP_INCLUDE_DB:
|
||||
db_backup_path, db_exception = _create_database_backup()
|
||||
if db_exception:
|
||||
logger.warning(f"Не удалось создать бекап БД для архива: {db_exception}")
|
||||
elif db_backup_path and os.path.exists(db_backup_path):
|
||||
tar.add(db_backup_path, arcname=f"{archive_folder}/database.sql")
|
||||
logger.info("База данных добавлена в архив")
|
||||
|
||||
if BACKUP_INCLUDE_CONFIG:
|
||||
config_path = project_root / "config.py"
|
||||
if config_path.exists():
|
||||
tar.add(config_path, arcname=f"{archive_folder}/config.py")
|
||||
logger.info("config.py добавлен в архив")
|
||||
else:
|
||||
logger.warning("config.py не найден, пропущен")
|
||||
|
||||
if BACKUP_INCLUDE_TEXTS:
|
||||
texts_path = project_root / "handlers" / "texts.py"
|
||||
if texts_path.exists():
|
||||
tar.add(texts_path, arcname=f"{archive_folder}/texts.py")
|
||||
logger.info("texts.py добавлен в архив")
|
||||
else:
|
||||
logger.warning("handlers/texts.py не найден, пропущен")
|
||||
|
||||
if BACKUP_INCLUDE_IMG:
|
||||
img_dir = project_root / "img"
|
||||
if img_dir.exists() and img_dir.is_dir():
|
||||
img_files = [f for f in img_dir.iterdir() if f.is_file()]
|
||||
for img_file in img_files:
|
||||
tar.add(img_file, arcname=f"{archive_folder}/img/{img_file.name}")
|
||||
logger.info(f"Папка img/ добавлена в архив ({len(img_files)} файлов)")
|
||||
else:
|
||||
logger.warning("Папка img/ не найдена, пропущена")
|
||||
|
||||
logger.info(f"Архив бекапа создан: {archive_path}")
|
||||
|
||||
if db_backup_path and os.path.exists(db_backup_path) and db_backup_path != str(archive_path):
|
||||
try:
|
||||
os.unlink(db_backup_path)
|
||||
logger.info(f"Временный файл БД удален: {db_backup_path}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Не удалось удалить временный файл БД: {e}")
|
||||
|
||||
return str(archive_path), None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Непредвиденная ошибка при создании архива бекапа: {e}")
|
||||
return None, e
|
||||
|
||||
|
||||
def _cleanup_old_backups() -> Exception | None:
|
||||
"""
|
||||
Удаляет бэкапы старше 3 дней.
|
||||
Удаляет бэкапы старше 3 дней (как .sql, так и .tar.gz файлы).
|
||||
|
||||
Returns:
|
||||
Optional[Exception]: Исключение в случае ошибки или None при успешном выполнении
|
||||
@@ -124,6 +204,13 @@ def _cleanup_old_backups() -> Exception | None:
|
||||
backup_file.unlink()
|
||||
logger.info(f"Удален старый бэкап: {backup_file}")
|
||||
|
||||
for archive_file in backup_dir.glob("*.tar.gz"):
|
||||
if archive_file.is_file():
|
||||
file_mtime = datetime.fromtimestamp(archive_file.stat().st_mtime)
|
||||
if file_mtime < cutoff_date:
|
||||
archive_file.unlink()
|
||||
logger.info(f"Удален старый архив: {archive_file}")
|
||||
|
||||
logger.info("Очистка старых бэкапов завершена")
|
||||
return None
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user