diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml
index 847ca6f5..09b6847b 100644
--- a/borgmatic/config/schema.yaml
+++ b/borgmatic/config/schema.yaml
@@ -1602,6 +1602,88 @@ properties:
example:
- start
- finish
+ zabbix:
+ type: object
+ additionalProperties: false
+ properties:
+ itemid:
+ type: integer
+ description: |
+ The itemid to publish to
+ for details.
+ example: 55105
+ host:
+ type: string
+ description: |
+ Host name where the item is stored.
+ Required if 'itemid' is not set
+ example: borg-server
+ key:
+ type: string
+ description: |
+ Key of the host where the item is stored.
+ Required if 'itemid' is not set
+ example: borg.status
+ server:
+ type: string
+ description: |
+ The address of your self-hosted zabbix instance.
+ example: https://zabbix.your-domain.com
+ username:
+ type: string
+ description: |
+ The username used for authentication.
+ example: testuser
+ password:
+ type: string
+ description: |
+ The password used for authentication.
+ example: fakepassword
+ api_key:
+ type: string
+ description: |
+ The api_key used for authentication.
+ example: fakekey
+ start:
+ type: object
+ properties:
+ value:
+ type: string
+ description: |
+ The string to set the item value to on start.
+ example: STARTED
+ finish:
+ type: object
+ properties:
+ value:
+ type: string
+ description: |
+ The string to set the item value to on finish.
+ example: FINISH
+ fail:
+ type: object
+ properties:
+ value:
+ type: string
+ description: |
+ The string to set the item value to on fail.
+ example: ERROR
+ 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
apprise:
type: object
required: ['services']
diff --git a/borgmatic/hooks/dispatch.py b/borgmatic/hooks/dispatch.py
index 6131ef6b..be85af8c 100644
--- a/borgmatic/hooks/dispatch.py
+++ b/borgmatic/hooks/dispatch.py
@@ -14,6 +14,7 @@ from borgmatic.hooks import (
postgresql,
sqlite,
uptimekuma,
+ zabbix,
)
logger = logging.getLogger(__name__)
@@ -32,6 +33,7 @@ HOOK_NAME_TO_MODULE = {
'postgresql_databases': postgresql,
'sqlite_databases': sqlite,
'uptime_kuma': uptimekuma,
+ 'zabbix': zabbix,
}
diff --git a/borgmatic/hooks/monitor.py b/borgmatic/hooks/monitor.py
index 9512fe99..de4951b7 100644
--- a/borgmatic/hooks/monitor.py
+++ b/borgmatic/hooks/monitor.py
@@ -9,6 +9,7 @@ MONITOR_HOOK_NAMES = (
'ntfy',
'pagerduty',
'uptime_kuma',
+ 'zabbix',
)
diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py
new file mode 100644
index 00000000..ea17d7a0
--- /dev/null
+++ b/borgmatic/hooks/zabbix.py
@@ -0,0 +1,93 @@
+import logging
+import requests
+import json
+
+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 Zabbix topic. 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() in run_states:
+ dry_run_label = ' (dry run; not actually pinging)' if dry_run else ''
+
+ state_config = hook_config.get(state.name.lower(),{'value': f'invalid',},)
+
+ base_url = hook_config.get('server', 'https://cloud.zabbix.com/zabbix/api_jsonrpc.php')
+ username = hook_config.get('username')
+ password = hook_config.get('password')
+ api_key = hook_config.get('api_key')
+ itemid = hook_config.get('itemid')
+ host = hook_config.get('host')
+ key = hook_config.get('key')
+
+ value = state_config.get('value')
+
+ headers = {'Content-Type': 'application/json-rpc'}
+
+ logger.info(f'{config_filename}: Pinging zabbix {dry_run_label}')
+ logger.debug(f'{config_filename}: Using zabbix ping URL {base_url}')
+
+ # Determine the zabbix method used to store the value: itemid or host/key
+ if (itemid) is not None:
+ logger.info(f'{config_filename}: Updating {itemid} on zabbix')
+ data = {"jsonrpc":"2.0","method":"history.push","params":{"itemid":itemid,"value":value},"id":1}
+
+ elif (host and key) is not None:
+ logger.info(f'{config_filename}: Updating Host:{host} and Key:{key} on zabbix')
+ data = {"jsonrpc":"2.0","method":"history.push","params":{"host":host,"key":key,"value":value},"id":1}
+
+ elif (host) is not None:
+ logger.warning( f'{config_filename}: Key missing for zabbix authentication' )
+
+ elif (key) is not None:
+ logger.warning( f'{config_filename}: Host missing for zabbix authentication' )
+
+ # Determine the authentication method: API key or username/password
+ auth = None
+ if (api_key) is not None:
+ logger.info(f'{config_filename}: Using API key auth for zabbix')
+ headers['Authorization'] = 'Bearer ' + api_key
+
+ elif (username and password) is not None:
+ logger.info(f'{config_filename}: Using user/pass auth with user {username} for zabbix')
+ response = requests.post(base_url, headers=headers, data='{"jsonrpc":"2.0","method":"user.login","params":{"username":"'+username+'","password":"'+password+'"},"id":1}')
+ data['auth'] = response.json().get('result')
+
+ elif username is not None:
+ logger.warning( f'{config_filename}: Password missing for zabbix authentication, defaulting to no auth' )
+
+ elif password is not None:
+ logger.warning( f'{config_filename}: Username missing for zabbix authentication, defaulting to no auth' )
+
+ if not dry_run:
+ logging.getLogger('urllib3').setLevel(logging.ERROR)
+ try:
+ response = requests.post(base_url, headers=headers, data=json.dumps(data))
+ if not response.ok:
+ response.raise_for_status()
+ except requests.exceptions.RequestException as error:
+ logger.warning(f'{config_filename}: zabbix error: {error}')
+
+
+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/docs/how-to/monitor-your-backups.md b/docs/how-to/monitor-your-backups.md
index e96c4040..ddc540b9 100644
--- a/docs/how-to/monitor-your-backups.md
+++ b/docs/how-to/monitor-your-backups.md
@@ -47,6 +47,7 @@ them as backups happen:
* [ntfy](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#ntfy-hook)
* [PagerDuty](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#pagerduty-hook)
* [Uptime Kuma](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#uptime-kuma-hook)
+ * [Zabbix](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#zabbix-hook)
The idea is that you'll receive an alert when something goes wrong or when the
service doesn't hear from borgmatic for a configured interval (if supported).
@@ -562,6 +563,55 @@ Heartbeat Retry = 360 # = 10 minutes
Resend Notification every X times = 1
```
+## zabbix hook
+
+New in version 1.8.15
+[zabbix](https://www.zabbix.com/) is an open-source monitoring tool used for tracking and managing the performance and availability of networks, servers, and applications in real-time.
+
+This hook does not do any notifications on it's own. Instead, it relies on
+your zabbix instance to notify and perform escalations based on the zabbix
+configuration. The `states` list will choose which states to trigger on.
+Each state can have its own custom values configured. These values are
+populated in the item data in zabbix. If none are provided, will use the
+default.
+
+An example configuration is shown here with all the available options.
+
+```yaml
+zabbix:
+ server: http://cloud.zabbix.com/zabbix/api_jsonrpc.php
+
+ username: myuser
+ password: secret
+ api_key: b2ecba64d8beb47fc161ae48b164cfd7104a79e8e48e6074ef5b141d8a0aeeca
+
+ host: "borg-server"
+ key: borg.status
+ itemid: 55105
+
+ start:
+ value: "STARTED"
+ finish:
+ value: "OK"
+ fail:
+ value: "ERROR"
+ states:
+ - start
+ - finish
+ - fail
+```
+
+Authentication Methods
+Authentication can be accomplished via `api_key` or `username` and `password`.
+If both are declared, `api_key` will be chosen.
+
+Items The item
+to be updated can be chosen by either declaring the `itemid` or
+`host` and `key`. If both are declared, `itemid` will be chosen.
+
+Keep in mind that `host` the Host name on the host and not the
+Visual name.
+
## Scripting borgmatic