Backup: refactor notifications
This commit is contained in:
@@ -39,18 +39,10 @@ logging.basicConfig(
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TelegramConfig:
|
||||
type: str
|
||||
telegram_bot_token: str
|
||||
telegram_chat_id: str
|
||||
notifications_name: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
host_name: str
|
||||
roots: List[Path]
|
||||
notifications: Dict[str, TelegramConfig]
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -160,19 +152,61 @@ class ResticStorage(Storage):
|
||||
return True
|
||||
|
||||
|
||||
class Notifier(ABC):
|
||||
def send(self, html_message: str):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class TelegramNotifier(Notifier):
|
||||
TYPE_NAME = "telegram"
|
||||
|
||||
def __init__(self, name: str, params: Dict[str, Any]):
|
||||
self.name = name
|
||||
self.telegram_bot_token = str(params.get("telegram_bot_token", ""))
|
||||
self.telegram_chat_id = str(params.get("telegram_chat_id", ""))
|
||||
if not all(
|
||||
[
|
||||
self.telegram_bot_token,
|
||||
self.telegram_chat_id,
|
||||
]
|
||||
):
|
||||
raise ValueError(
|
||||
f"Missing notification configuration values for backend {name}"
|
||||
)
|
||||
|
||||
def send(self, html_message: str):
|
||||
url = f"https://api.telegram.org/bot{self.telegram_bot_token}/sendMessage"
|
||||
data = {
|
||||
"chat_id": self.telegram_chat_id,
|
||||
"parse_mode": "HTML",
|
||||
"text": html_message,
|
||||
}
|
||||
|
||||
response = requests.post(url, data=data, timeout=30)
|
||||
|
||||
if response.status_code == 200:
|
||||
logger.info("Telegram notification sent successfully")
|
||||
else:
|
||||
logger.error(
|
||||
f"Failed to send Telegram notification: {response.status_code} - {response.text}"
|
||||
)
|
||||
|
||||
|
||||
class BackupManager:
|
||||
def __init__(self, config: Config, roots: List[Path], storages: List[Storage]):
|
||||
def __init__(
|
||||
self,
|
||||
config: Config,
|
||||
roots: List[Path],
|
||||
storages: List[Storage],
|
||||
notifiers: List[Notifier],
|
||||
):
|
||||
self.errors: List[str] = []
|
||||
self.warnings: List[str] = []
|
||||
self.successful_backups: List[str] = []
|
||||
self.config = config
|
||||
self.roots: List[Path] = roots
|
||||
self.storages = storages
|
||||
|
||||
def _select_telegram(self) -> Optional[TelegramConfig]:
|
||||
if "telegram" in self.config.notifications:
|
||||
return self.config.notifications["telegram"]
|
||||
return next(iter(self.config.notifications.values()), None)
|
||||
self.notifiers = notifiers
|
||||
|
||||
def find_applications(self) -> List[Application]:
|
||||
"""Get all application directories and their owners."""
|
||||
@@ -308,54 +342,32 @@ class BackupManager:
|
||||
|
||||
return backup_dirs
|
||||
|
||||
def send_telegram_notification(self, success: bool) -> None:
|
||||
"""Send notification to Telegram"""
|
||||
telegram_cfg = self._select_telegram()
|
||||
if telegram_cfg is None:
|
||||
logger.warning("No telegram notification backend configured")
|
||||
return
|
||||
def send_notification(self, success: bool) -> None:
|
||||
"""Send notification to Notifiers"""
|
||||
|
||||
try:
|
||||
if success and not self.errors:
|
||||
message = (
|
||||
f"<b>{telegram_cfg.notifications_name}</b>: бекап успешно завершен!"
|
||||
)
|
||||
if self.successful_backups:
|
||||
message += (
|
||||
f"\n\nУспешные бекапы: {', '.join(self.successful_backups)}"
|
||||
)
|
||||
else:
|
||||
message = f"<b>{telegram_cfg.notifications_name}</b>: бекап завершен с ошибками!"
|
||||
if success and not self.errors:
|
||||
message = f"<b>{self.config.host_name}</b>: бекап успешно завершен!"
|
||||
if self.successful_backups:
|
||||
message += f"\n\nУспешные бекапы: {', '.join(self.successful_backups)}"
|
||||
else:
|
||||
message = f"<b>{self.config.host_name}</b>: бекап завершен с ошибками!"
|
||||
|
||||
if self.successful_backups:
|
||||
message += (
|
||||
f"\n\n✅ Успешные бекапы: {', '.join(self.successful_backups)}"
|
||||
)
|
||||
|
||||
if self.warnings:
|
||||
message += f"\n\n⚠️ Предупреждения:\n" + "\n".join(self.warnings)
|
||||
|
||||
if self.errors:
|
||||
message += f"\n\n❌ Ошибки:\n" + "\n".join(self.errors)
|
||||
|
||||
url = f"https://api.telegram.org/bot{telegram_cfg.telegram_bot_token}/sendMessage"
|
||||
data = {
|
||||
"chat_id": telegram_cfg.telegram_chat_id,
|
||||
"parse_mode": "HTML",
|
||||
"text": message,
|
||||
}
|
||||
|
||||
response = requests.post(url, data=data, timeout=30)
|
||||
|
||||
if response.status_code == 200:
|
||||
logger.info("Telegram notification sent successfully")
|
||||
else:
|
||||
logger.error(
|
||||
f"Failed to send Telegram notification: {response.status_code} - {response.text}"
|
||||
if self.successful_backups:
|
||||
message += (
|
||||
f"\n\n✅ Успешные бекапы: {', '.join(self.successful_backups)}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send Telegram notification: {str(e)}")
|
||||
if self.warnings:
|
||||
message += f"\n\n⚠️ Предупреждения:\n" + "\n".join(self.warnings)
|
||||
|
||||
if self.errors:
|
||||
message += f"\n\n❌ Ошибки:\n" + "\n".join(self.errors)
|
||||
|
||||
for notificator in self.notifiers:
|
||||
try:
|
||||
notificator.send(message)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send notification: {str(e)}")
|
||||
|
||||
def run_backup_process(self) -> bool:
|
||||
"""Main backup process"""
|
||||
@@ -399,7 +411,7 @@ class BackupManager:
|
||||
overall_success = overall_success and backup_result
|
||||
|
||||
# Send notification
|
||||
self.send_telegram_notification(overall_success)
|
||||
self.send_notification(overall_success)
|
||||
|
||||
logger.info("Backup process completed")
|
||||
|
||||
@@ -422,6 +434,8 @@ def initialize(config_path: Path) -> BackupManager:
|
||||
logger.error(f"Failed to read config file {config_path}: {e}")
|
||||
raise
|
||||
|
||||
host_name = str(raw_config.get("host_name", "unknown"))
|
||||
|
||||
roots_raw = raw_config.get("roots") or []
|
||||
if not isinstance(roots_raw, list) or not roots_raw:
|
||||
raise ValueError("roots must be a non-empty list of paths in config.toml")
|
||||
@@ -438,37 +452,22 @@ def initialize(config_path: Path) -> BackupManager:
|
||||
if not storages:
|
||||
raise ValueError("At least one storage backend must be configured")
|
||||
|
||||
notifications_raw = raw_config.get("notifications") or {}
|
||||
notifications: Dict[str, TelegramConfig] = {}
|
||||
for name, cfg in notifications_raw.items():
|
||||
if not isinstance(cfg, dict):
|
||||
raise ValueError(f"Notification config for {name} must be a table")
|
||||
notifications[name] = TelegramConfig(
|
||||
type=cfg.get("type", ""),
|
||||
telegram_bot_token=cfg.get("telegram_bot_token", ""),
|
||||
telegram_chat_id=cfg.get("telegram_chat_id", ""),
|
||||
notifications_name=cfg.get("notifications_name", ""),
|
||||
)
|
||||
|
||||
if not notifications:
|
||||
notifications_raw = raw_config.get("notifier") or {}
|
||||
notifiers: List[Notifier] = []
|
||||
for name, params in notifications_raw.items():
|
||||
if not isinstance(params, dict):
|
||||
raise ValueError(f"Notificator config for {name} must be a table")
|
||||
notifier_type = params.get("type", "")
|
||||
if notifier_type == TelegramNotifier.TYPE_NAME:
|
||||
notifiers.append(TelegramNotifier(name, params))
|
||||
if not notifiers:
|
||||
raise ValueError("At least one notification backend must be configured")
|
||||
|
||||
for name, cfg in notifications.items():
|
||||
if not all(
|
||||
[
|
||||
cfg.type,
|
||||
cfg.telegram_bot_token,
|
||||
cfg.telegram_chat_id,
|
||||
cfg.notifications_name,
|
||||
]
|
||||
):
|
||||
raise ValueError(
|
||||
f"Missing notification configuration values for backend {name}"
|
||||
)
|
||||
config = Config(host_name=host_name, roots=roots)
|
||||
|
||||
config = Config(roots=roots, notifications=notifications)
|
||||
|
||||
return BackupManager(config=config, roots=roots, storages=storages)
|
||||
return BackupManager(
|
||||
config=config, roots=roots, storages=storages, notifiers=notifiers
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
Reference in New Issue
Block a user