131 lines
4.0 KiB
Python
131 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Signal Bridge for Alertmanager
|
|
Receives webhooks from Alertmanager and forwards to Signal API
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import requests
|
|
from flask import Flask, request, jsonify
|
|
|
|
app = Flask(__name__)
|
|
|
|
# Configuration from environment variables
|
|
SIGNAL_API_URL = os.environ.get('SIGNAL_API_URL', 'http://signal-api:8080')
|
|
SIGNAL_SENDER = os.environ.get('SIGNAL_SENDER', '') # Your Signal number
|
|
SIGNAL_RECIPIENTS = os.environ.get('SIGNAL_RECIPIENTS', '').split(',') # Comma-separated
|
|
|
|
def format_alert_message(alert_data):
|
|
"""Format Alertmanager webhook payload into a readable message"""
|
|
messages = []
|
|
|
|
status = alert_data.get('status', 'unknown')
|
|
|
|
for alert in alert_data.get('alerts', []):
|
|
alert_status = alert.get('status', status)
|
|
labels = alert.get('labels', {})
|
|
annotations = alert.get('annotations', {})
|
|
|
|
severity = labels.get('severity', 'unknown')
|
|
alertname = labels.get('alertname', 'Unknown Alert')
|
|
instance = labels.get('instance', 'unknown')
|
|
|
|
summary = annotations.get('summary', alertname)
|
|
description = annotations.get('description', '')
|
|
|
|
# Status emoji
|
|
if alert_status == 'resolved':
|
|
status_emoji = '✅'
|
|
status_text = 'RESOLVED'
|
|
elif severity == 'critical':
|
|
status_emoji = '🚨'
|
|
status_text = 'CRITICAL'
|
|
else:
|
|
status_emoji = '⚠️'
|
|
status_text = 'WARNING'
|
|
|
|
msg = f"{status_emoji} [{status_text}] {summary}"
|
|
if description:
|
|
msg += f"\n{description}"
|
|
|
|
messages.append(msg)
|
|
|
|
return "\n\n".join(messages)
|
|
|
|
def send_signal_message(message):
|
|
"""Send message via Signal API"""
|
|
if not SIGNAL_SENDER or not SIGNAL_RECIPIENTS:
|
|
app.logger.error("Signal sender or recipients not configured")
|
|
return False
|
|
|
|
success = True
|
|
for recipient in SIGNAL_RECIPIENTS:
|
|
recipient = recipient.strip()
|
|
if not recipient:
|
|
continue
|
|
|
|
try:
|
|
payload = {
|
|
"message": message,
|
|
"number": SIGNAL_SENDER,
|
|
"recipients": [recipient]
|
|
}
|
|
|
|
response = requests.post(
|
|
f"{SIGNAL_API_URL}/v2/send",
|
|
json=payload,
|
|
timeout=30
|
|
)
|
|
|
|
if response.status_code in [200, 201]:
|
|
app.logger.info(f"Message sent to {recipient}")
|
|
else:
|
|
app.logger.error(f"Failed to send to {recipient}: {response.status_code} - {response.text}")
|
|
success = False
|
|
|
|
except Exception as e:
|
|
app.logger.error(f"Error sending to {recipient}: {e}")
|
|
success = False
|
|
|
|
return success
|
|
|
|
@app.route('/health', methods=['GET'])
|
|
def health():
|
|
return jsonify({"status": "healthy"}), 200
|
|
|
|
@app.route('/alert', methods=['POST'])
|
|
def receive_alert():
|
|
"""Receive alert from Alertmanager and forward to Signal"""
|
|
try:
|
|
alert_data = request.get_json()
|
|
|
|
if not alert_data:
|
|
return jsonify({"error": "No data received"}), 400
|
|
|
|
app.logger.info(f"Received alert: {json.dumps(alert_data, indent=2)}")
|
|
|
|
message = format_alert_message(alert_data)
|
|
|
|
if send_signal_message(message):
|
|
return jsonify({"status": "sent"}), 200
|
|
else:
|
|
return jsonify({"status": "partial_failure"}), 207
|
|
|
|
except Exception as e:
|
|
app.logger.error(f"Error processing alert: {e}")
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@app.route('/test', methods=['POST'])
|
|
def test_message():
|
|
"""Send a test message"""
|
|
message = request.json.get('message', '🧪 Test alert from Signal Bridge')
|
|
|
|
if send_signal_message(message):
|
|
return jsonify({"status": "sent"}), 200
|
|
else:
|
|
return jsonify({"status": "failed"}), 500
|
|
|
|
if __name__ == '__main__':
|
|
app.run(host='0.0.0.0', port=5000)
|