64 lines
2.8 KiB
Python
64 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Receive Beszel's Shoutrrr JSON and deliver through the supported postbox CLI."""
|
|
import json
|
|
import os
|
|
import subprocess
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
|
|
|
|
def deliver(payload):
|
|
title = payload.get('title', 'Beszel fleet alert')
|
|
message = payload.get('message')
|
|
if not isinstance(title, str) or not isinstance(message, str) or not message.strip():
|
|
raise ValueError('Expected a nonempty message and string title')
|
|
result = subprocess.run(
|
|
[os.environ['POSTBOX'], '--json', 'send', '--to', os.environ['BESZEL_ALERT_RECIPIENT'],
|
|
'--subject', '[Beszel] ' + title],
|
|
input=message + '\n\nHub: http://10.250.50.70:8090\n',
|
|
text=True, capture_output=True, timeout=25,
|
|
)
|
|
if result.returncode:
|
|
raise RuntimeError('postbox delivery failed: ' + result.stderr.strip())
|
|
receipt = json.loads(result.stdout)
|
|
print(json.dumps({'event': 'delivered', 'title': title, 'receipt': receipt}), flush=True)
|
|
return receipt
|
|
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def respond(self, status, body):
|
|
data = json.dumps(body).encode()
|
|
self.send_response(status)
|
|
self.send_header('Content-Type', 'application/json')
|
|
self.send_header('Content-Length', str(len(data)))
|
|
self.end_headers()
|
|
self.wfile.write(data)
|
|
|
|
def do_GET(self):
|
|
self.respond(200 if self.path == '/healthz' else 404,
|
|
{'service': 'beszel-althing', 'delivery': 'verified per POST'})
|
|
|
|
def do_POST(self):
|
|
if self.path != '/beszel':
|
|
return self.respond(404, {'error': 'Unknown route'})
|
|
if self.client_address[0] not in os.environ['BESZEL_ALLOWED_SOURCES'].split(','):
|
|
return self.respond(403, {'error': 'Source not allowed'})
|
|
try:
|
|
length = int(self.headers.get('Content-Length', '0'))
|
|
if not 0 < length <= 65536:
|
|
raise ValueError('Invalid body size')
|
|
self.connection.settimeout(10)
|
|
payload = json.loads(self.rfile.read(length))
|
|
if not isinstance(payload, dict):
|
|
raise ValueError('Expected JSON object')
|
|
receipt = deliver(payload)
|
|
except (ValueError, TypeError) as exc:
|
|
return self.respond(400, {'error': str(exc)})
|
|
except (OSError, RuntimeError, subprocess.TimeoutExpired) as exc:
|
|
print(json.dumps({'event': 'delivery_failed', 'error': str(exc)}), flush=True)
|
|
return self.respond(502, {'error': 'Althing delivery failed; inspect service journal'})
|
|
self.respond(200, {'delivered': True, 'receipt': receipt})
|
|
|
|
|
|
if __name__ == '__main__':
|
|
ThreadingHTTPServer((os.environ['BESZEL_BIND_HOST'], int(os.environ.get('BESZEL_BIND_PORT', '8096'))), Handler).serve_forever()
|