Files
homelab-optimized/alerting/signal-bridge/app.py
Gitea Mirror Bot 24b249b290
Some checks failed
Documentation / Deploy to GitHub Pages (push) Has been cancelled
Documentation / Build Docusaurus (push) Has started running
Sanitized mirror from private repository - 2026-04-19 09:32:43 UTC
2026-04-19 09:32:43 +00:00

131 lines
3.9 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)