commit b1552cb7a853d19d79c24f68ecc222b9f571232c Author: Lucas Gabriel Vuotto Date: Sun Dec 31 15:14:19 2023 +0000 Initial import diff --git a/damntfy.conf.example b/damntfy.conf.example new file mode 100644 index 0000000..e4206bd --- /dev/null +++ b/damntfy.conf.example @@ -0,0 +1,10 @@ +{ + ntfy_default_topic => "alerts", + ntfy_token => "tk_s3kr1tt0k3n", + ntfy_url => "https://ntfy.example.com", + + app_secrets => [ + # Generate with "openssl rand -base64 33". + "t0ps3kr1t", + ], +} diff --git a/damntfy.pl b/damntfy.pl new file mode 100755 index 0000000..dc90e59 --- /dev/null +++ b/damntfy.pl @@ -0,0 +1,204 @@ +#!/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 + +