diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..e2ac09c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,23 @@ +version: 2 +updates: + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "daily" + labels: + - "🧩 dependencies" + - "🤖 bot" + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + labels: + - "🧩 dependencies" + - "🤖 bot" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + labels: + - "🧩 dependencies" + - "🤖 bot" \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f42e9b8..1a19532 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -15,7 +15,6 @@ on: push: branches: [ main ] pull_request: - # The branches below must be a subset of the branches above branches: [ main ] schedule: - cron: '15 18 * * 4' diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 3398186..02296cd 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -3,12 +3,23 @@ name: Docker on: push: branches: [ main ] - tags: [ v* ] + pull_request: + branches: [ main ] env: IMAGE_NAME: qnap-pushover jobs: + build: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + + steps: + - uses: actions/checkout@v2 + + - name: Build image + run: docker build . --file Dockerfile --tag $IMAGE_NAME + build-and-push: runs-on: ubuntu-latest if: github.event_name == 'push' @@ -42,4 +53,4 @@ jobs: echo VERSION=$VERSION docker tag $IMAGE_NAME $IMAGE_ID:$VERSION - docker push $IMAGE_ID:$VERSION + docker push $IMAGE_ID:$VERSION \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 01a9d69..88edbcb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:alpine +FROM python:3.9.1-alpine3.12 COPY requirements.txt / RUN pip install -r requirements.txt COPY main.py / diff --git a/LICENSE b/LICENSE index 5e205b8..f620ab0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,8 @@ MIT License -Copyright (c) 2018 Vincent Cox +Copyright © 2021 TheCatLady +Copyright © 2019 M. T. Lott +Copyright © 2018 Vincent Cox Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 852d47d..5f40ca9 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ # `qnap-pushover` 🔔 -[![Python](https://img.shields.io/github/languages/top/TheCatLady/docker-qnap-pushover?logo=python&style=flat-square)](https://github.com/TheCatLady/docker-qnap-pushover) -[![Docker Image Size](https://img.shields.io/docker/image-size/thecatlady/qnap-pushover/latest?style=flat-square&logo=docker&label=docker%20image%20size)](https://hub.docker.com/r/thecatlady/qnap-pushover) -[![Docker Hub Pulls](https://img.shields.io/docker/pulls/thecatlady/qnap-pushover?style=flat-square&label=docker%20hub%20pulls&logo=docker)](https://hub.docker.com/r/thecatlady/qnap-pushover) -[![GitHub Repo Stars](https://img.shields.io/github/stars/TheCatLady/docker-qnap-pushover?label=github%20stars&logo=github&style=flat-square)](https://github.com/TheCatLady/docker-qnap-pushover/stargazers)
-[![Last Commit](https://img.shields.io/github/last-commit/TheCatLady/docker-qnap-pushover?style=flat-square&logo=git)](https://github.com/TheCatLady/docker-qnap-pushover) -[![GitHub Container Registry Build Status](https://img.shields.io/github/workflow/status/TheCatLady/docker-qnap-pushover/Docker?label=github%20container%20registry%20build&logo=github&style=flat-square)](https://github.com/TheCatLady/docker-qnap-pushover) -[![Docker Hub Build Status](https://img.shields.io/docker/cloud/build/thecatlady/qnap-pushover?style=flat-square&label=docker%20hub%20build&logo=docker)](https://hub.docker.com/r/thecatlady/qnap-pushover)
-[![License](https://img.shields.io/github/license/TheCatLady/docker-qnap-pushover?logo=open%20source%20initiative&style=flat-square)](https://github.com/TheCatLady/docker-qnap-pushover/blob/main/LICENSE) +[![Python](https://img.shields.io/github/languages/top/TheCatLady/docker-qnap-pushover?style=flat-square&logoColor=white&logo=python)](https://github.com/TheCatLady/docker-qnap-pushover) +[![Code Quality](https://img.shields.io/lgtm/grade/python/github/TheCatLady/docker-qnap-pushover?style=flat-square&logoColor=white&logo=lgtm&label=code%20quality)](https://lgtm.com/projects/g/TheCatLady/docker-qnap-pushover/) +[![Image Size](https://img.shields.io/docker/image-size/thecatlady/qnap-pushover/latest?style=flat-square&logoColor=white&logo=docker&label=image%20size)](https://hub.docker.com/r/thecatlady/qnap-pushover) +[![Docker Hub Pulls](https://img.shields.io/docker/pulls/thecatlady/qnap-pushover?style=flat-square&logoColor=white&logo=docker&label=docker%20hub%20pulls)](https://hub.docker.com/r/thecatlady/qnap-pushover)
+[![Last Commit](https://img.shields.io/github/last-commit/TheCatLady/docker-qnap-pushover?style=flat-square&logoColor=white&logo=git)](https://github.com/TheCatLady/docker-qnap-pushover) +[![GitHub Container Registry Build Status](https://img.shields.io/github/workflow/status/TheCatLady/docker-qnap-pushover/Docker?style=flat-square&logoColor=white&logo=github&label=github%20container%20registry%20build)](https://github.com/TheCatLady/docker-qnap-pushover) +[![Docker Hub Build Status](https://img.shields.io/docker/cloud/build/thecatlady/qnap-pushover?style=flat-square&logoColor=white&logo=docker&label=docker%20hub%20build)](https://hub.docker.com/r/thecatlady/qnap-pushover)
+[![License](https://img.shields.io/github/license/TheCatLady/docker-qnap-pushover?style=flat-square&logoColor=white&logo=open%20source%20initiative)](https://github.com/TheCatLady/docker-qnap-pushover/blob/main/LICENSE) [![Become a GitHub Sponsor](https://img.shields.io/badge/github%20sponsors-become%20a%20sponsor-ff69b4?style=flat-square&logo=github%20sponsors)](https://github.com/sponsors/TheCatLady) [![Donate via PayPal](https://img.shields.io/badge/paypal-make%20a%20donation-blue?style=flat-square&logo=paypal)](http://paypal.me/DHoung) -[Pushover](https://pushover.net/) notifications for [QNAP](https://www.qnap.com/) NAS system events via `python-pushover` +[Pushover](https://pushover.net/) notifications for [QNAP](https://www.qnap.com/) NAS system events via [`python-pushover`](https://github.com/Thibauth/python-pushover) ## Usage @@ -21,7 +21,7 @@ If you would prefer to pull from GHCR, simply replace `thecatlady/qnap-pushover` ### Docker Compose (recommended) -Add the following volume and service definitions to a `docker-compose.yaml` file: +Add the following volume and service definitions to a `docker-compose.yml` file: ```yaml volumes: @@ -46,20 +46,12 @@ services: restart: always ``` -Then, run the following command from the directory containing your `docker-compose.yaml` file: +Then, run the following command from the directory containing your `docker-compose.yml` file: ```bash docker-compose up -d ``` -To update the container when a new image is available, run the following: - -```bash -docker-compose pull qnap-pushover -docker-compose up -d -docker image prune -``` - ### Docker CLI Run the following command to create the required named volume: @@ -88,7 +80,23 @@ docker run -d \ thecatlady/qnap-pushover ``` -To update the container when a new image is available, run the commands below followed by your `docker run` command: +## Updating + +The process to update the container when a new image is available is dependent on how you set it up initially. + +### Docker Compose + +Run the following commands from the directory containing your `docker-compose.yml` file: + +```bash +docker-compose pull qnap-pushover +docker-compose up -d +docker image prune +``` + +### Docker CLI + +Run the commands below, followed by your original `docker run` command: ```bash docker stop qnap-pushover @@ -107,8 +115,8 @@ The container image is configured using the following parameters passed at runti |`-e LOG_LEVEL=`|Container logging level; `DEBUG` < `INFO` < `WARN` < `ERROR` < `CRITICAL`|`WARN`|no| |`-e NOTIFY_LEVEL=`|Minimum system event type to generate a notification; `INFO` < `WARN` < `ERROR`|`WARN`|no| |`-e POLL_INTERVAL=`|Poll interval in seconds|`10`|no| -|`-e INCLUDE=`|List of keywords which must be present in the event description to trigger a notification (comma-delimited)||no| -|`-e EXCLUDE=`|List of keywords which must _not_ be present in the event description to trigger a notification (comma-delimited)||no| +|`-e INCLUDE=`|List of keywords which _must_ be present in the event description to trigger a notification (comma-delimited)||no| +|`-e EXCLUDE=`|List of keywords which _must not_ be present in the event description to trigger a notification (comma-delimited)||no| |`-e TESTING_MODE=`|Testing mode (`true` or `false`); if set to `true`, will re-queue the last 10 system log events at _every_ container start and result in duplicate notifications|`false`|no| |`-e PUSHOVER_TOKEN=`|[Pushover application API token](https://pushover.net/api#registration); e.g., `azGDORePK8gMaC0QOYAMyEEuzJnyUi`||**yes**| |`-e PUSHOVER_RECIPIENT=`|[Pushover user and/or group key(s)](https://pushover.net/api#identifiers); e.g., `uQiRzpo4DXghDmr9QzzfQu27cmVRsG` or `gznej3rKEVAvPUxu9vvNnqpmZpokzF` (up to 50, comma-delimited)||**yes**| @@ -118,31 +126,16 @@ The container image is configured using the following parameters passed at runti ## Building Locally -If you would like to make modifications to the code, you can build the Docker image yourself instead of pulling the pre-built image available from [GitHub Container Registry (GHCR)](https://github.com/users/TheCatLady/packages/container/package/qnap-pushover) or [Docker Hub](https://hub.docker.com/r/thecatlady/qnap-pushover). +If you would like to make modifications to the code, you can build the Docker image yourself instead of pulling the pre-built image available from [GitHub Container Registry (GHCR)](https://github.com/users/TheCatLady/packages/container/package/qnap-pushover) and [Docker Hub](https://hub.docker.com/r/thecatlady/qnap-pushover). ```bash git clone https://github.com/TheCatLady/docker-qnap-pushover.git cd docker-qnap-pushover -docker build \ - --no-cache \ - --pull \ - -t thecatlady/qnap-pushover . -docker volume create qnap-pushover -docker run -d \ - --name=qnap-pushover \ - -e TZ=America/New_York `#optional` \ - -e LOG_LEVEL=WARN `#optional` \ - -e NOTIFY_LEVEL=WARN `#optional` \ - -e POLL_INTERVAL=10 `#optional` \ - -e TESTING_MODE=false `#optional` \ - -e PUSHOVER_TOKEN= \ - -e PUSHOVER_RECIPIENT= \ - -v qnap-pushover:/data \ - -v /etc/logs/event.log:/event.log:ro \ - --restart always \ - thecatlady/qnap-pushover +docker build --no-cache --pull -t thecatlady/qnap-pushover . ``` +Once the image has been built, follow the directions in the "Usage" section above to start the container. + ## How to Contribute Show your support by starring this project! 🌟 Pull requests, bug reports, and feature requests are also welcome! diff --git a/main.py b/main.py index 9ae9eb5..418ec37 100644 --- a/main.py +++ b/main.py @@ -6,17 +6,18 @@ import sqlite3 import time -EVENT_ID_FILE = "last_event_id.txt" -DB_FILE = "event.log" -DB_NAME = "NASLOG_EVENT" -ID_COL = "event_id" -TYPE_COL = "event_type" -DESC_COL = "event_desc" -DATE_COL = "event_date" -TIME_COL = "event_time" -USER_COL = "event_user" -IP_COL = "event_ip" -COLOR = ["grey", "#ffc311", "#ca414b"] +EVENT_LOG_PATH = "/event.log" +EVENT_ID_PATH = "/data/last_event_id.txt" +FONT_COLOR = ["grey", "#ffc311", "#ca414b"] + +LOGGING_LEVEL = { + 'critical': logging.CRITICAL, + 'error': logging.ERROR, + 'warn': logging.WARNING, + 'warning': logging.WARNING, + 'info': logging.INFO, + 'debug': logging.DEBUG +} # Banner print(''' @@ -27,16 +28,7 @@ https://github.com/TheCatLady/docker-qnap-pushover ''') -LOGGING_LEVELS = { - 'critical': logging.CRITICAL, - 'error': logging.ERROR, - 'warn': logging.WARNING, - 'warning': logging.WARNING, - 'info': logging.INFO, - 'debug': logging.DEBUG -} - -logging_level = LOGGING_LEVELS.get(os.environ['LOG_LEVEL'].lower()) +logging_level = LOGGING_LEVEL.get(os.environ['LOG_LEVEL'].lower()) if logging_level is None: logging_level = logging.WARNING @@ -50,7 +42,7 @@ NOTIFY_TYPE = 1 else: NOTIFY_TYPE = 2 -except: +except Exception: NOTIFY_TYPE = 1 finally: if NOTIFY_TYPE == 0: @@ -65,14 +57,14 @@ try: POLL_INTERVAL = int(os.environ['POLL_INTERVAL']) -except: +except Exception: POLL_INTERVAL = 10 finally: logging.info(f"Poll interval is set to {POLL_INTERVAL} seconds.") try: INCLUDE = list(filter(str.strip, os.environ['INCLUDE'].lower().split(','))) -except: +except Exception: INCLUDE = [] finally: if len(INCLUDE) > 0: @@ -82,7 +74,7 @@ try: EXCLUDE = list(filter(str.strip, os.environ['EXCLUDE'].lower().split(','))) -except: +except Exception: EXCLUDE = [] finally: if len(EXCLUDE) > 0: @@ -92,7 +84,7 @@ try: TESTING_MODE = bool(os.getenv('TESTING_MODE', 'false').lower().strip() in ['true', '1']) -except: +except Exception: TESTING_MODE = False finally: if TESTING_MODE: @@ -106,14 +98,11 @@ pushover_client = pushover.Client(PUSHOVER_RECIPIENT, api_token=PUSHOVER_TOKEN) logging.info(f"Using Pushover application API token {PUSHOVER_TOKEN} and recipient user/group key(s) {PUSHOVER_RECIPIENT}.") - log_path = os.path.abspath(DB_FILE) - event_id_path = os.path.abspath(os.path.join("data", EVENT_ID_FILE)) - - if os.path.isfile(log_path): - db_conn = sqlite3.connect(log_path) # event.log is not a text file, but a SQLite database + if os.path.isfile(EVENT_LOG_PATH): + db_conn = sqlite3.connect(EVENT_LOG_PATH) # event.log is not a text file, but a SQLite database try: - with open(event_id_path, "r") as f: + with open(EVENT_ID_PATH, "r") as f: last_event_id = int(f.readline()) # read the last-processed event ID from a file if it exists logging.info(f"Read the last-processed event ID from the data file. ({last_event_id})") except (FileNotFoundError, ValueError): @@ -121,7 +110,7 @@ if last_event_id == 0: # set the last-processed event ID to the newest event ID if we weren't able to read it from a file cursor = db_conn.cursor() - cursor.execute(f"SELECT {ID_COL} FROM {DB_NAME} ORDER BY {ID_COL} DESC LIMIT 1;") + cursor.execute(f"SELECT event_id FROM NASLOG_EVENT ORDER BY event_id DESC LIMIT 1;") last_event_id = cursor.fetchone()[0] logging.info(f"Setting the last-processed event ID to the newest event ID in the database. ({last_event_id})") @@ -129,21 +118,21 @@ last_event_id -= 10 # for testing, re-queue the previous 10 events logging.info("Testing mode is enabled. Re-queuing last 10 system log events for processing.") else: - raise Exception(f"Unable to open {DB_FILE}. Was the log file mounted to the container?") + raise Exception(f"Unable to open {EVENT_LOG_PATH}. Was the log file mounted to the container?") while True: cursor = db_conn.cursor() - cursor.execute(f"SELECT {ID_COL} FROM {DB_NAME} ORDER BY {ID_COL} DESC LIMIT 1;") + cursor.execute(f"SELECT event_id FROM NASLOG_EVENT ORDER BY event_id DESC LIMIT 1;") newest_event_id = cursor.fetchone()[0] if newest_event_id > last_event_id: new_event_count = newest_event_id - last_event_id - logging.info(f"{new_event_count} new system log events detected in {DB_FILE}.") + logging.info(f"{new_event_count} new system log events detected in {EVENT_LOG_PATH}.") try: for i in range(new_event_count): cursor = db_conn.cursor() - cursor.execute(f"SELECT {TYPE_COL}, {DESC_COL}, {DATE_COL}, {TIME_COL}, {USER_COL}, {IP_COL} FROM {DB_NAME} WHERE {ID_COL}={last_event_id + 1 + i};") + cursor.execute(f"SELECT event_type, event_desc, event_date, event_time, event_user, event_ip FROM NASLOG_EVENT WHERE event_id={last_event_id + 1 + i};") event = cursor.fetchone() event_type = event[0] event_desc = event[1].strip() @@ -190,7 +179,7 @@ if quotes % 2 == 0: if first_line: - message = f"{prev}{s}"; + message = f"{prev}{s}"; first_line = False else: message += f"
{prev}{s}" @@ -200,7 +189,7 @@ else: prev += f"{s}. " else: - message = f"{message}" + message = f"{message}" message_details = "" @@ -242,9 +231,9 @@ f"Only {i} of {new_event_count} new system log events were processed successfully. " \ f"Re-queuing the remaining {new_event_count - i} unprocessed events.") else: - logging.info(f"No new system log events detected in {DB_FILE}.") + logging.info(f"No new system log events detected in {EVENT_LOG_PATH}.") - with open(event_id_path, "w+") as f: + with open(EVENT_ID_PATH, "w+") as f: f.write(str(last_event_id)) f.truncate() logging.info(f"Wrote the last-processed event ID to the data file. ({last_event_id})") diff --git a/requirements.txt b/requirements.txt index c10e958..8105703 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,6 @@ -python-pushover +certifi==2020.12.5 +chardet==4.0.0 +idna==2.10 +python-pushover==0.4 +requests==2.25.1 +urllib3==1.26.2 \ No newline at end of file