#!/usr/bin/env perl use Mojolicious::Lite -signatures; use Mojo::Headers; use Mojo::URL; use Mojo::Util qw(b64_encode); my $config = plugin "Config" => { default => { max_request_size => 1024 * 1024, ntfy_default_priority => 3, ntfy_default_topic => "am", ntfy_url => "https://ntfy.sh", app_secrets => [app->moniker], }, }; app->max_request_size($config->{max_request_size}); app->exception_format("txt"); app->secrets($config->{app_secrets}); sub am_validjson ($json) { return $json->{version} eq "4" && defined($json->{status}) && ref $json->{alerts} eq "ARRAY" && $json->{alerts}->@* > 0; } sub am_labelpairs ($m) { return map {"$_=$m->{$_}"} grep {$_ ne "alertname"} sort keys $m->%*; } sub ntfy_post ($ua, $json) { state $url = Mojo::URL->new($config->{ntfy_url}); state $headers = do { my $hs = Mojo::Headers->new(); $hs->accept("application/json"); $hs->content_type("application/json"); if (defined($config->{ntfy_token})) { $hs->authorization("Bearer $config->{ntfy_token}"); } elsif (defined($config->{ntfy_user}) && defined($config->{ntfy_pass})) { my $ui = "$config->{ntfy_user}:$config->{ntfy_pass}"; $hs->authorization("Basic " . b64_encode($ui)); } $hs->to_hash; }; return $ua->post($url, $headers, json => $json)->result; } get "/" => sub ($c) { $c->render(template => "index"); }; post "/webhook" => sub ($c) { my $query_params = $c->req->query_params; my $req_json = $c->req->json; return $c->reply->exception("Invalid input")->rendered(400) if ! am_validjson($req_json); my $priority = int($query_params->param("priority") // $config->{ntfy_default_priority}); my $topic = $query_params->param("topic") // $config->{ntfy_default_topic}; my $title = uc($req_json->{status}); $title .= " - $req_json->{groupLabels}->{alertname}" if defined $req_json->{groupLabels}->{alertname}; my @tags_common = am_labelpairs $req_json->{commonLabels}; my @tags_group = am_labelpairs $req_json->{groupLabels}; $c->stash( "am.groupKey" => $req_json->{groupKey}, "am.truncatedAlerts" => $req_json->{truncatedAlerts}, "am.status" => $req_json->{status}, "am.receiver" => $req_json->{receiver}, "am.groupLabels" => $req_json->{groupLabels}, "am.commonLabels" => $req_json->{commonLabels}, "am.commonAnnotations" => $req_json->{commonAnnotations}, "am.externalURL" => $req_json->{externalURL}, "am.alerts" => $req_json->{alerts}, "am.alerts.Firing" => [grep {$_->{status} eq "firing"} $req_json->{alerts}->@*], "am.alerts.Resolved" => [grep {$_->{status} eq "resolved"} $req_json->{alerts}->@*], ); my $json = { message => $c->render_to_string( template => "message", format => "txt", ), priority => $priority, tags => [@tags_group, @tags_common], title => $title, topic => $topic, }; my $ua = $c->ua->request_timeout(10); my $res = ntfy_post($ua, $json); return $c->reply->exception("Upstream error")->rendered($res->code) if !$res->is_success; $c->render(json => $res->json); }; app->start; __DATA__ @@ message.txt.ep % my $alerts_firing = stash "am.alerts.Firing"; % my $alerts_resolved = stash "am.alerts.Resolved"; % my $common_annotations = stash "am.commonAnnotations"; % if (defined $common_annotations->{summary}) { %= $common_annotations->{summary} % } % if ($alerts_firing->@* > 0) { FIRING:<%== scalar $alerts_firing->@* %> % for my $alert ($alerts_firing->@*) { %== $alert->{annotations}->{description}; % } % } % if ($alerts_resolved->@* > 0) { RESOLVED:<%== scalar $alerts_resolved->@* %> % for my $alert ($alerts_resolved->@*) { - <%== $alert->{labels}->{instance} %> % } % } @@ index.html.ep % layout "main"; % title "damntfy";

damntfy

Alertmanager–ntfy integrator

POST /webhook
Receives an Alertmanager webhook payload and sends a ntfy payload.
@@ layouts/main.html.ep <%= title %> %= content