Files
SnarfCode/scripts/setup_zabbix_notifications.py

206 lines
7.7 KiB
Python

#!/usr/bin/env python3
"""
Configure Zabbix 7.0.16 Notification Pipeline:
1. Creates/Updates 'Snarfnet Gotify + Discord Failover' Webhook MediaType.
- Primary path: Sends to Gotify (http://140.44.4.70:8090) -> pushes to devices + Gotify-Discord Bridge.
- Failover path: If Gotify/Shipyard is unreachable, sends directly to Discord (#infra-alerts or #ups-power).
2. Attaches MediaType to Zabbix 'Admin' user (userid=1).
3. Enables Trigger Action 'Report problems to Zabbix administrators' (status=0).
"""
import json
import ssl
import urllib.request
ZABBIX_URL = "https://zabbix.snarfnet.net/api_jsonrpc.php"
ZABBIX_USER = "Admin"
ZABBIX_PASS = "zabbix"
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
def zbx_req(method: str, params: dict, auth: str = None):
payload = {
"jsonrpc": "2.0",
"method": method,
"params": params,
"id": 1,
}
headers = {"Content-Type": "application/json-rpc"}
if auth:
headers["Authorization"] = f"Bearer {auth}"
req = urllib.request.Request(
ZABBIX_URL,
data=json.dumps(payload).encode("utf-8"),
headers=headers,
method="POST",
)
with urllib.request.urlopen(req, timeout=10, context=ctx) as r:
resp = json.loads(r.read().decode("utf-8"))
if "error" in resp:
raise RuntimeError(f"Zabbix API error in {method}: {resp['error']}")
return resp["result"]
WEBHOOK_JS = r"""
try {
var params = JSON.parse(value);
var subject = params.Subject || 'Zabbix Alert';
var message = params.Message || '';
var severity = parseInt(params.Severity || '3', 10);
var host = (params.Host || '').toLowerCase();
// Map Zabbix severity (0-5) to Gotify priority (2-10)
var prioMap = {0: 2, 1: 3, 2: 5, 3: 7, 4: 9, 5: 10};
var priority = prioMap[severity] || 6;
var isUps = (host.indexOf('nut') !== -1 || host.indexOf('ups') !== -1 || subject.toLowerCase().indexOf('ups') !== -1);
var gotifyToken = isUps ? params.GotifyUpsToken : params.GotifyInfraToken;
var discordWebhook = isUps ? params.DiscordUpsWebhook : params.DiscordInfraWebhook;
var req = new HttpRequest();
req.addHeader('Content-Type: application/json');
var gotifyUrl = params.GotifyUrl.replace(/\/$/, '') + '/message?token=' + gotifyToken;
var gotifyPayload = JSON.stringify({
title: subject,
message: message,
priority: priority
});
var resp = req.post(gotifyUrl, gotifyPayload);
if (req.getStatus() >= 200 && req.getStatus() < 300) {
return 'OK (Delivered via Gotify + Discord Bridge)';
}
// Automatic Direct-to-Discord Failover if Gotify/Shipyard is unreachable
Zabbix.log(3, '[Snarfnet Webhook] Gotify returned HTTP ' + req.getStatus() + ', failing over directly to Discord.');
var dReq = new HttpRequest();
dReq.addHeader('Content-Type: application/json');
var color = (priority >= 8) ? 15158332 : ((priority >= 5) ? 15965202 : 3447003);
var discordPayload = JSON.stringify({
username: 'Snarfnet Zabbix (Direct Failover)',
embeds: [{
title: subject.substring(0, 256),
description: message.substring(0, 4000),
color: color,
footer: {text: 'Zabbix Direct Failover • Priority ' + priority}
}]
});
dReq.post(discordWebhook, discordPayload);
if (dReq.getStatus() >= 200 && dReq.getStatus() < 300) {
return 'OK (Delivered via Direct Discord Failover)';
}
throw 'Both Gotify and Direct Discord failed (Discord HTTP ' + dReq.getStatus() + ')';
} catch (error) {
Zabbix.log(3, '[Snarfnet Webhook] Error: ' + error);
throw 'Sending failed: ' + error;
}
"""
def main():
token = zbx_req("user.login", {"username": ZABBIX_USER, "password": ZABBIX_PASS})
print("Authenticated to Zabbix 7.0.16 API.")
mt_name = "Snarfnet Gotify + Discord Failover"
existing = zbx_req("mediatype.get", {"filter": {"name": [mt_name]}}, auth=token)
mt_params = [
{"name": "Subject", "value": "{ALERT.SUBJECT}"},
{"name": "Message", "value": "{ALERT.MESSAGE}"},
{"name": "Severity", "value": "{EVENT.NSEVERITY}"},
{"name": "Host", "value": "{HOST.NAME}"},
{"name": "GotifyUrl", "value": "http://140.44.4.70:8090"},
{"name": "GotifyInfraToken", "value": "A7zBx9InFrAaLeRtStOk"},
{"name": "GotifyUpsToken", "value": "A4uN4fEmErGeNcYtOkEn"},
{
"name": "DiscordInfraWebhook",
"value": "https://discord.com/api/webhooks/1566076777963593772/wKN30N0ZXVJKGP-prZ1_P9cp_u7Oakmh8R4bVh0qr8RFpyeGcJZrHfPQBJxtfzQqb4dn",
},
{
"name": "DiscordUpsWebhook",
"value": "https://discord.com/api/webhooks/1566076118535114813/2e_fCJeHb-mF8VwerzOORGRa-QUDFuwN6R2Gfz-MGUiIOudA0PiXGZSyd19PNFG-vR7D",
},
]
mt_templates = [
{
"eventsource": 0, # Triggers
"recovery": 0, # Problem
"subject": "🚨 [{EVENT.SEVERITY}] {HOST.NAME}: {EVENT.NAME}",
"message": "**Host:** `{HOST.NAME}` (`{HOST.IP}`)\n**Severity:** {EVENT.SEVERITY}\n**Trigger:** {EVENT.NAME}\n**Status:** {EVENT.STATUS}\n**Time:** {EVENT.DATE} {EVENT.TIME}\n**Event ID:** {EVENT.ID}",
},
{
"eventsource": 0, # Triggers
"recovery": 1, # Recovery
"subject": "✅ [RESOLVED] {HOST.NAME}: {EVENT.NAME}",
"message": "**Host:** `{HOST.NAME}` (`{HOST.IP}`)\n**Resolved After:** {EVENT.DURATION}\n**Trigger:** {EVENT.NAME}\n**Time:** {EVENT.RECOVERY.DATE} {EVENT.RECOVERY.TIME}\n**Event ID:** {EVENT.ID}",
},
]
if existing:
mt_id = existing[0]["mediatypeid"]
zbx_req(
"mediatype.update",
{
"mediatypeid": mt_id,
"type": 4, # Webhook
"status": 0, # Enabled
"script": WEBHOOK_JS.strip(),
"timeout": "15s",
"parameters": mt_params,
"message_templates": mt_templates,
},
auth=token,
)
print(f"Updated MediaType '{mt_name}' (mediatypeid={mt_id})")
else:
res = zbx_req(
"mediatype.create",
{
"name": mt_name,
"type": 4, # Webhook
"status": 0, # Enabled
"script": WEBHOOK_JS.strip(),
"timeout": "15s",
"parameters": mt_params,
"message_templates": mt_templates,
},
auth=token,
)
mt_id = res["mediatypeids"][0]
print(f"Created MediaType '{mt_name}' (mediatypeid={mt_id})")
# Attach to Admin user (userid=1) for Warning, Average, High, Disaster (bitmask 60 = 4+8+16+32)
zbx_req(
"user.update",
{
"userid": "1",
"medias": [
{
"mediatypeid": str(mt_id),
"sendto": "snarf@snarfnet.net",
"active": 0,
"severity": 60, # Warning (2), Average (3), High (4), Disaster (5)
"period": "1-7,00:00-24:00",
}
],
},
auth=token,
)
print("Attached MediaType to Zabbix Admin user (userid=1).")
# Enable Trigger Action 'Report problems to Zabbix administrators'
actions = zbx_req("action.get", {"filter": {"eventsource": 0}}, auth=token)
for act in actions:
if "Report problems" in act["name"] or act["actionid"] == "3":
zbx_req("action.update", {"actionid": act["actionid"], "status": 0}, auth=token)
print(f"Enabled Trigger Action '{act['name']}' (actionid={act['actionid']}).")
if __name__ == "__main__":
main()