Skip to content

Commit 8efd885

Browse files
authored
Merge pull request #67 from homeylab/61-notification
61 notification
2 parents de669b5 + 16c9f9a commit 8efd885

File tree

8 files changed

+79
-27
lines changed

8 files changed

+79
-27
lines changed

README.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,8 @@ notifications:
262262
storage_path: ""
263263
custom_title: ""
264264
custom_attachment_path: ""
265+
on_success: false
266+
on_failure: true
265267
```
266268

267269
#### Options and Descriptions
@@ -290,6 +292,7 @@ More descriptions can be found for each section below:
290292
| `keep_last` | `int` | `false` | Optional (default: `0`), if exporter can delete older archives. valid values are:<br>- set to `-1` if you want to delete all archives after each run (useful if you only want to upload to object storage)<br>- set to `1+` if you want to retain a certain number of archives<br>- `0` will result in no action done. |
291293
| `run_interval` | `int` | `false` | Optional (default: `0`). If specified, exporter will run as an application and pause for `{run_interval}` seconds before subsequent runs. Example: `86400` seconds = `24` hours or run once a day. Setting this property to `0` will invoke a single run and exit. Used for basic scheduling of backups. |
292294
| `minio` | `object` | `false` | Optional [Minio](#minio-backups) configuration options. |
295+
| `notifications` | `object` | `false` | Optional [notification](#notifications) configuration options. |
293296

294297
#### Valid Environment Variables
295298
General
@@ -492,10 +495,32 @@ minio:
492495
| `keep_last` | `int` | `false` | Optional (default: `0`), if exporter can delete older archives in minio.<br>- set to `1+` if you want to retain a certain number of archives<br>- `0` will result in no action done |
493496

494497
## Notifications
495-
It is possible to send notifications when an export run fails. Currently, the only supported notification service is [apprise](https://github.com/caronc/apprise). Apprise is a general purpose notification service and has a variety of integrations and includes generic HTTP POST.
498+
It is possible to send notifications when an export run succeeds orfails. Currently, the only supported notification service is [apprise](https://github.com/caronc/apprise). Apprise is a general purpose notification service and has a variety of integrations and includes generic HTTP POST.
499+
500+
Notifications are optional and the `notification` section can be omitted/removed/commented out entirely to keep a smaller configuration if not required.
501+
502+
The title for notifications is configurable but not if not specified, a default will be used. Example:
503+
```
504+
##### Failure Message #####
505+
{TITLE}: Bookstack File Exporter Failed
506+
{BODY}:
507+
Bookstack File Exporter encountered an unrecoverable error.
508+
509+
Occurred At: 2025-09-06 01:02:47
510+
511+
Error: 401 Client Error: Unauthorized for url: https://test.bookstack/api/shelve
512+
513+
514+
##### Success Message #####
515+
{TITLE}: Bookstack File Exporter Success
516+
{BODY}:
517+
Bookstack File Exporter completed successfully.
518+
519+
Completed At: 2025-09-06 01:05:27
520+
```
496521

497522
### apprise
498-
The apprise configuration is a part of the configuration yaml file and can be modified under `notifications.apprise`.
523+
The apprise configuration is a part of the configuration yaml file under the notifications section and can be modified under `notifications.apprise`.
499524

500525
| Item | Type | Description |
501526
| ---- | ---- | ----------- |
@@ -505,6 +530,8 @@ The apprise configuration is a part of the configuration yaml file and can be mo
505530
| `apprise.storage_path` | `str` | For persistent storage, specify a path for apprise to use |
506531
| `apprise.custom_title` | `str` | Replace the default message title for apprise notifications |
507532
| `apprise.custom_attachment_path` | `str` | To include a custom attachment to the apprise notification, specify the path to a file |
533+
| `apprise.on_success` | `bool` | Default: `false`, set to `true` if notifications should be sent on successful export runs |
534+
| `apprise.on_failure` | `bool` | Default: `true`, send notifications if run fails |
508535

509536
`apprise.service_urls` can contain sensitive information and can be specified as an environment variable instead as a string list, example: `export APPRISE_URLS='["json://localhost:8080/notify"]'`.
510537

bookstack_file_exporter/config_helper/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class AppRiseNotifyConfig(BaseModel):
4444
storage_path: Optional[str] = ""
4545
custom_title: Optional[str] = ""
4646
custom_attachment_path: Optional[str] = ""
47+
on_success: Optional[bool] = False
48+
on_failure: Optional[bool] = True
4749

4850
class Notifications(BaseModel):
4951
"""YAML schema for user provided notification settings"""

bookstack_file_exporter/config_helper/notifications.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515
# "storage_path": "APPRISE_STORAGE_PATH"
1616
}
1717

18-
_DEFAULT_TITLE = "Bookstack File Exporter Failed"
19-
20-
# pylint: disable=too-few-public-methods
18+
# pylint: disable=too-few-public-methods, too-many-instance-attributes
2119
class AppRiseNotifyConfig:
2220
"""
2321
Convenience class to hold apprise notification configuration
@@ -36,6 +34,8 @@ def __init__(self, config: models.AppRiseNotifyConfig):
3634
self.storage_path = config.storage_path
3735
self.custom_title = config.custom_title
3836
self.custom_attachment = config.custom_attachment_path
37+
self.on_success = config.on_success
38+
self.on_failure = config.on_failure
3939

4040
def validate(self) -> None:
4141
"""validate apprise configuration"""
@@ -56,7 +56,3 @@ def validate(self) -> None:
5656
log.Error("Failed to parse env var for apprise urls. \
5757
Ensure proper json string format")
5858
raise url_err
59-
60-
# set default custom_title if not provided
61-
if not self.custom_title:
62-
self.custom_title = _DEFAULT_TITLE

bookstack_file_exporter/notify/handler.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
from typing import Union
23

34
from bookstack_file_exporter.config_helper import models, notifications
45
from bookstack_file_exporter.notify import notifiers
@@ -31,7 +32,7 @@ def _get_targets(self, config: models.Notifications):
3132

3233
return targets
3334

34-
def do_notify(self, excep: Exception) -> None:
35+
def do_notify(self, excep: Union[None, Exception] = None) -> None:
3536
"""handle notification sending for all configured targets"""
3637
if len(self.targets) == 0:
3738
log.debug("No notification targets found")
@@ -40,9 +41,11 @@ def do_notify(self, excep: Exception) -> None:
4041
log.debug("Starting notification handling for: %s", target)
4142
self._supported_notifiers[target](config, excep)
4243

43-
4444
def _handle_apprise(self, config: models.AppRiseNotifyConfig, excep: Exception):
4545
a_config = notifications.AppRiseNotifyConfig(config)
4646
a_config.validate()
4747
apprise = notifiers.AppRiseNotify(a_config)
48-
apprise.notify(excep)
48+
# only send notification if on_success or on_failure is set
49+
if (not excep and a_config.on_success) or (excep and a_config.on_failure):
50+
log.info("Sending notification for run status")
51+
apprise.notify(excep)

bookstack_file_exporter/notify/notifiers.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
from datetime import datetime
2+
from typing import Union
23
from apprise import Apprise, AppriseAsset, AppriseConfig
34

45
from bookstack_file_exporter.config_helper import notifications
56

7+
_DEFAULT_TITLE_PREFIX = "Bookstack File Exporter "
8+
69
# pylint: disable=too-few-public-methods
710
class AppRiseNotify:
811
"""
@@ -39,28 +42,44 @@ def _create_client(self):
3942
client.asset=asset
4043
return client
4144

42-
def _get_message_body(self, error_msg: str) -> str:
45+
def _get_title(self, excep: Union[None, Exception]) -> str:
46+
if self.config.custom_title:
47+
return self.config.custom_title
48+
if excep:
49+
return _DEFAULT_TITLE_PREFIX + "Failed"
50+
return _DEFAULT_TITLE_PREFIX + "Success"
51+
52+
def _get_message_text(self, error_msg: Union[None, Exception]) -> str:
4353
timestamp = datetime.today().strftime('%Y-%m-%d %H:%M:%S')
44-
body = f"""
45-
Bookstack File Exporter encountered an unrecoverable error.
46-
47-
Occurred At: {timestamp}
48-
49-
Error message: {error_msg}
50-
"""
54+
if error_msg:
55+
error_str = str(error_msg)
56+
body = f"""
57+
Bookstack File Exporter encountered an unrecoverable error.
58+
59+
Occurred At: {timestamp}
60+
61+
Error message: {error_str}
62+
"""
63+
else:
64+
body = f"""
65+
Bookstack File Exporter completed successfully.
66+
67+
Completed At: {timestamp}
68+
"""
5169
return body
5270

5371
def notify(self, excep: Exception):
5472
"""send notification with exception message"""
55-
custom_body = self._get_message_body(str(excep))
73+
custom_body = self._get_message_text(excep)
74+
title_ = self._get_title(excep)
5675
if self.config.custom_attachment:
5776
self._client.notify(
58-
title=self.config.custom_title,
77+
title=title_,
5978
body=custom_body,
6079
attach=self.config.custom_attachment
6180
)
6281
else:
6382
self._client.notify(
64-
title=self.config.custom_title,
83+
title=title_,
6584
body=custom_body
6685
)

bookstack_file_exporter/run.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,13 @@ def run(config: ConfigNode):
2929
"""run export process with error handling and notification support"""
3030
try:
3131
exporter(config)
32+
if config.user_inputs.notifications:
33+
notif = NotifyHandler(config.user_inputs.notifications)
34+
notif.do_notify()
3235
except Exception as run_err: # general catch all for notifications
33-
log.error("Run failed: %s", str(run_err))
3436
if not config.user_inputs.notifications:
3537
raise run_err
3638
try:
37-
log.info("Sending failure notification(s)")
3839
notif = NotifyHandler(config.user_inputs.notifications)
3940
notif.do_notify(run_err)
4041
except Exception as notif_err:

examples/config.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,6 @@ notifications:
8888
plugin_paths: []
8989
storage_path: ""
9090
custom_title: ""
91-
custom_attachment_path: ""
91+
custom_attachment_path: ""
92+
on_success: false
93+
on_failure: true

examples/minio_config.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,6 @@ notifications:
112112
plugin_paths: []
113113
storage_path: ""
114114
custom_title: ""
115-
custom_attachment_path: ""
115+
custom_attachment_path: ""
116+
on_success: false
117+
on_failure: true

0 commit comments

Comments
 (0)