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)