#!/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()