#!/usr/bin/env python3
"""
Alert Notifier - Webhook receiver for Alertmanager
Forwards alerts to Telegram and ntfy
Environment Variables:
TELEGRAM_BOT_TOKEN - Telegram bot token
TELEGRAM_CHAT_ID - Telegram chat ID
NTFY_TOPIC - ntfy.sh topic name (optional)
"""
import os
import json
import logging
from http.server import HTTPServer, BaseHTTPRequestHandler
import urllib.request
import urllib.parse
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
TELEGRAM_BOT_TOKEN = os.environ.get('TELEGRAM_BOT_TOKEN', '')
TELEGRAM_CHAT_ID = os.environ.get('TELEGRAM_CHAT_ID', '')
NTFY_TOPIC = os.environ.get('NTFY_TOPIC', '')
SEVERITY_EMOJI = {
'critical': 'đ¨',
'warning': 'â ī¸',
'info': 'âšī¸',
}
def format_alert_telegram(alert: dict, status: str) -> str:
"""Format a single alert for Telegram."""
labels = alert.get('labels', {})
annotations = alert.get('annotations', {})
severity = labels.get('severity', 'unknown')
emoji = SEVERITY_EMOJI.get(severity, 'đ')
if status == 'resolved':
emoji = 'â
'
alertname = labels.get('alertname', 'Unknown')
job = labels.get('job', '')
summary = annotations.get('summary', alertname)
description = annotations.get('description', '')
msg = f"{emoji} {status.upper()}: {summary}\n"
if job:
msg += f"Service: {job}\n"
if description:
msg += f"{description}\n"
return msg
def send_telegram(message: str) -> bool:
"""Send message to Telegram."""
if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
logger.warning("Telegram not configured")
return False
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
data = {
'chat_id': TELEGRAM_CHAT_ID,
'text': message,
'parse_mode': 'HTML',
'disable_web_page_preview': True
}
try:
req = urllib.request.Request(
url,
data=urllib.parse.urlencode(data).encode(),
headers={'Content-Type': 'application/x-www-form-urlencoded'}
)
with urllib.request.urlopen(req, timeout=10) as resp:
return resp.status == 200
except Exception as e:
logger.error(f"Telegram send failed: {e}")
return False
def send_ntfy(title: str, message: str, priority: str = 'default') -> bool:
"""Send message to ntfy."""
if not NTFY_TOPIC:
return False
url = f"https://ntfy.sh/{NTFY_TOPIC}"
priority_map = {
'critical': 'urgent',
'warning': 'high',
'info': 'low'
}
ntfy_priority = priority_map.get(priority, 'default')
try:
req = urllib.request.Request(
url,
data=message.encode('utf-8'),
headers={
'Title': title,
'Priority': ntfy_priority,
'Tags': 'warning' if priority == 'critical' else 'loudspeaker'
}
)
with urllib.request.urlopen(req, timeout=10) as resp:
return resp.status == 200
except Exception as e:
logger.error(f"ntfy send failed: {e}")
return False
class AlertHandler(BaseHTTPRequestHandler):
def do_POST(self):
if self.path != '/webhook':
self.send_response(404)
self.end_headers()
return
content_length = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(content_length)
try:
payload = json.loads(body)
self.process_alerts(payload)
self.send_response(200)
self.end_headers()
self.wfile.write(b'OK')
except Exception as e:
logger.error(f"Error processing webhook: {e}")
self.send_response(500)
self.end_headers()
self.wfile.write(str(e).encode())
def do_GET(self):
if self.path == '/health':
self.send_response(200)
self.end_headers()
self.wfile.write(b'OK')
else:
self.send_response(404)
self.end_headers()
def process_alerts(self, payload: dict):
"""Process Alertmanager webhook payload."""
status = payload.get('status', 'unknown')
alerts = payload.get('alerts', [])
if not alerts:
return
logger.info(f"Received {len(alerts)} alerts with status: {status}")
# Build message
messages = []
highest_severity = 'info'
for alert in alerts:
msg = format_alert_telegram(alert, alert.get('status', status))
messages.append(msg)
severity = alert.get('labels', {}).get('severity', 'info')
if severity == 'critical':
highest_severity = 'critical'
elif severity == 'warning' and highest_severity != 'critical':
highest_severity = 'warning'
combined_message = '\n'.join(messages)
# Send notifications
if TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID:
success = send_telegram(combined_message)
logger.info(f"Telegram: {'sent' if success else 'failed'}")
if NTFY_TOPIC:
title = f"Mana Alert ({len(alerts)} alerts)"
# Strip HTML for ntfy
plain_message = combined_message.replace('', '').replace('', '')
plain_message = plain_message.replace('', '').replace('', '')
success = send_ntfy(title, plain_message, highest_severity)
logger.info(f"ntfy: {'sent' if success else 'failed'}")
def log_message(self, format, *args):
logger.info(f"{self.client_address[0]} - {format % args}")
def main():
port = int(os.environ.get('PORT', 8080))
logger.info(f"Starting Alert Notifier on port {port}")
logger.info(f"Telegram configured: {bool(TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID)}")
logger.info(f"ntfy configured: {bool(NTFY_TOPIC)}")
server = HTTPServer(('0.0.0.0', port), AlertHandler)
try:
server.serve_forever()
except KeyboardInterrupt:
logger.info("Shutting down")
server.shutdown()
if __name__ == '__main__':
main()