diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index a2ba64eb..9a37c884 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -1306,6 +1306,127 @@ properties: example: - start - finish + apprise: + type: object + required: ['service_urls'] # TODO + additionalProperties: false + properties: + service_urls: + type: array + items: + type: string + description: | + List of Apprise service URLs to publish to. + example: + - "mastodon://accesskey/host/?visibility=direct" + - "pagerduty://A1BRTD4JD@TIiajkdnlazkcOXrIdevi7F/node01.local/drive_sda/" + start: + type: object + properties: + title: + type: string + description: | + Specify the message title. + example: Ping! + body: + type: string + description: | + Specify the message body. + exampe: Your backups have failed. + notification_type: + type: string + description: | + The Apprise message type. + enum: + - info + - success + - failure + - warning + example: + - failure + # tags: + # type: array + # items: + # type: string + # description: | + # One or more tags to filter which services to notify. + finish: + type: object + properties: + title: + type: string + description: | + Specify the message title. + example: Ping! + body: + type: string + description: | + Specify the message body. + exampe: Your backups have failed. + notification_type: + type: string + description: | + The Apprise message type. + enum: + - info + - success + - failure + - warning + example: + - failure + # tags: + # type: array + # items: + # type: string + # description: | + # One or more tags to filter which services to notify. + fail: + type: object + properties: + title: + type: string + description: | + Specify the message title. + example: Ping! + body: + type: string + description: | + Specify the message body. + exampe: Your backups have failed. + notification_type: + type: string + description: | + The Apprise message type. + enum: + - info + - success + - failure + - warning + example: + - failure + # tags: + # type: array + # items: + # type: string + # description: | + # One or more tags to filter which services to notify. + states: + type: array + items: + type: string + enum: + - start + - finish + - fail + uniqueItems: true + description: | + List of one or more monitoring states to ping for: "start", + "finish", and/or "fail". Defaults to pinging for failure + only. + example: + - start + - finish + healthchecks: type: object required: ['ping_url'] diff --git a/borgmatic/hooks/apprise.py b/borgmatic/hooks/apprise.py new file mode 100644 index 00000000..dbb4e478 --- /dev/null +++ b/borgmatic/hooks/apprise.py @@ -0,0 +1,83 @@ +import logging + +import apprise +from apprise import NotifyType, NotifyFormat + +logger = logging.getLogger(__name__) + + +def initialize_monitor( + ping_url, config, config_filename, monitoring_log_level, dry_run +): # pragma: no cover + ''' + No initialization is necessary for this monitor. + ''' + pass + + +def ping_monitor(hook_config, config, config_filename, state, monitoring_log_level, dry_run): + ''' + Ping the configured Apprise service URLs. + Use the given configuration filename in any log entries. + If this is a dry run, then don't actually ping anything. + ''' + run_states = hook_config.get('states', ['fail']) + + if state.name.lower() not in run_states: + return + + state_config = hook_config.get( + state.name.lower(), + { + 'title': f'A borgmatic {state.name} event happened', + 'body': f'A borgmatic {state.name} event happened', + 'notification_type': 'success', # TODO: default per state.name + # 'tag': ['borgmatic'], + }, + ) + + # TODO: Currently not very meaningful message. + # However, the Apprise service URLs can contain sensitive info. + dry_run_label = ' (dry run; not actually pinging)' if dry_run else '' + logger.info(f'{config_filename}: Pinging Apprise {dry_run_label}') + logger.debug(f'{config_filename}: Using Apprise ping') + + title = state_config.get('title', '') + body = state_config.get('body') + notify_type = state_config.get('notification_type', 'success') + + apobj = apprise.Apprise() + apobj.add(hook_config.get('service_urls')) + + if dry_run: + return + + result = apobj.notify( + title=title, + body=body, + body_format=NotifyFormat.TEXT, + notify_type=get_notify_type(notify_type) + ) + + if result is False: + logger.warning(f'{config_filename}: error sending some apprise notifications') + + +def get_notify_type(s): + if s == 'info': + return NotifyType.INFO + if s == 'success': + return NotifyType.SUCCESS + if s == 'warning': + return NotifyType.WARNING + if s == 'failure': + return NotifyType.FAILURE + + +def destroy_monitor( + ping_url_or_uuid, config, config_filename, monitoring_log_level, dry_run +): # pragma: no cover + ''' + No destruction is necessary for this monitor. + ''' + pass diff --git a/borgmatic/hooks/dispatch.py b/borgmatic/hooks/dispatch.py index 24793b5a..d437c980 100644 --- a/borgmatic/hooks/dispatch.py +++ b/borgmatic/hooks/dispatch.py @@ -1,6 +1,7 @@ import logging from borgmatic.hooks import ( + apprise, cronhub, cronitor, healthchecks, @@ -17,6 +18,7 @@ from borgmatic.hooks import ( logger = logging.getLogger(__name__) HOOK_NAME_TO_MODULE = { + 'apprise': apprise, 'cronhub': cronhub, 'cronitor': cronitor, 'healthchecks': healthchecks, diff --git a/borgmatic/hooks/monitor.py b/borgmatic/hooks/monitor.py index 118639f5..0cbfef4b 100644 --- a/borgmatic/hooks/monitor.py +++ b/borgmatic/hooks/monitor.py @@ -1,6 +1,6 @@ from enum import Enum -MONITOR_HOOK_NAMES = ('healthchecks', 'cronitor', 'cronhub', 'pagerduty', 'ntfy', 'loki') +MONITOR_HOOK_NAMES = ('apprise', 'healthchecks', 'cronitor', 'cronhub', 'pagerduty', 'ntfy', 'loki') class State(Enum): diff --git a/setup.py b/setup.py index c9c16d37..9b7e698a 100644 --- a/setup.py +++ b/setup.py @@ -35,6 +35,7 @@ setup( 'requests', 'ruamel.yaml>0.15.0,<0.18.0', 'setuptools', + 'apprise' ), include_package_data=True, python_requires='>=3.7',