mirror of
https://codeberg.org/privacy1st/exec-notify
synced 2024-12-22 23:16:04 +01:00
v0.1.5
add usage and improve doc; lazy config loading;
This commit is contained in:
parent
9eafc7bb77
commit
19d4188205
24
README.md
24
README.md
@ -53,9 +53,26 @@ sudo python3 -m pip install --upgrade --index-url https://test.pypi.org/simple/
|
||||
|
||||
## Configuration
|
||||
|
||||
Create configuration file `/etc/execNotify/cfg.ini`.
|
||||
Create configuration file `/etc/execNotify/cfg.ini`:
|
||||
|
||||
For the required fields, see [./etc/execNotify/cfg.ini.example](etc/execNotify/cfg.ini.example)
|
||||
```shell
|
||||
install -Dm0600 /etc/execNotify/cfg.ini << 'EOF'
|
||||
[DEFAULT]
|
||||
|
||||
[mail]
|
||||
host = smtp.example.com
|
||||
port = 465
|
||||
password = examplePassword
|
||||
|
||||
from = noreply@example.com
|
||||
to = me@example.com
|
||||
|
||||
[file]
|
||||
maildir = /home/exampleUser/mail/
|
||||
EOF
|
||||
```
|
||||
|
||||
See also: [./etc/execNotify/cfg.ini.example](etc/execNotify/cfg.ini.example)
|
||||
|
||||
|
||||
## Usage
|
||||
@ -64,9 +81,6 @@ For the required fields, see [./etc/execNotify/cfg.ini.example](etc/execNotify/c
|
||||
|
||||
Add `de-p1st-execNotify` in front of your command to execute.
|
||||
|
||||
You can pipe into `de-p1st-execNotify` but **not** from `de-p1st-execNotify`
|
||||
as the output gets modified.
|
||||
|
||||
**Example:**
|
||||
|
||||
`de-p1st-execNotify ls /non/existent/file` will mail you the exit code, stdout and stderr of `ls /non/existent/file`
|
||||
|
@ -3,10 +3,10 @@
|
||||
|
||||
[metadata]
|
||||
name = de.p1st.exec_notify
|
||||
version = 0.1.4
|
||||
version = 0.1.5
|
||||
author = Daniel Langbein
|
||||
author_email = daniel@systemli.org
|
||||
description = Send mail with process output
|
||||
description = mail notification if command fails
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
url = https://codeberg.org/privacy1st/execNotify
|
||||
|
@ -8,6 +8,15 @@ from de.p1st.exec_notify.lib import exec, config, mail, util
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Usage:
|
||||
de-p1st-execNotify <command>
|
||||
|
||||
with command = <executable> [<argument> ...]
|
||||
|
||||
:return: exit code of executed command
|
||||
"""
|
||||
|
||||
if len(argv) <= 1:
|
||||
print('No command given to execute!', file=sys.stderr)
|
||||
exit(1)
|
||||
@ -15,7 +24,7 @@ def main():
|
||||
exit(executeCommand(argv[1:]))
|
||||
|
||||
|
||||
def executeCommand(command: List) -> int:
|
||||
def executeCommand(command: List[str]) -> int:
|
||||
"""
|
||||
Executes the given command and sends an email on failure.
|
||||
|
||||
|
@ -1,60 +1,80 @@
|
||||
import sys
|
||||
import traceback
|
||||
from sys import stderr
|
||||
from pathlib import Path, PosixPath
|
||||
import configparser
|
||||
from typing import Tuple
|
||||
|
||||
from de.p1st.exec_notify.lib import util
|
||||
|
||||
|
||||
def getHostAndPort():
|
||||
class LazyConfig:
|
||||
# (static) class variable
|
||||
_config: configparser.ConfigParser = None
|
||||
|
||||
@staticmethod
|
||||
def get(section: str, key: str):
|
||||
if LazyConfig._config is None:
|
||||
LazyConfig._config = configparser.ConfigParser()
|
||||
LazyConfig._config.read(_getCfgFile())
|
||||
return LazyConfig._config[section][key]
|
||||
|
||||
|
||||
def getHostAndPort() -> Tuple[str, str]:
|
||||
try:
|
||||
return config['mail']['host'], config['mail']['port']
|
||||
return LazyConfig.get('mail', 'host'), LazyConfig.get('mail', 'port')
|
||||
except Exception as e:
|
||||
print(f'execNotify>> Could not read value [mail][host] from {_getCfgFile()}', file=sys.stderr)
|
||||
print(f'execNotify>> Could not read value [mail][host] from {_getCfgFile()}', file=stderr)
|
||||
traceback.print_exc()
|
||||
exit(1)
|
||||
|
||||
|
||||
def getPassword():
|
||||
def getPassword() -> str:
|
||||
try:
|
||||
return config['mail']['password']
|
||||
return LazyConfig.get('mail', 'password')
|
||||
except Exception as e:
|
||||
print(f'execNotify>> Could not read value [mail][password] from {_getCfgFile()}', file=sys.stderr)
|
||||
print(f'execNotify>> Could not read value [mail][password] from {_getCfgFile()}', file=stderr)
|
||||
traceback.print_exc()
|
||||
exit(1)
|
||||
|
||||
|
||||
def getFrom():
|
||||
def getFrom() -> str:
|
||||
try:
|
||||
return config['mail']['from'] # used for mail login as well
|
||||
return LazyConfig.get('mail', 'from') # used for mail login as well
|
||||
except Exception as e:
|
||||
print(f'execNotify>> Could not read value [mail][from] from {_getCfgFile()}', file=sys.stderr)
|
||||
print(f'execNotify>> Could not read value [mail][from] from {_getCfgFile()}', file=stderr)
|
||||
traceback.print_exc()
|
||||
exit(1)
|
||||
|
||||
|
||||
def getTo():
|
||||
def getTo() -> str:
|
||||
try:
|
||||
return config['mail']['to']
|
||||
return LazyConfig.get('mail', 'to')
|
||||
except Exception as e:
|
||||
print(f'execNotify>> Could not read value [mail][to] from {_getCfgFile()}', file=sys.stderr)
|
||||
print(f'execNotify>> Could not read value [mail][to] from {_getCfgFile()}', file=stderr)
|
||||
traceback.print_exc()
|
||||
exit(1)
|
||||
|
||||
|
||||
def getMailDir() -> Path:
|
||||
try:
|
||||
return Path(config['file']['maildir'])
|
||||
return Path(LazyConfig.get('file', 'maildir'))
|
||||
except Exception as e:
|
||||
print(f'execNotify>> Could not read value [file][maildir] from {_getCfgFile()}', file=sys.stderr)
|
||||
print(f'execNotify>> Could not read value [file][maildir] from {_getCfgFile()}', file=stderr)
|
||||
traceback.print_exc()
|
||||
exit(1)
|
||||
|
||||
|
||||
#
|
||||
# Helper Methods
|
||||
#
|
||||
|
||||
|
||||
def _getCfgFile() -> Path:
|
||||
return _getCfgDir().joinpath('cfg.ini')
|
||||
|
||||
|
||||
def _getCfgDir() -> Path:
|
||||
if util.isInDevelopment():
|
||||
return util.getProjectBase().joinpath('etc','execNotify')
|
||||
return util.getProjectBase().joinpath('etc', 'execNotify')
|
||||
else:
|
||||
return PosixPath('/etc/execNotify/')
|
||||
|
||||
|
||||
config: configparser.ConfigParser = configparser.ConfigParser()
|
||||
config.read(_getCfgFile())
|
||||
|
@ -1,12 +1,12 @@
|
||||
from typing import List
|
||||
import sys
|
||||
from sys import stdin
|
||||
import subprocess
|
||||
|
||||
|
||||
def execute(command: List[str]):
|
||||
"""
|
||||
Run the given command in a subprocess and pass stdin (if given) to that process.
|
||||
Wait for command to complete.
|
||||
Runs the given command in a subprocess and passes stdin (if given) to that process.
|
||||
Waits for command to complete.
|
||||
|
||||
:param command: A command to executed as list of words, e.g. ['echo', 'Hello world!']
|
||||
:return: (exit_code, stdout, stderr)
|
||||
@ -14,7 +14,7 @@ def execute(command: List[str]):
|
||||
|
||||
completed: subprocess.CompletedProcess = subprocess.run(
|
||||
command,
|
||||
stdin=sys.stdin,
|
||||
stdin=stdin,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
text=True
|
||||
|
@ -1,4 +1,4 @@
|
||||
import sys
|
||||
from sys import stderr
|
||||
import datetime
|
||||
import smtplib, ssl
|
||||
import socket
|
||||
@ -10,9 +10,9 @@ from de.p1st.exec_notify.lib import config, util
|
||||
|
||||
|
||||
def sendMailOrWriteToFile(SUBJECT: str, BODY: str, informAboutLocalMail: bool = True):
|
||||
mailDir = config.getMailDir()
|
||||
|
||||
if informAboutLocalMail and _localMailExists():
|
||||
mailDir = config.getMailDir()
|
||||
|
||||
SUBJECT=f'{SUBJECT} | UNREAD LOCAL MAIL'
|
||||
BODY=f'[!] Note [!]\n' \
|
||||
f'There is local mail inside {mailDir} that was not delivered previously! ' \
|
||||
@ -22,8 +22,8 @@ def sendMailOrWriteToFile(SUBJECT: str, BODY: str, informAboutLocalMail: bool =
|
||||
try:
|
||||
sendMail(SUBJECT=SUBJECT, BODY=BODY)
|
||||
except Exception as e:
|
||||
print(f'execNotify>> Could not send mail: {e}', file=sys.stderr)
|
||||
print(f'execNotify>> Writing to file instead ...', file=sys.stderr)
|
||||
print(f'execNotify>> Could not send mail: {e}', file=stderr)
|
||||
print(f'execNotify>> Writing to file instead ...', file=stderr)
|
||||
|
||||
# Instead, try to save the mail so that the user can read
|
||||
# it later when connected to this computer
|
||||
@ -32,7 +32,7 @@ def sendMailOrWriteToFile(SUBJECT: str, BODY: str, informAboutLocalMail: bool =
|
||||
|
||||
def sendMail(SUBJECT: str, BODY: str):
|
||||
"""
|
||||
:throws Exception: If mail could not be sent
|
||||
:raises Exception: If mail could not be sent
|
||||
"""
|
||||
|
||||
FROM = config.getFrom()
|
||||
@ -92,8 +92,10 @@ def _localMailExists():
|
||||
"""
|
||||
:return: True if local mail exists in maildir folder. Once the mail is read the user shall delete (or move) it.
|
||||
"""
|
||||
|
||||
mailDir = config.getMailDir()
|
||||
if not mailDir.exists():
|
||||
return False
|
||||
else:
|
||||
# mailDir has at least one child (file/folder)
|
||||
return len(list(mailDir.iterdir())) > 0
|
||||
|
@ -6,7 +6,7 @@ def isInDevelopment() -> bool:
|
||||
"""
|
||||
Helpful for local development where this python module is not installed.
|
||||
|
||||
:return: True if environment variable DE_P1ST_EXEC_NOTIFY is set.
|
||||
:returns: True if environment variable DE_P1ST_EXEC_NOTIFY is set.
|
||||
"""
|
||||
return 'DE_P1ST_EXEC_NOTIFY' in os.environ
|
||||
|
||||
@ -18,13 +18,19 @@ def getProjectBase() -> Path:
|
||||
def readFirstLine(file: Path) -> str:
|
||||
"""
|
||||
:param file: Path to file
|
||||
:return: first line of file
|
||||
:returns: first line of file
|
||||
"""
|
||||
with open(file, "r") as f:
|
||||
return f.readline()
|
||||
|
||||
|
||||
def appendLinePrefix(prefix: str, s: str) -> str:
|
||||
"""
|
||||
Append `prefix` to the left of each line in `s`
|
||||
|
||||
:returns: modified `s`
|
||||
"""
|
||||
|
||||
if s is None or len(s) == 0:
|
||||
return prefix
|
||||
result = ''.join([prefix + line for line in s.splitlines(keepends=True)])
|
||||
|
@ -7,10 +7,16 @@ from de.p1st.exec_notify.lib import exec, config, mail
|
||||
|
||||
def main():
|
||||
"""
|
||||
echo <body> | ./notify
|
||||
echo <body> | ./notify <subject>
|
||||
./notify <subject> <body>
|
||||
./notify <subject> <body_word_1> <body_word_2> ... <body_word_n>
|
||||
Send an email with some content and optionally specify an email subject.
|
||||
|
||||
Usage:
|
||||
echo <body> | de-p1st-notify
|
||||
|
||||
echo <body> | de-p1st-notify <subject>
|
||||
|
||||
de-p1st-notify <body>
|
||||
|
||||
de-p1st-notify <subject> <body>
|
||||
"""
|
||||
|
||||
BODY = None
|
||||
@ -18,7 +24,7 @@ def main():
|
||||
hostname = socket.gethostname()
|
||||
|
||||
if len(argv) > 3:
|
||||
print('execNotify>> Expected at most two arguments!')
|
||||
print('execNotify>> Expected at most two arguments!', file=stderr)
|
||||
exit(1)
|
||||
if len(argv) == 2 or len(argv) == 3:
|
||||
subj = argv[1]
|
||||
@ -31,6 +37,7 @@ def main():
|
||||
BODY = stdin.read()
|
||||
|
||||
SUBJECT = f'{hostname} | {subj}'
|
||||
print(SUBJECT)
|
||||
print(BODY)
|
||||
|
||||
mail.sendMailOrWriteToFile(SUBJECT=SUBJECT, BODY=BODY)
|
||||
|
Loading…
Reference in New Issue
Block a user