mirror of
https://codeberg.org/privacy1st/exec-notify
synced 2024-12-22 23:16:04 +01:00
v0.0.2
This commit is contained in:
parent
5d51ce01b2
commit
1183b6fc23
11
.gitignore
vendored
11
.gitignore
vendored
@ -1,3 +1,8 @@
|
|||||||
execNotifyDir/config/cfg.ini
|
/etc/execNotify/cfg.ini
|
||||||
__pycache__/
|
/mail/
|
||||||
.idea/workspace.xml
|
|
||||||
|
/.idea/workspace.xml
|
||||||
|
/__pycache__/
|
||||||
|
|
||||||
|
/dist/
|
||||||
|
/src/de_p1st_execNotify.egg-info/
|
||||||
|
9
.idea/execNotify.iml
generated
9
.idea/execNotify.iml
generated
@ -1,8 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module type="PYTHON_MODULE" version="4">
|
<module type="PYTHON_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$" />
|
<content url="file://$MODULE_DIR$">
|
||||||
<orderEntry type="jdk" jdkName="Python 3.7 (venv37)" jdkType="Python SDK" />
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/src/de_p1st_execNotify.egg-info" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/mail" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.9 (venv39-2)" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (venv37)" project-jdk-type="Python SDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (venv39-2)" project-jdk-type="Python SDK" />
|
||||||
</project>
|
</project>
|
25
.idea/runConfigurations/execNotify.xml
generated
Normal file
25
.idea/runConfigurations/execNotify.xml
generated
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="execNotify" type="PythonConfigurationType" factoryName="Python">
|
||||||
|
<module name="execNotify" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
<env name="DE_P1ST_EXEC_NOTIFY" value="" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="$USER_HOME$/venv39-2/bin/python" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/de/p1st/exec_notify" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="false" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||||
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/de/p1st/exec_notify/execNotify" />
|
||||||
|
<option name="PARAMETERS" value="ls $USER" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="false" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
25
.idea/runConfigurations/notify.xml
generated
Normal file
25
.idea/runConfigurations/notify.xml
generated
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="notify" type="PythonConfigurationType" factoryName="Python">
|
||||||
|
<module name="execNotify" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
<env name="DE_P1ST_EXEC_NOTIFY" value="" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="$USER_HOME$/venv39-2/bin/python" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/de/p1st/exec_notify" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="false" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||||
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/de/p1st/exec_notify/notify" />
|
||||||
|
<option name="PARAMETERS" value="someSubject someBody-123" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="false" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
24
Makefile
24
Makefile
@ -1,24 +0,0 @@
|
|||||||
.PHONY: all install clean copy remove
|
|
||||||
|
|
||||||
all: install
|
|
||||||
|
|
||||||
clean: remove
|
|
||||||
|
|
||||||
install: permissions
|
|
||||||
|
|
||||||
permissions: copy
|
|
||||||
chmod 755 /usr/local/bin/notify
|
|
||||||
chmod 755 /usr/local/bin/execNotify
|
|
||||||
find /usr/local/bin/execNotifyDir \( -type d -exec chmod 755 {} + \) -o \( -type f -exec chmod 644 {} + \)
|
|
||||||
chown root:root /usr/local/bin/execNotify
|
|
||||||
chown -R root:root /usr/local/bin/execNotifyDir
|
|
||||||
|
|
||||||
copy:
|
|
||||||
cp notify /usr/local/bin/notify
|
|
||||||
cp execNotify /usr/local/bin/execNotify
|
|
||||||
cp -r execNotifyDir/ /usr/local/bin/
|
|
||||||
|
|
||||||
remove:
|
|
||||||
rm /usr/local/bin/notify
|
|
||||||
rm /usr/local/bin/execNotify
|
|
||||||
rm -r /usr/local/bin/execNotifyDir
|
|
79
README.md
79
README.md
@ -1,43 +1,88 @@
|
|||||||
# execNotify
|
# execNotify
|
||||||
|
|
||||||
Send notification on failure.
|
* Send email notification if command fails with [execNotify](src/de/p1st/exec_notify/execNotify).
|
||||||
Or send an unconditional notification.
|
* Send unconditional notifications with [notify](src/de/p1st/exec_notify/notify).
|
||||||
|
|
||||||
## setup
|
## Installation
|
||||||
|
|
||||||
Create a `config.ini` file inside `execNotifyDir/config`.
|
Create a _venv_ and install the module from TestPyPI:
|
||||||
|
|
||||||
For the required fields, see `execNotifyDir/config/config.ini.example`
|
|
||||||
|
|
||||||
## installation
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo make install
|
python3 -m pip install --index-url https://test.pypi.org/simple/ --no-deps de-p1st-execNotify
|
||||||
```
|
```
|
||||||
|
|
||||||
## usage of execNotify
|
The above command uses the parameter `--no-deps`:
|
||||||
|
|
||||||
|
> Since TestPyPI doesn’t have the same packages as the live PyPI, it’s possible that attempting
|
||||||
|
> to install dependencies may fail or install something unexpected. While this package
|
||||||
|
> doesn’t have any dependencies, it’s a good practice to avoid installing dependencies when
|
||||||
|
> using TestPyPI.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Create configuration file `/etc/execNotify/cfg.ini`.
|
||||||
|
|
||||||
|
For the required fields, see [./etc/execNotify/cfg.ini.example](etc/execNotify/cfg.ini.example)
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Usage of execNotify
|
||||||
|
|
||||||
Add `execNotify` in front of your command to execute.
|
Add `execNotify` in front of your command to execute.
|
||||||
|
|
||||||
You can pipe into `execNotify` but **not** from `execNotify`
|
You can pipe into `execNotify` but **not** from `execNotify`
|
||||||
as the output gets modified.
|
as the output gets modified.
|
||||||
|
|
||||||
### Example
|
**Example:**
|
||||||
|
|
||||||
For `ls /home` run `execNotify ls /home`
|
`execNotify ls /non/existent/file` will mail you the exit code, stdout and stderr of `ls /non/existent/file`
|
||||||
|
|
||||||
## usage of notify
|
### Usage of notify
|
||||||
|
|
||||||
Send stdout via mail:
|
Send stdout via mail:
|
||||||
|
|
||||||
`echo "Hello world!" | notify`
|
`echo "Hello world!" | notify`
|
||||||
|
|
||||||
Or with optional subject:
|
Send stdout and stderr via mail:
|
||||||
|
|
||||||
|
`echo "Hello world!" 2>&1 | notify`
|
||||||
|
|
||||||
|
Send stdout and specify an optional email subject:
|
||||||
|
|
||||||
`echo "Hello world!" | notify "someSubject"`
|
`echo "Hello world!" | notify "someSubject"`
|
||||||
|
|
||||||
Or without the pipe:
|
Send message without using a pipe:
|
||||||
|
|
||||||
`notify "someSubject" "Hello" "World!" "What's" "up?"`
|
|
||||||
|
|
||||||
`notify "someSubject" "Hello World! What's up?"`
|
`notify "someSubject" "Hello World! What's up?"`
|
||||||
|
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
When started with environment variable `DE_P1ST_EXEC_NOTIFY` set,
|
||||||
|
then the configuration file is read from [./etc/execNotify/cfg.ini](etc/execNotify/cfg.ini).
|
||||||
|
|
||||||
|
### Uploading to TestPyPI
|
||||||
|
|
||||||
|
More detailed instructions can be found at https://packaging.python.org/tutorials/packaging-projects/
|
||||||
|
|
||||||
|
0) Set up a _venv_
|
||||||
|
1) Develop and test locally
|
||||||
|
2) Increase/Adjust `[metadata][version]` in [setup.cfg](setup.cfg)
|
||||||
|
3) Install the `build` and `twine` modules to your _venv_.
|
||||||
|
4) Next generate a [distribution archive](https://packaging.python.org/tutorials/packaging-projects/#generating-distribution-archives):
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python3 -m build
|
||||||
|
```
|
||||||
|
|
||||||
|
5) Upload the distribution packages with twine. For the username, use `__token__`. For the password, use a
|
||||||
|
[test.pypi.org API token](https://test.pypi.org/manage/account/#api-tokens):
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python3 -m twine upload --repository testpypi dist/*
|
||||||
|
```
|
||||||
|
|
||||||
|
6) Congratulations! You can view the uploaded module under:
|
||||||
|
|
||||||
|
* [https://test.pypi.org/project/de-p1st-execNotify/](https://test.pypi.org/project/de-p1st-execNotify/)
|
||||||
|
@ -9,4 +9,4 @@ from = noreply@example.com
|
|||||||
to = me@example.com
|
to = me@example.com
|
||||||
|
|
||||||
[file]
|
[file]
|
||||||
errorfile = /home/exampleUser/ERROR.mail
|
maildir = /home/exampleUser/mail/
|
40
execNotify
40
execNotify
@ -1,40 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
from sys import argv
|
|
||||||
import socket
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from execNotifyDir import exec, config, mail
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(argv) >= 2:
|
|
||||||
if not executeCommand(argv[1:]):
|
|
||||||
exit(1)
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
def executeCommand(command: List) -> bool:
|
|
||||||
keys = ['command', 'status', 'stderr', 'stdout']
|
|
||||||
code, stdout, stderr = exec.execute(command)
|
|
||||||
values = [str(command), str(code), stderr, stdout]
|
|
||||||
|
|
||||||
BODY = ''
|
|
||||||
for key, value in zip(keys, values):
|
|
||||||
BODY += '=== {} ===\n{}\n'.format(key, value)
|
|
||||||
print(BODY)
|
|
||||||
|
|
||||||
if code != 0:
|
|
||||||
hostname = socket.gethostname()
|
|
||||||
|
|
||||||
SUBJECT = '{} | {}'.format(hostname, str(command))
|
|
||||||
mail.sendMailOrWriteToFile(SUBJECT=SUBJECT, BODY=BODY)
|
|
||||||
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
if mail.prevMailNotSent():
|
|
||||||
mail.informAboutOldMail()
|
|
@ -1,15 +0,0 @@
|
|||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
def getProjectBase() -> Path:
|
|
||||||
return Path(os.path.realpath(__file__)).parent.parent
|
|
||||||
|
|
||||||
|
|
||||||
def readFirstLine(file: Path) -> str:
|
|
||||||
"""
|
|
||||||
:param file: Path to file
|
|
||||||
:return: first line of file
|
|
||||||
"""
|
|
||||||
with open(file, "r") as f:
|
|
||||||
return f.readline()
|
|
36
notify
36
notify
@ -1,36 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
from sys import argv, stderr, stdin
|
|
||||||
import socket
|
|
||||||
from execNotifyDir import exec, config, mail
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
echo <body> | ./notify
|
|
||||||
echo <body> | ./notify <subject>
|
|
||||||
./notify <subject> <body_1> <body_2> ... <body_n>
|
|
||||||
"""
|
|
||||||
|
|
||||||
BODY = None
|
|
||||||
subj = None
|
|
||||||
hostname = socket.gethostname()
|
|
||||||
|
|
||||||
if len(argv) >= 2:
|
|
||||||
subj = argv[1]
|
|
||||||
if len(argv) >= 3:
|
|
||||||
BODY = str(argv[2:])
|
|
||||||
if subj is None:
|
|
||||||
subj = "notify"
|
|
||||||
if BODY is None:
|
|
||||||
BODY = "=== stdin ===\n" + stdin.read()
|
|
||||||
|
|
||||||
SUBJECT = "{} | {}".format(hostname, subj)
|
|
||||||
print(BODY)
|
|
||||||
|
|
||||||
mail.sendMailOrWriteToFile(SUBJECT=SUBJECT, BODY=BODY)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
if mail.prevMailNotSent():
|
|
||||||
mail.informAboutOldMail()
|
|
8
pyproject.toml
Normal file
8
pyproject.toml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# https://packaging.python.org/tutorials/packaging-projects/#creating-pyproject-toml
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = [
|
||||||
|
"setuptools>=42",
|
||||||
|
"wheel"
|
||||||
|
]
|
||||||
|
build-backend = "setuptools.build_meta"
|
28
setup.cfg
Normal file
28
setup.cfg
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# setup.cfg is the configuration file for setuptools.
|
||||||
|
# https://packaging.python.org/tutorials/packaging-projects/#configuring-metadata
|
||||||
|
# https://pypi.org/classifiers/
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
name = de-p1st-execNotify
|
||||||
|
version = 0.0.2
|
||||||
|
author = Daniel Langbein
|
||||||
|
author_email = daniel@systemli.org
|
||||||
|
description = Send mail with process output
|
||||||
|
long_description = file: README.md
|
||||||
|
long_description_content_type = text/markdown
|
||||||
|
url = https://codeberg.org/langfingaz/execNotify
|
||||||
|
project_urls =
|
||||||
|
Bug Tracker = https://codeberg.org/langfingaz/execNotify/issues
|
||||||
|
classifiers =
|
||||||
|
Programming Language :: Python :: 3
|
||||||
|
License :: OSI Approved :: MIT License
|
||||||
|
Operating System :: Unix
|
||||||
|
|
||||||
|
[options]
|
||||||
|
package_dir =
|
||||||
|
= src
|
||||||
|
packages = find:
|
||||||
|
python_requires = >=3.8
|
||||||
|
|
||||||
|
[options.packages.find]
|
||||||
|
where = src
|
0
src/de/p1st/__init__.py
Normal file
0
src/de/p1st/__init__.py
Normal file
0
src/de/p1st/exec_notify/__init__.py
Normal file
0
src/de/p1st/exec_notify/__init__.py
Normal file
44
src/de/p1st/exec_notify/execNotify
Executable file
44
src/de/p1st/exec_notify/execNotify
Executable file
@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
from sys import argv
|
||||||
|
import socket
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from de.p1st.exec_notify.lib import exec, config, mail
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(argv) <= 1:
|
||||||
|
print('No command given to execute!', file=sys.stderr)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
exit(executeCommand(argv[1:]))
|
||||||
|
|
||||||
|
|
||||||
|
def executeCommand(command: List) -> int:
|
||||||
|
"""
|
||||||
|
Executes the given command and sends an email on failure.
|
||||||
|
|
||||||
|
:return: exit code of executed command
|
||||||
|
"""
|
||||||
|
|
||||||
|
keys = ['command', 'exit code', 'stderr', 'stdout']
|
||||||
|
exitCode, stdout, stderr = exec.execute(command)
|
||||||
|
values = [str(command), str(exitCode), stderr, stdout]
|
||||||
|
|
||||||
|
BODY = ''
|
||||||
|
for key, value in zip(keys, values):
|
||||||
|
BODY += '=== {} ===\n{}\n'.format(key, value)
|
||||||
|
print(BODY)
|
||||||
|
|
||||||
|
if exitCode != 0:
|
||||||
|
hostname = socket.gethostname()
|
||||||
|
|
||||||
|
SUBJECT = '{} | {}'.format(hostname, str(command))
|
||||||
|
mail.sendMailOrWriteToFile(SUBJECT=SUBJECT, BODY=BODY)
|
||||||
|
|
||||||
|
return exitCode
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
0
src/de/p1st/exec_notify/lib/__init__.py
Normal file
0
src/de/p1st/exec_notify/lib/__init__.py
Normal file
@ -1,7 +1,8 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path, PosixPath
|
||||||
import configparser
|
import configparser
|
||||||
|
|
||||||
from execNotifyDir import util
|
from de.p1st.exec_notify.lib import util
|
||||||
|
|
||||||
|
|
||||||
def getHostAndPort():
|
def getHostAndPort():
|
||||||
return config['mail']['host'], config['mail']['port']
|
return config['mail']['host'], config['mail']['port']
|
||||||
@ -19,8 +20,8 @@ def getTo():
|
|||||||
return config['mail']['to']
|
return config['mail']['to']
|
||||||
|
|
||||||
|
|
||||||
def getErrorFile() -> Path:
|
def getMailDir() -> Path:
|
||||||
return Path(config['file']['errorfile'])
|
return Path(config['file']['maildir'])
|
||||||
|
|
||||||
|
|
||||||
def _getCfgFile() -> Path:
|
def _getCfgFile() -> Path:
|
||||||
@ -28,7 +29,11 @@ def _getCfgFile() -> Path:
|
|||||||
|
|
||||||
|
|
||||||
def _getCfgDir() -> Path:
|
def _getCfgDir() -> Path:
|
||||||
return util.getProjectBase().joinpath('execNotifyDir').joinpath('config')
|
if util.isInDevelopment():
|
||||||
|
return util.getProjectBase().joinpath('etc','execNotify')
|
||||||
|
else:
|
||||||
|
return PosixPath('/etc/execNotify/')
|
||||||
|
|
||||||
|
|
||||||
config: configparser.ConfigParser = configparser.ConfigParser()
|
config: configparser.ConfigParser = configparser.ConfigParser()
|
||||||
config.read(_getCfgFile())
|
config.read(_getCfgFile())
|
@ -9,7 +9,7 @@ def execute(command: List[str]):
|
|||||||
Wait for command to complete.
|
Wait for command to complete.
|
||||||
|
|
||||||
:param command: A command to executed as list of words, e.g. ["echo", "hello"]
|
:param command: A command to executed as list of words, e.g. ["echo", "hello"]
|
||||||
:return: (status, stdout, stderr)
|
:return: (exit_code, stdout, stderr)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
completed: subprocess.CompletedProcess = subprocess.run(
|
completed: subprocess.CompletedProcess = subprocess.run(
|
@ -6,15 +6,20 @@ import socket
|
|||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
|
||||||
from execNotifyDir import config
|
from de.p1st.exec_notify.lib import config
|
||||||
|
|
||||||
|
|
||||||
def sendMailOrWriteToFile(SUBJECT: str, BODY: str):
|
def sendMailOrWriteToFile(SUBJECT: str, BODY: str, informAboutLocalMail: bool = True):
|
||||||
|
if informAboutLocalMail and localMailExists():
|
||||||
|
BODY=f'[!] Note [!]\nThere is some local mail inside [file][maildir] that could not be sent previously! ' \
|
||||||
|
f'Please read and then delete the local mail.\n\n\n' \
|
||||||
|
f'{BODY}'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sendMail(SUBJECT=SUBJECT, BODY=BODY)
|
sendMail(SUBJECT=SUBJECT, BODY=BODY)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Error: Could not send mail! Writing to file instead ...", file=sys.stderr)
|
print(f'execNotify>> Could not send mail: {e}', file=sys.stderr)
|
||||||
print(e, file=sys.stderr)
|
print(f'execNotify>> Writing to file instead ...', file=sys.stderr)
|
||||||
|
|
||||||
# Instead, try to save the mail so that the user can read
|
# Instead, try to save the mail so that the user can read
|
||||||
# it later when connected to this computer
|
# it later when connected to this computer
|
||||||
@ -23,7 +28,7 @@ def sendMailOrWriteToFile(SUBJECT: str, BODY: str):
|
|||||||
|
|
||||||
def sendMail(SUBJECT: str, BODY: str):
|
def sendMail(SUBJECT: str, BODY: str):
|
||||||
"""
|
"""
|
||||||
:throws Exception: if mail could not be sent
|
:throws Exception: If mail could not be sent
|
||||||
"""
|
"""
|
||||||
|
|
||||||
FROM = config.getFrom()
|
FROM = config.getFrom()
|
||||||
@ -55,32 +60,20 @@ def saveMail(SUBJECT: str, BODY: str):
|
|||||||
time = datetime.datetime.now()
|
time = datetime.datetime.now()
|
||||||
timeStr = time.strftime('%Y%m%d_%H%M%S')
|
timeStr = time.strftime('%Y%m%d_%H%M%S')
|
||||||
|
|
||||||
DATE = '=' * 20 + '\n=== date ===\n' + timeStr + '\n'
|
|
||||||
SUBJECT = '=== subject ===\n' + SUBJECT + '\n'
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# create parent directory if not existent
|
||||||
|
mailDir = config.getMailDir()
|
||||||
|
mailDir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
# append to file; create file if not existent
|
# append to file; create file if not existent
|
||||||
with open(config.getErrorFile(), "a") as f:
|
with open(mailDir.joinpath(timeStr), "a") as f:
|
||||||
f.write(DATE)
|
f.write(f'{"=" * 20}\n=== date ===\n{timeStr}\n=== subject ===\n{SUBJECT}\n=== body ===\n{BODY}\n')
|
||||||
f.write(SUBJECT)
|
|
||||||
f.write(BODY)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('Error: Could not write to file!', file=sys.stderr)
|
print(f'execNotify>> Could not write to file: {e}', file=sys.stderr)
|
||||||
print(e, file=sys.stderr)
|
|
||||||
|
|
||||||
|
|
||||||
def prevMailNotSent():
|
def localMailExists():
|
||||||
return config.getErrorFile().exists()
|
|
||||||
|
|
||||||
|
|
||||||
def informAboutOldMail():
|
|
||||||
"""
|
"""
|
||||||
Try to inform user via mail about previous error(s) that could not be sent to him before.
|
:return: True if local mail exists in maildir folder. Once the mail is read the user shall delete (or move) it.
|
||||||
Maybe this time sending of an email works ;)
|
|
||||||
"""
|
"""
|
||||||
|
return len(list(config.getMailDir().iterdir())) > 0
|
||||||
SUBJECT = '{} | Some mails not sent!'.format(socket.gethostname())
|
|
||||||
BODY = 'Please check the file {} for mails which could previously not be sent to you!\n' \
|
|
||||||
'Note: You may delete the file after reading it ;)' \
|
|
||||||
.format(config.getErrorFile())
|
|
||||||
sendMail(SUBJECT=SUBJECT, BODY=BODY)
|
|
24
src/de/p1st/exec_notify/lib/util.py
Normal file
24
src/de/p1st/exec_notify/lib/util.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
return 'DE_P1ST_EXEC_NOTIFY' in os.environ
|
||||||
|
|
||||||
|
|
||||||
|
def getProjectBase() -> Path:
|
||||||
|
return Path(os.path.realpath(__file__)).parent.parent.parent.parent.parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def readFirstLine(file: Path) -> str:
|
||||||
|
"""
|
||||||
|
:param file: Path to file
|
||||||
|
:return: first line of file
|
||||||
|
"""
|
||||||
|
with open(file, "r") as f:
|
||||||
|
return f.readline()
|
40
src/de/p1st/exec_notify/notify
Executable file
40
src/de/p1st/exec_notify/notify
Executable file
@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from sys import argv, stderr, stdin
|
||||||
|
import socket
|
||||||
|
|
||||||
|
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>
|
||||||
|
"""
|
||||||
|
|
||||||
|
BODY = None
|
||||||
|
subj = None
|
||||||
|
hostname = socket.gethostname()
|
||||||
|
|
||||||
|
if len(argv) > 3:
|
||||||
|
print('execNotify>> Expected at most two arguments!')
|
||||||
|
exit(1)
|
||||||
|
if len(argv) == 2 or len(argv) == 3:
|
||||||
|
subj = argv[1]
|
||||||
|
if len(argv) == 3:
|
||||||
|
BODY = argv[2]
|
||||||
|
|
||||||
|
if subj is None:
|
||||||
|
subj = 'notify'
|
||||||
|
if BODY is None:
|
||||||
|
BODY = f'=== stdin ===\n{stdin.read()}'
|
||||||
|
|
||||||
|
SUBJECT = f'{hostname} | {subj}'
|
||||||
|
print(BODY)
|
||||||
|
|
||||||
|
mail.sendMailOrWriteToFile(SUBJECT=SUBJECT, BODY=BODY)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
Loading…
Reference in New Issue
Block a user