This commit is contained in:
JerryXiao 2021-07-01 11:37:54 +08:00
parent 2d4fb825a1
commit c07831944c
Signed by: Jerry
GPG key ID: 22618F758B5BE2E5
5 changed files with 102 additions and 3 deletions

View file

@ -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
View 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.")

View file

@ -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
View 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=="
}
}