From 4c7dcd9279c78fdd9dc6f95a032a15144f7f61f2 Mon Sep 17 00:00:00 2001 From: Oleks Date: Mon, 16 Mar 2026 17:26:06 +0200 Subject: [PATCH] alertmanager-gotify-bridge: initial commit with bridge and CI --- .woodpecker.yaml | 24 ++++++++++++++ Dockerfile | 5 +++ bridge.py | 86 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 .woodpecker.yaml create mode 100644 Dockerfile create mode 100644 bridge.py diff --git a/.woodpecker.yaml b/.woodpecker.yaml new file mode 100644 index 0000000..defdd9f --- /dev/null +++ b/.woodpecker.yaml @@ -0,0 +1,24 @@ +labels: + arch: amd64 + +when: + - event: tag + ref: "refs/tags/v*" + +steps: + - name: build-and-push + image: git.oleks.space/oleks/nix-ci:latest + environment: + REGISTRY_TOKEN: + from_secret: registry_token + commands: + - echo "$REGISTRY_TOKEN" | docker login git.oleks.space -u oleks --password-stdin + - docker buildx create --name amd64 --driver remote "tcp://buildkit-rootless-amd64.infra.svc.cluster.local:1234" + - TAG=$(echo "$CI_COMMIT_TAG" | sed 's/^v//') + - IMAGE="git.oleks.space/oleks/alertmanager-gotify-bridge" + - echo "Building $IMAGE:$TAG" + - docker buildx build --builder amd64 --platform linux/amd64 --tag "$IMAGE:$TAG" --tag "$IMAGE:latest" --push . + backend_options: + kubernetes: + nodeSelector: + kubernetes.io/hostname: howard2404 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1138ab0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3.12-alpine +COPY bridge.py /app/bridge.py +WORKDIR /app +EXPOSE 8080 +CMD ["python", "bridge.py"] diff --git a/bridge.py b/bridge.py new file mode 100644 index 0000000..6494f0e --- /dev/null +++ b/bridge.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +"""Alertmanager webhook receiver that forwards alerts to Gotify.""" + +import json +import os +import sys +from http.server import HTTPServer, BaseHTTPRequestHandler +from urllib.request import Request, urlopen +from urllib.error import URLError + +GOTIFY_URL = os.environ["GOTIFY_URL"] +DISASTER_TOKEN = os.environ["DISASTER_TOKEN"] +WARNING_TOKEN = os.environ["WARNING_TOKEN"] +DISASTER_PRIORITY = int(os.environ.get("DISASTER_PRIORITY", "8")) +WARNING_PRIORITY = int(os.environ.get("WARNING_PRIORITY", "4")) + +SEVERITY_MAP = { + "critical": (DISASTER_TOKEN, DISASTER_PRIORITY), + "warning": (WARNING_TOKEN, WARNING_PRIORITY), +} + + +def send_gotify(token, title, message, priority): + data = json.dumps({ + "title": title, + "message": message, + "priority": priority, + }).encode() + req = Request( + f"{GOTIFY_URL}/message?token={token}", + data=data, + headers={"Content-Type": "application/json"}, + ) + try: + urlopen(req) + except URLError as e: + print(f"ERROR sending to Gotify: {e}", file=sys.stderr) + + +class Handler(BaseHTTPRequestHandler): + def do_POST(self): + try: + length = int(self.headers.get("Content-Length", 0)) + body = json.loads(self.rfile.read(length)) + except (json.JSONDecodeError, ValueError) as e: + self.send_response(400) + self.end_headers() + self.wfile.write(f"Bad request: {e}".encode()) + return + + for alert in body.get("alerts", []): + severity = alert.get("labels", {}).get("severity", "warning") + token, priority = SEVERITY_MAP.get(severity, SEVERITY_MAP["warning"]) + status = alert.get("status", "firing") + alertname = alert.get("labels", {}).get("alertname", "Unknown") + summary = alert.get("annotations", {}).get("summary", "") + + prefix = "RESOLVED" if status == "resolved" else "FIRING" + title = f"[{prefix}] {alertname}" + message = summary or f"{alertname} is {status}" + + send_gotify(token, title, message, priority) + print(f"Forwarded: {title} -> severity={severity} priority={priority}") + + self.send_response(200) + self.end_headers() + self.wfile.write(b"ok") + + def do_GET(self): + if self.path == "/health": + self.send_response(200) + self.end_headers() + self.wfile.write(b"ok") + return + self.send_response(404) + self.end_headers() + + def log_message(self, format, *args): + print(format % args) + + +if __name__ == "__main__": + port = int(os.environ.get("PORT", "8080")) + server = HTTPServer(("0.0.0.0", port), Handler) + print(f"Bridge listening on :{port}") + server.serve_forever()