Sanitized mirror from private repository - 2026-04-18 11:19:59 UTC
This commit is contained in:
140
hosts/vms/homelab-vm/gitea-ntfy-bridge/bridge.py
Normal file
140
hosts/vms/homelab-vm/gitea-ntfy-bridge/bridge.py
Normal file
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Gitea to ntfy Webhook Bridge - Translates Gitea events to ntfy notifications"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import urllib.request
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
# Force unbuffered output
|
||||
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', buffering=1)
|
||||
sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', buffering=1)
|
||||
|
||||
NTFY_URL = os.environ.get("NTFY_URL", "https://ntfy.vish.gg")
|
||||
NTFY_TOPIC = os.environ.get("NTFY_TOPIC", "homelab-alerts")
|
||||
|
||||
class WebhookHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
"""Health check endpoint"""
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/plain")
|
||||
self.end_headers()
|
||||
self.wfile.write(b"Gitea-ntfy bridge OK\n")
|
||||
print(f"Health check from {self.client_address[0]}", flush=True)
|
||||
|
||||
def do_POST(self):
|
||||
content_length = int(self.headers.get("Content-Length", 0))
|
||||
body = self.rfile.read(content_length)
|
||||
|
||||
try:
|
||||
data = json.loads(body) if body else {}
|
||||
event_type = self.headers.get("X-Gitea-Event", "unknown")
|
||||
|
||||
print(f"Received {event_type} event from {self.client_address[0]}", flush=True)
|
||||
|
||||
title, message, tags, priority = self.format_message(event_type, data)
|
||||
|
||||
if title and message:
|
||||
print(f"Sending notification: {title}", flush=True)
|
||||
self.send_ntfy(title, message, tags, priority)
|
||||
self.send_response(200)
|
||||
else:
|
||||
print(f"Ignoring event type: {event_type}", flush=True)
|
||||
self.send_response(204) # No content to send
|
||||
except Exception as e:
|
||||
print(f"Error processing webhook: {e}", flush=True)
|
||||
self.send_response(500)
|
||||
|
||||
self.end_headers()
|
||||
|
||||
def format_message(self, event_type, data):
|
||||
"""Format Gitea event into ntfy message"""
|
||||
repo = data.get("repository", {}).get("full_name", "unknown")
|
||||
sender = data.get("sender", {}).get("login", "unknown")
|
||||
|
||||
title = None
|
||||
message = None
|
||||
tags = "git"
|
||||
priority = "default"
|
||||
|
||||
if event_type == "push":
|
||||
commits = data.get("commits", [])
|
||||
branch = data.get("ref", "").replace("refs/heads/", "")
|
||||
count = len(commits)
|
||||
title = f"Push to {repo}"
|
||||
message = f"{sender} pushed {count} commit(s) to {branch}"
|
||||
if commits:
|
||||
message += f"\n\n* {commits[0].get('message', '').split(chr(10))[0]}"
|
||||
if count > 1:
|
||||
message += f"\n* ... and {count - 1} more"
|
||||
tags = "package"
|
||||
|
||||
elif event_type == "pull_request":
|
||||
action = data.get("action", "")
|
||||
pr = data.get("pull_request", {})
|
||||
pr_title = pr.get("title", "")
|
||||
pr_num = pr.get("number", "")
|
||||
title = f"PR #{pr_num} {action}"
|
||||
message = f"{repo}: {pr_title}\nBy: {sender}"
|
||||
tags = "twisted_rightwards_arrows"
|
||||
if action == "opened":
|
||||
priority = "high"
|
||||
|
||||
elif event_type == "issues":
|
||||
action = data.get("action", "")
|
||||
issue = data.get("issue", {})
|
||||
issue_title = issue.get("title", "")
|
||||
issue_num = issue.get("number", "")
|
||||
title = f"Issue #{issue_num} {action}"
|
||||
message = f"{repo}: {issue_title}\nBy: {sender}"
|
||||
tags = "clipboard"
|
||||
|
||||
elif event_type == "release":
|
||||
action = data.get("action", "")
|
||||
release = data.get("release", {})
|
||||
tag = release.get("tag_name", "")
|
||||
title = f"Release {tag}"
|
||||
message = f"{repo}: New release {action}\n{release.get('name', tag)}"
|
||||
tags = "rocket"
|
||||
priority = "high"
|
||||
|
||||
elif event_type == "create":
|
||||
ref_type = data.get("ref_type", "")
|
||||
ref = data.get("ref", "")
|
||||
title = f"New {ref_type}: {ref}"
|
||||
message = f"{repo}\nCreated by: {sender}"
|
||||
tags = "sparkles"
|
||||
|
||||
elif event_type == "delete":
|
||||
ref_type = data.get("ref_type", "")
|
||||
ref = data.get("ref", "")
|
||||
title = f"Deleted {ref_type}: {ref}"
|
||||
message = f"{repo}\nDeleted by: {sender}"
|
||||
tags = "wastebasket"
|
||||
|
||||
return title, message, tags, priority
|
||||
|
||||
def send_ntfy(self, title, message, tags="git", priority="default"):
|
||||
"""Send notification to ntfy"""
|
||||
url = f"{NTFY_URL}/{NTFY_TOPIC}"
|
||||
headers = {
|
||||
"Title": title,
|
||||
"Tags": tags,
|
||||
"Priority": priority,
|
||||
}
|
||||
|
||||
req = urllib.request.Request(url, data=message.encode('utf-8'), headers=headers, method="POST")
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
print(f"Sent: {title} -> {resp.status}")
|
||||
except Exception as e:
|
||||
print(f"Failed to send ntfy: {e}")
|
||||
|
||||
def log_message(self, format, *args):
|
||||
print(f"[{self.log_date_time_string()}] {format % args}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
server = HTTPServer(("0.0.0.0", 8095), WebhookHandler)
|
||||
print(f"Gitea-ntfy bridge running on :8095 -> {NTFY_URL}/{NTFY_TOPIC}")
|
||||
server.serve_forever()
|
||||
Reference in New Issue
Block a user