HEX
Server: LiteSpeed
System: Linux sarajevo.maychu.cloud 5.14.0-503.40.1.el9_5.x86_64 #1 SMP PREEMPT_DYNAMIC Mon May 5 06:06:04 EDT 2025 x86_64
User: inqua407 (1189)
PHP: 8.3.17
Disabled: exec,execl,system,passthru,shell_exec,escapeshellarg,escapeshellcmd,proc_close,ini_alter,proc_open,dl,popen,show_source,posix_getpwuid,getpwuid,posix_geteuid,posix_getegid,posix_getgrgid,open_basedir,safe_mode_include_dir,pcntl_exec,pcntl_fork,proc_get_status,proc_nice,proc_terminate,pclose,virtual,openlog,popen,pclose,virtual,openlog,escapeshellcmd,escapeshellarg,dl,show_source,symlink,mail
Upload Files
File: //opt/getbackup/bin/notifier.py
#!/usr/bin/env python3
import json
import os
import smtplib
import socket
import ssl
import sys
import traceback
from datetime import datetime
from email.mime.text import MIMEText
from urllib import parse, request

SETTINGS_FILE = '/opt/getbackup/etc/settings.json'
LOG_FILE = '/opt/getbackup/logs/notifier.log'
TIMEOUT = 15


def now_str():
    return datetime.now().strftime('%Y-%m-%d %H:%M:%S')


def write_log(msg: str):
    try:
        os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
        with open(LOG_FILE, 'a', encoding='utf-8') as fh:
            fh.write(f'[{now_str()}] {msg}\n')
    except Exception:
        pass


def load_settings():
    if not os.path.exists(SETTINGS_FILE):
        write_log(f'[WARN] Missing settings file: {SETTINGS_FILE}')
        return {}
    try:
        with open(SETTINGS_FILE, 'r', encoding='utf-8') as fh:
            return json.load(fh)
    except Exception as exc:
        write_log(f'[ERROR] Cannot load settings: {exc}')
        return {}


def level_allowed(level, levels):
    if not isinstance(levels, list) or not levels:
        return False
    normalized = {str(x).upper() for x in levels}
    return str(level).upper() in normalized


def render_template(template: str, data: dict):
    if not isinstance(template, str) or not template:
        return ''
    out = template
    for k, v in data.items():
        out = out.replace('{' + k + '}', str(v))
    return out


def send_telegram(settings, ctx):
    if not settings.get('telegram_enabled'):
        return False, 'telegram disabled'

    token = (settings.get('telegram_token') or '').strip()
    chat_id = str(settings.get('telegram_chat_id') or '').strip()
    levels = settings.get('telegram_levels') or []

    if not token or not chat_id:
        return False, 'telegram missing token/chat_id'
    if not level_allowed(ctx['level'], levels):
        return False, f"telegram level filtered ({ctx['level']})"

    text_tpl = settings.get('telegram_message') or (
        '*GetBackup Alert*\n'
        'Server: {hostname}\n'
        'Level: {level}\n'
        'Job: {job}\n'
        'Time: {time}\n\n'
        '{message}'
    )
    text = render_template(text_tpl, ctx)
    url = f'https://api.telegram.org/bot{token}/sendMessage'

    payload = {
        'chat_id': chat_id,
        'text': text,
        'parse_mode': 'Markdown',
        'disable_web_page_preview': 'true',
    }

    data = parse.urlencode(payload).encode('utf-8')
    req = request.Request(url, data=data, method='POST')
    try:
        with request.urlopen(req, timeout=TIMEOUT) as resp:
            body = resp.read().decode('utf-8', errors='replace')
            if '"ok":true' in body:
                return True, 'telegram sent'
            return False, f'telegram api not ok: {body[:240]}'
    except Exception as exc:
        return False, f'telegram error: {exc}'


def _smtp_send(host, port, user, password, sender, recipients, msg, insecure=False):
    tls_ctx = ssl.create_default_context()
    if insecure:
        tls_ctx.check_hostname = False
        tls_ctx.verify_mode = ssl.CERT_NONE

    if port == 465:
        with smtplib.SMTP_SSL(host, port, timeout=TIMEOUT, context=tls_ctx) as server:
            server.ehlo()
            if user:
                server.login(user, password)
            server.sendmail(sender, recipients, msg.as_string())
    else:
        with smtplib.SMTP(host, port, timeout=TIMEOUT) as server:
            server.ehlo()
            server.starttls(context=tls_ctx)
            server.ehlo()
            if user:
                server.login(user, password)
            server.sendmail(sender, recipients, msg.as_string())


def send_smtp(settings, ctx):
    if not settings.get('smtp_enabled'):
        return False, 'smtp disabled'

    host = (settings.get('smtp_host') or '').strip()
    port = int(settings.get('smtp_port') or 587)
    user = (settings.get('smtp_user') or '').strip()
    password = settings.get('smtp_pass') or ''
    sender = (settings.get('smtp_from') or '').strip()
    to_addr = (settings.get('smtp_to') or '').strip()
    levels = settings.get('smtp_levels') or []

    if not (host and sender and to_addr):
        return False, 'smtp missing host/from/to'
    if not level_allowed(ctx['level'], levels):
        return False, f"smtp level filtered ({ctx['level']})"

    recipients = [x.strip() for x in to_addr.split(',') if x.strip()]
    if not recipients:
        return False, 'smtp recipient list empty'

    subject_tpl = settings.get('smtp_subject') or '[{hostname}] GetBackup {level} - {job}'
    body_tpl = settings.get('smtp_body') or 'Server: {hostname}\nTime: {time}\nLevel: {level}\nJob: {job}\n\n{message}'

    subject = render_template(subject_tpl, ctx)
    body = render_template(body_tpl, ctx)

    msg = MIMEText(body, _charset='utf-8')
    msg['Subject'] = subject
    msg['From'] = sender
    msg['To'] = ', '.join(recipients)

    try:
        _smtp_send(host, port, user, password, sender, recipients, msg, insecure=False)
        return True, 'smtp sent'
    except ssl.SSLCertVerificationError as exc:
        try:
            _smtp_send(host, port, user, password, sender, recipients, msg, insecure=True)
            write_log(f'[WARN] SMTP TLS verify failed, fallback insecure TLS used: {exc}')
            return True, 'smtp sent (insecure tls fallback)'
        except Exception as fallback_exc:
            return False, f'smtp fallback failed: {fallback_exc}'
    except Exception as exc:
        return False, f'smtp error: {exc}'


def main():
    level = (sys.argv[1] if len(sys.argv) > 1 else 'INFO').upper()
    job = sys.argv[2] if len(sys.argv) > 2 else 'System'
    message = sys.argv[3] if len(sys.argv) > 3 else ''

    ctx = {
        'hostname': socket.gethostname(),
        'time': now_str(),
        'level': level,
        'job': job,
        'message': message,
    }

    settings = load_settings()
    tg_ok, tg_msg = send_telegram(settings, ctx)
    sm_ok, sm_msg = send_smtp(settings, ctx)

    if tg_ok or sm_ok:
        write_log(f'[OK] level={level} job={job} telegram={tg_msg} smtp={sm_msg}')
    else:
        write_log(f'[WARN] level={level} job={job} telegram={tg_msg} smtp={sm_msg}')


if __name__ == '__main__':
    try:
        main()
    except Exception as exc:
        write_log(f'[FATAL] notifier crash: {exc}')
        write_log(traceback.format_exc().strip().replace('\n', ' | '))
        sys.exit(0)