Initial import
This commit is contained in:
commit
b1552cb7a8
10
damntfy.conf.example
Normal file
10
damntfy.conf.example
Normal file
@ -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",
|
||||||
|
],
|
||||||
|
}
|
204
damntfy.pl
Executable file
204
damntfy.pl
Executable file
@ -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";
|
||||||
|
<h1>damntfy</h1>
|
||||||
|
<p>Alertmanager–ntfy integrator</a>
|
||||||
|
<dl>
|
||||||
|
<dt>POST <a href="/webhook">/webhook</a></dt>
|
||||||
|
<dd>Receives an Alertmanager webhook payload and sends a ntfy payload.</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
@@ layouts/main.html.ep
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
||||||
|
<title><%= title %></title>
|
||||||
|
<style type="text/css">
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd,
|
||||||
|
dl,
|
||||||
|
dt,
|
||||||
|
h1,
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0 auto;
|
||||||
|
min-height: 100vh;
|
||||||
|
max-width: 60rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
dl,
|
||||||
|
h1,
|
||||||
|
p {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 3rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
margin-left: 3rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
%= content
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user