mirror of
https://github.com/isjerryxiao/pacroller.git
synced 2024-12-22 22:29:24 +08:00
add smtp
This commit is contained in:
parent
2d4fb825a1
commit
c07831944c
5 changed files with 102 additions and 3 deletions
|
@ -1,11 +1,13 @@
|
|||
import json
|
||||
from pathlib import Path
|
||||
import importlib.util
|
||||
from base64 import b64decode
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
CONFIG_DIR = Path('/etc/pacroller')
|
||||
CONFIG_FILE = 'config.json'
|
||||
CONFIG_FILE_SMTP = 'smtp.json'
|
||||
F_KNOWN_OUTPUT_OVERRIDE = 'known_output_override.py'
|
||||
LIB_DIR = Path('/var/lib/pacroller')
|
||||
DB_FILE = 'db'
|
||||
|
@ -21,6 +23,11 @@ if (cfg := (CONFIG_DIR / CONFIG_FILE)).exists():
|
|||
else:
|
||||
_config = dict()
|
||||
|
||||
if (smtp_cfg := (CONFIG_DIR / CONFIG_FILE_SMTP)).exists():
|
||||
_smtp_config: dict = json.loads(smtp_cfg.read_text())
|
||||
else:
|
||||
_smtp_config = dict()
|
||||
|
||||
def _import_module(fpath: Path) -> Any:
|
||||
spec = importlib.util.spec_from_file_location(str(fpath).removesuffix('.py').replace('/', '.'), fpath)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
|
@ -64,3 +71,23 @@ for i in NEEDRESTART_CMD:
|
|||
|
||||
SYSTEMD = bool(_config.get('systemd-check', True))
|
||||
PACMAN_SCC = bool(_config.get('clear_pkg_cache', False))
|
||||
|
||||
SMTP_ENABLED = bool(_smtp_config.get('enabled', False))
|
||||
SMTP_SSL = bool(_smtp_config.get('ssl', True))
|
||||
SMTP_HOST = _smtp_config.get('host', "")
|
||||
SMTP_PORT = int(_smtp_config.get('port', 0))
|
||||
SMTP_FROM = _smtp_config.get('from', "")
|
||||
SMTP_TO = _smtp_config.get('to', "")
|
||||
SMTP_AUTH = dict(_smtp_config.get('auth', {}))
|
||||
if SMTP_ENABLED:
|
||||
assert SMTP_HOST
|
||||
assert SMTP_FROM
|
||||
assert SMTP_TO
|
||||
assert 0 <= SMTP_PORT <= 65536
|
||||
if SMTP_AUTH:
|
||||
assert SMTP_AUTH['username']
|
||||
if _smtp_auth_b64 := SMTP_AUTH.get('password_base64', ''):
|
||||
SMTP_AUTH['password'] = b64decode(_smtp_auth_b64).decode('utf-8')
|
||||
SMTP_AUTH.pop('password_base64')
|
||||
assert SMTP_AUTH['password']
|
||||
SMTP_AUTH = {k:v for k, v in SMTP_AUTH.items() if k in {'username', 'password'}}
|
||||
|
|
46
src/pacroller/mailer.py
Normal file
46
src/pacroller/mailer.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
import smtplib
|
||||
from email.message import EmailMessage
|
||||
from typing import List
|
||||
import logging
|
||||
from platform import node
|
||||
from pacroller.config import NETWORK_RETRY, SMTP_ENABLED, SMTP_SSL, SMTP_HOST, SMTP_PORT, SMTP_FROM, SMTP_TO, SMTP_AUTH
|
||||
|
||||
logger = logging.getLogger()
|
||||
hostname = node() or "unknown-host"
|
||||
|
||||
class MailSender:
|
||||
def __init__(self) -> None:
|
||||
self.host = SMTP_HOST
|
||||
self.port = SMTP_PORT
|
||||
self.ssl = SMTP_SSL
|
||||
self.auth = SMTP_AUTH
|
||||
self.mailfrom = SMTP_FROM
|
||||
self.mailto = SMTP_TO.split()
|
||||
self.smtp_cls = smtplib.SMTP_SSL if self.ssl else smtplib.SMTP
|
||||
def send_text_plain(self, text: str, subject: str = f"pacroller from {hostname}", mailto: List[str] = list()) -> None:
|
||||
if not SMTP_ENABLED:
|
||||
return
|
||||
for _ in range(NETWORK_RETRY):
|
||||
try:
|
||||
server = self.smtp_cls(self.host, self.port)
|
||||
if self.auth:
|
||||
server.login(self.auth["username"], self.auth["password"])
|
||||
mailto = mailto if mailto else self.mailto
|
||||
msg = EmailMessage()
|
||||
msg.set_content(f"from pacroller running on {hostname=}:\n\n{text}")
|
||||
msg['Subject'] = subject
|
||||
msg['From'] = self.mailfrom
|
||||
msg['To'] = ', '.join(mailto)
|
||||
server.send_message(msg)
|
||||
server.quit()
|
||||
except Exception:
|
||||
logger.exception("error while smtp send_message")
|
||||
else:
|
||||
logger.debug(f"smtp sent {text=}")
|
||||
break
|
||||
else:
|
||||
logger.error(f"unable to send email after {NETWORK_RETRY} attempts {text=}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(module)s - %(funcName)s - %(levelname)s - %(message)s')
|
||||
MailSender().send_text_plain("This is a test mail\nIf you see this email, your smtp config is working.")
|
|
@ -16,6 +16,7 @@ from pacroller.config import (CONFIG_DIR, CONFIG_FILE, LIB_DIR, DB_FILE, PACMAN_
|
|||
TIMEOUT, UPGRADE_TIMEOUT, NETWORK_RETRY, CUSTOM_SYNC, SYNC_SH,
|
||||
EXTRA_SAFE, SHELL, HOLD, NEEDRESTART, NEEDRESTART_CMD, SYSTEMD,
|
||||
PACMAN_PKG_DIR, PACMAN_SCC, PACMAN_DB_LCK, SAVE_STDOUT, LOG_DIR)
|
||||
from pacroller.mailer import MailSender
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
@ -279,6 +280,7 @@ def main() -> None:
|
|||
logger.handlers[0].setLevel(logging.INFO)
|
||||
locale_set()
|
||||
interactive = args.interactive == "on" or not (args.interactive == 'off' or not isatty(0))
|
||||
send_mail = MailSender().send_text_plain if not interactive else lambda *_, **_: None
|
||||
logger.debug(f"interactive questions {'enabled' if interactive else 'disabled'}")
|
||||
|
||||
if args.action == 'run':
|
||||
|
@ -286,26 +288,35 @@ def main() -> None:
|
|||
logger.error('you need to be root')
|
||||
exit(1)
|
||||
if prev_err := has_previous_error():
|
||||
logger.error(f'Cannot continue, a previous error {prev_err} is still present. Please resolve this issue and run reset.')
|
||||
_err = f'Cannot continue, a previous error {prev_err} is still present. Please resolve this issue and run reset.'
|
||||
logger.error(_err)
|
||||
send_mail(_err)
|
||||
exit(2)
|
||||
if SYSTEMD:
|
||||
if _s := is_system_failed():
|
||||
logger.error(f'systemd is in {_s} state, refused')
|
||||
_err = f'systemd is in {_s} state, refused'
|
||||
logger.error(_err)
|
||||
send_mail(_err)
|
||||
exit(11)
|
||||
if Path(PACMAN_DB_LCK).exists():
|
||||
logger.error(f'Database is locked at {PACMAN_DB_LCK}')
|
||||
_err = f'Database is locked at {PACMAN_DB_LCK}'
|
||||
logger.error(_err)
|
||||
send_mail(_err)
|
||||
exit(2)
|
||||
try:
|
||||
report = do_system_upgrade(debug=args.debug, interactive=interactive)
|
||||
except NonFatal:
|
||||
send_mail(f"NonFatal Error:\n{traceback.format_exc()}")
|
||||
raise
|
||||
except Exception as e:
|
||||
write_db(None, e)
|
||||
send_mail(f"Fatal Error:\n{traceback.format_exc()}")
|
||||
raise
|
||||
else:
|
||||
exc = CheckFailed('manual inspection required') if report.failed else None
|
||||
write_db(report, exc)
|
||||
if exc:
|
||||
send_mail(f"{exc}\n\n{report.summary(verbose=args.verbose, show_package=False)}")
|
||||
exit(2)
|
||||
if NEEDRESTART:
|
||||
run_needrestart()
|
||||
|
|
15
src/pacroller/smtp.json
Normal file
15
src/pacroller/smtp.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"enabled": false,
|
||||
"ssl": true,
|
||||
"host": "smtp.example.com",
|
||||
"port": 465,
|
||||
"from": "me@example.com",
|
||||
"to": "you1@example.com you2@example.com",
|
||||
"auth": {
|
||||
"comment1": "## if you don't want any kind of authorization, delete the whole auth section",
|
||||
"comment2": "## you can have either one of the two following password keys",
|
||||
"username": "myname",
|
||||
"password": "mypassword",
|
||||
"password_base64": "bXlwYXNzd29yZA=="
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue